Refactor some more model views to use MasterView.

(emailbounce, depositlink, tax)
This commit is contained in:
Lance Edgar 2016-02-14 21:34:01 -06:00
parent bc1c7b3554
commit 215a8c13b0
9 changed files with 123 additions and 328 deletions

View file

@ -1,13 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Deposit Links", url('depositlinks'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Deposit Link", url('depositlink.edit', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Deposit Link", url('depositlink.view', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,12 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/grid.mako" />
<%def name="title()">Deposit Links</%def>
<%def name="context_menu_items()">
% if request.has_perm('depositlinks.create'):
<li>${h.link_to("Create a new Deposit Link", url('depositlink.new'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,15 +1,5 @@
## -*- coding: utf-8 -*-
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<% bounce = form.fieldset.model %>
<li>${h.link_to("Back to Email Bounces", url('emailbounces'))}</li>
% if not bounce.processed and request.has_perm('emailbounces.process'):
<li>${h.link_to("Mark this Email Bounce as Processed", url('emailbounce.process', uuid=bounce.uuid))}</li>
% elif bounce.processed and request.has_perm('emailbounces.unprocess'):
<li>${h.link_to("Mark this Email Bounce as UN-processed", url('emailbounce.unprocess', uuid=bounce.uuid))}</li>
% endif
</%def>
<%inherit file="/master/view.mako" />
<%def name="head_tags()">
${parent.head_tags()}
@ -44,6 +34,15 @@
</script>
</%def>
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if not bounce.processed and request.has_perm('emailbounces.process'):
<li>${h.link_to("Mark this Email Bounce as Processed", url('emailbounces.process', uuid=bounce.uuid))}</li>
% elif bounce.processed and request.has_perm('emailbounces.unprocess'):
<li>${h.link_to("Mark this Email Bounce as UN-processed", url('emailbounces.unprocess', uuid=bounce.uuid))}</li>
% endif
</%def>
${parent.body()}
<pre id="message">

View file

@ -1,6 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/grid.mako" />
<%def name="title()">Email Bounces</%def>
${parent.body()}

View file

@ -1,13 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Taxes", url('taxes'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Tax", url('tax.edit', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Tax", url('tax.view', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,12 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/grid.mako" />
<%def name="title()">Taxes</%def>
<%def name="context_menu_items()">
% if request.has_perm('taxes.create'):
<li>${h.link_to("Create a new Tax", url('tax.new'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar
# Copyright © 2010-2016 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,7 +24,7 @@
Views for Email Bounces
"""
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
import os
import datetime
@ -35,75 +35,45 @@ from rattail.bouncer.config import get_profile_keys
import formalchemy
from pyramid.response import FileResponse
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from webhelpers.html import literal
from tailbone import newgrids as grids
from tailbone.db import Session
from tailbone.views import SearchableAlchemyGridView, CrudView
from tailbone.forms import renderers
from tailbone.views import MasterView
from tailbone.forms.renderers.bouncer import BounceMessageFieldRenderer
from tailbone.grids.search import BooleanSearchFilter, ChoiceSearchFilter
class EmailBouncesGrid(SearchableAlchemyGridView):
class EmailBouncesView(MasterView):
"""
Main grid view for email bounces.
Master view for email bounces.
"""
mapped_class = model.EmailBounce
config_prefix = 'emailbounces'
model_class = model.EmailBounce
model_title_plural = "Email Bounces"
url_prefix = '/email-bounces'
creatable = False
editable = False
def __init__(self, request):
super(EmailBouncesGrid, self).__init__(request)
super(EmailBouncesView, self).__init__(request)
self.handler_options = [('', '(any)')] + sorted(get_profile_keys(self.rattail_config))
def join_map(self):
return {
'processed_by': lambda q: q.outerjoin(model.User),
}
def get_handler(self, bounce):
return get_handler(self.rattail_config, bounce.config_key)
def filter_map(self):
def processed_is(q, v):
if v == 'True':
return q.filter(model.EmailBounce.processed != None)
else:
return q.filter(model.EmailBounce.processed == None)
def processed_nt(q, v):
if v == 'True':
return q.filter(model.EmailBounce.processed == None)
else:
return q.filter(model.EmailBounce.processed != None)
return self.make_filter_map(
exact=['config_key'],
ilike=['bounce_recipient_address', 'intended_recipient_address'],
processed={'is': processed_is, 'nt': processed_nt},
processed_by=self.filter_ilike(model.User.username))
def filter_config(self):
return self.make_filter_config(
include_filter_config_key=True,
filter_type_config_key='is',
filter_label_config_key="Source",
filter_factory_config_key=ChoiceSearchFilter(self.handler_options),
filter_factory_processed=BooleanSearchFilter,
filter_type_processed='is',
processed=False,
include_filter_processed=True,
filter_label_bounce_recipient_address="Bounced To",
filter_label_intended_recipient_address="Intended For")
def sort_config(self):
return self.make_sort_config(sort='bounced', dir='desc')
def sort_map(self):
return self.make_sort_map(
'config_key', 'bounced', 'bounce_recipient_address', 'intended_recipient_address',
processed_by=self.sorter(model.User.username))
def grid(self):
g = self.make_grid()
def configure_grid(self, g):
g.joiners['processed_by'] = lambda q: q.outerjoin(model.User)
g.filters['config_key'].default_active = True
g.filters['config_key'].default_verb = 'equal'
g.filters['config_key'].label = "Source"
g.filters['config_key'].set_value_renderer(grids.filters.ChoiceValueRenderer(self.handler_options))
g.filters['bounce_recipient_address'].label = "Bounced To"
g.filters['intended_recipient_address'].label = "Intended For"
g.filters['processed'].default_active = True
g.filters['processed'].default_verb = 'is_null'
g.filters['processed_by'] = g.make_filter('processed_by', model.User.username)
g.sorters['processed_by'] = g.make_sorter(model.User.username)
g.default_sortkey = 'bounced'
g.default_sortdir = 'desc'
g.configure(
include=[
g.config_key.label("Source"),
@ -113,44 +83,10 @@ class EmailBouncesGrid(SearchableAlchemyGridView):
g.processed_by,
],
readonly=True)
if self.request.has_perm('emailbounces.view'):
g.viewable = True
g.view_route_name = 'emailbounce'
if self.request.has_perm('emailbounces.delete'):
g.deletable = True
g.delete_route_name = 'emailbounce.delete'
return g
class LinksFieldRenderer(formalchemy.FieldRenderer):
def render_readonly(self, **kwargs):
value = self.raw_value
if not value:
return 'n/a'
html = literal('<ul>')
for link in value:
html += literal('<li>{0}:&nbsp; <a href="{1}" target="_blank">{2}</a></li>'.format(
link.type, link.url, link.title))
html += literal('</ul>')
return html
class EmailBounceCrud(CrudView):
"""
Main CRUD view for email bounces.
"""
mapped_class = model.EmailBounce
home_route = 'emailbounces'
pretty_name = "Email Bounce"
def get_handler(self, bounce):
return get_handler(self.rattail_config, bounce.config_key)
def fieldset(self, bounce):
assert isinstance(bounce, model.EmailBounce)
def configure_fieldset(self, fs):
bounce = fs.model
handler = self.get_handler(bounce)
fs = self.make_fieldset(bounce)
fs.append(formalchemy.Field('message',
value=handler.msgpath(bounce),
renderer=BounceMessageFieldRenderer.new(self.request, handler)))
@ -172,11 +108,10 @@ class EmailBounceCrud(CrudView):
if not bounce.processed:
del fs.processed
del fs.processed_by
return fs
def template_kwargs(self, form):
kwargs = super(EmailBounceCrud, self).template_kwargs(form)
bounce = form.fieldset.model
def template_kwargs_view(self, **kwargs):
bounce = kwargs['instance']
kwargs['bounce'] = bounce
handler = self.get_handler(bounce)
kwargs['handler'] = handler
with open(handler.msgpath(bounce), 'rb') as f:
@ -187,33 +122,27 @@ class EmailBounceCrud(CrudView):
"""
View for marking a bounce as processed.
"""
bounce = self.get_model_from_request()
if not bounce:
return HTTPNotFound()
bounce = self.get_instance()
bounce.processed = datetime.datetime.utcnow()
bounce.processed_by = self.request.user
self.request.session.flash("Email bounce has been marked processed.")
return HTTPFound(location=self.request.route_url('emailbounces'))
return self.redirect(self.request.route_url('emailbounces'))
def unprocess(self):
"""
View for marking a bounce as *unprocessed*.
"""
bounce = self.get_model_from_request()
if not bounce:
return HTTPNotFound()
bounce = self.get_instance()
bounce.processed = None
bounce.processed_by = None
self.request.session.flash("Email bounce has been marked UN-processed.")
return HTTPFound(location=self.request.route_url('emailbounces'))
return self.redirect(self.request.route_url('emailbounces'))
def download(self):
"""
View for downloading the message file associated with a bounce.
"""
bounce = self.get_model_from_request()
if not bounce:
return HTTPNotFound()
bounce = self.get_instance()
handler = self.get_handler(bounce)
path = handler.msgpath(bounce)
response = FileResponse(path, request=self.request)
@ -221,35 +150,46 @@ class EmailBounceCrud(CrudView):
response.headers[b'Content-Disposition'] = b'attachment; filename="bounce.eml"'
return response
@classmethod
def defaults(cls, config):
def add_routes(config):
config.add_route('emailbounces', '/emailbounces/')
config.add_route('emailbounce', '/emailbounces/{uuid}')
config.add_route('emailbounce.process', '/emailbounces/{uuid}/process')
config.add_route('emailbounce.unprocess', '/emailbounces/{uuid}/unprocess')
config.add_route('emailbounce.delete', '/emailbounces/{uuid}/delete')
config.add_route('emailbounce.download', '/emailbounces/{uuid}/download')
# mark bounce as processed
config.add_route('emailbounces.process', '/email-bounces/{uuid}/process')
config.add_view(cls, attr='process', route_name='emailbounces.process',
permission='emailbounces.process')
config.add_tailbone_permission('emailbounces', 'emailbounces.process',
"Mark Email Bounce as processed")
# mark bounce as *not* processed
config.add_route('emailbounces.unprocess', '/email-bounces/{uuid}/unprocess')
config.add_view(cls, attr='unprocess', route_name='emailbounces.unprocess',
permission='emailbounces.unprocess')
config.add_tailbone_permission('emailbounces', 'emailbounces.unprocess',
"Mark Email Bounce as UN-processed")
# download raw email
config.add_route('emailbounces.download', '/email-bounces/{uuid}/download')
config.add_view(cls, attr='download', route_name='emailbounces.download',
permission='emailbounces.download')
config.add_tailbone_permission('emailbounces', 'emailbounces.download',
"Download raw message of Email Bounce")
cls._defaults(config)
class LinksFieldRenderer(formalchemy.FieldRenderer):
def render_readonly(self, **kwargs):
value = self.raw_value
if not value:
return 'n/a'
html = literal('<ul>')
for link in value:
html += literal('<li>{0}:&nbsp; <a href="{1}" target="_blank">{2}</a></li>'.format(
link.type, link.url, link.title))
html += literal('</ul>')
return html
def includeme(config):
add_routes(config)
config.add_view(EmailBouncesGrid, route_name='emailbounces',
renderer='/emailbounces/index.mako',
permission='emailbounces.list')
config.add_view(EmailBounceCrud, attr='read', route_name='emailbounce',
renderer='/emailbounces/crud.mako',
permission='emailbounces.view')
config.add_view(EmailBounceCrud, attr='process', route_name='emailbounce.process',
permission='emailbounces.process')
config.add_view(EmailBounceCrud, attr='unprocess', route_name='emailbounce.unprocess',
permission='emailbounces.unprocess')
config.add_view(EmailBounceCrud, attr='download', route_name='emailbounce.download',
permission='emailbounces.download')
config.add_view(EmailBounceCrud, attr='delete', route_name='emailbounce.delete',
permission='emailbounces.delete')
EmailBouncesView.defaults(config)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar
# Copyright © 2010-2016 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,29 +24,24 @@
Deposit Link Views
"""
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from rattail.db import model
from tailbone.views import SearchableAlchemyGridView, CrudView
from tailbone.views import MasterView
class DepositLinksGrid(SearchableAlchemyGridView):
class DepositLinksView(MasterView):
"""
Master view for deposit links.
"""
model_class = model.DepositLink
url_prefix = '/deposit-links'
mapped_class = model.DepositLink
config_prefix = 'depositlinks'
sort = 'code'
def filter_map(self):
return self.make_filter_map(exact=['code', 'amount'],
ilike=['description'])
def filter_config(self):
return self.make_filter_config(include_filter_description=True,
filter_type_description='lk')
def grid(self):
g = self.make_grid()
def configure_grid(self, g):
g.filters['description'].default_active = True
g.filters['description'].default_verb = 'contains'
g.default_sortkey = 'code'
g.configure(
include=[
g.code,
@ -54,55 +49,15 @@ class DepositLinksGrid(SearchableAlchemyGridView):
g.amount,
],
readonly=True)
if self.request.has_perm('depositlinks.view'):
g.viewable = True
g.view_route_name = 'depositlink.view'
if self.request.has_perm('depositlinks.edit'):
g.editable = True
g.edit_route_name = 'depositlink.edit'
if self.request.has_perm('depositlinks.delete'):
g.deletable = True
g.delete_route_name = 'depositlink.delete'
return g
class DepositLinkCrud(CrudView):
mapped_class = model.DepositLink
home_route = 'depositlinks'
def fieldset(self, model):
fs = self.make_fieldset(model)
def configure_fieldset(self, fs):
fs.configure(
include=[
fs.code,
fs.description,
fs.amount,
])
return fs
def add_routes(config):
config.add_route('depositlinks', '/depositlinks')
config.add_route('depositlink.new', '/depositlinks/new')
config.add_route('depositlink.view', '/depositlinks/{uuid}')
config.add_route('depositlink.edit', '/depositlinks/{uuid}/edit')
config.add_route('depositlink.delete', '/depositlinks/{uuid}/delete')
def includeme(config):
add_routes(config)
# list deposit links
config.add_view(DepositLinksGrid, route_name='depositlinks',
renderer='/depositlinks/index.mako', permission='depositlinks.list')
# deposit link crud
config.add_view(DepositLinkCrud, attr='create', route_name='depositlink.new',
renderer='/depositlinks/crud.mako', permission='depositlinks.create')
config.add_view(DepositLinkCrud, attr='read', route_name='depositlink.view',
renderer='/depositlinks/crud.mako', permission='depositlinks.view')
config.add_view(DepositLinkCrud, attr='update', route_name='depositlink.edit',
renderer='/depositlinks/crud.mako', permission='depositlinks.edit')
config.add_view(DepositLinkCrud, attr='delete', route_name='depositlink.delete',
permission='depositlinks.delete')
DepositLinksView.defaults(config)

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar
# Copyright © 2010-2016 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,28 +24,25 @@
Tax Views
"""
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from rattail.db import model
from tailbone.views import SearchableAlchemyGridView, CrudView
from tailbone.views import MasterView
class TaxesGrid(SearchableAlchemyGridView):
class TaxesView(MasterView):
"""
Master view for taxes.
"""
model_class = model.Tax
model_title_plural = "Taxes"
route_prefix = 'taxes'
mapped_class = model.Tax
config_prefix = 'taxes'
sort = 'code'
def filter_map(self):
return self.make_filter_map(exact=['code'], ilike=['description'])
def filter_config(self):
return self.make_filter_config(include_filter_description=True,
filter_type_description='lk')
def grid(self):
g = self.make_grid()
def configure_grid(self, g):
g.filters['description'].default_active = True
g.filters['description'].default_verb = 'contains'
g.default_sortkey = 'code'
g.configure(
include=[
g.code,
@ -53,55 +50,15 @@ class TaxesGrid(SearchableAlchemyGridView):
g.rate,
],
readonly=True)
if self.request.has_perm('taxes.view'):
g.viewable = True
g.view_route_name = 'tax.view'
if self.request.has_perm('taxes.edit'):
g.editable = True
g.edit_route_name = 'tax.edit'
if self.request.has_perm('taxes.delete'):
g.deletable = True
g.delete_route_name = 'tax.delete'
return g
class TaxCrud(CrudView):
mapped_class = model.Tax
home_route = 'taxes'
def fieldset(self, model):
fs = self.make_fieldset(model)
def configure_fieldset(self, fs):
fs.configure(
include=[
fs.code,
fs.description,
fs.rate,
])
return fs
def add_routes(config):
config.add_route('taxes', '/taxes')
config.add_route('tax.new', '/taxes/new')
config.add_route('tax.view', '/taxes/{uuid}')
config.add_route('tax.edit', '/taxes/{uuid}/edit')
config.add_route('tax.delete', '/taxes/{uuid}/delete')
def includeme(config):
add_routes(config)
# list taxes
config.add_view(TaxesGrid, route_name='taxes',
renderer='/taxes/index.mako', permission='taxes.list')
# tax crud
config.add_view(TaxCrud, attr='create', route_name='tax.new',
renderer='/taxes/crud.mako', permission='taxes.create')
config.add_view(TaxCrud, attr='read', route_name='tax.view',
renderer='/taxes/crud.mako', permission='taxes.view')
config.add_view(TaxCrud, attr='update', route_name='tax.edit',
renderer='/taxes/crud.mako', permission='taxes.edit')
config.add_view(TaxCrud, attr='delete', route_name='tax.delete',
permission='taxes.delete')
TaxesView.defaults(config)