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 -*- ## -*- coding: utf-8 -*-
<%inherit file="/crud.mako" /> <%inherit file="/master/view.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>
<%def name="head_tags()"> <%def name="head_tags()">
${parent.head_tags()} ${parent.head_tags()}
@ -44,6 +34,15 @@
</script> </script>
</%def> </%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()} ${parent.body()}
<pre id="message"> <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 # Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar # Copyright © 2010-2016 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -24,7 +24,7 @@
Views for Email Bounces Views for Email Bounces
""" """
from __future__ import unicode_literals from __future__ import unicode_literals, absolute_import
import os import os
import datetime import datetime
@ -35,75 +35,45 @@ from rattail.bouncer.config import get_profile_keys
import formalchemy import formalchemy
from pyramid.response import FileResponse from pyramid.response import FileResponse
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from webhelpers.html import literal from webhelpers.html import literal
from tailbone import newgrids as grids
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import SearchableAlchemyGridView, CrudView from tailbone.views import MasterView
from tailbone.forms import renderers
from tailbone.forms.renderers.bouncer import BounceMessageFieldRenderer 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 model_class = model.EmailBounce
config_prefix = 'emailbounces' model_title_plural = "Email Bounces"
url_prefix = '/email-bounces'
creatable = False
editable = False
def __init__(self, request): 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)) self.handler_options = [('', '(any)')] + sorted(get_profile_keys(self.rattail_config))
def join_map(self): def get_handler(self, bounce):
return { return get_handler(self.rattail_config, bounce.config_key)
'processed_by': lambda q: q.outerjoin(model.User),
}
def filter_map(self): def configure_grid(self, g):
g.joiners['processed_by'] = lambda q: q.outerjoin(model.User)
def processed_is(q, v): g.filters['config_key'].default_active = True
if v == 'True': g.filters['config_key'].default_verb = 'equal'
return q.filter(model.EmailBounce.processed != None) g.filters['config_key'].label = "Source"
else: g.filters['config_key'].set_value_renderer(grids.filters.ChoiceValueRenderer(self.handler_options))
return q.filter(model.EmailBounce.processed == None) g.filters['bounce_recipient_address'].label = "Bounced To"
g.filters['intended_recipient_address'].label = "Intended For"
def processed_nt(q, v): g.filters['processed'].default_active = True
if v == 'True': g.filters['processed'].default_verb = 'is_null'
return q.filter(model.EmailBounce.processed == None) g.filters['processed_by'] = g.make_filter('processed_by', model.User.username)
else: g.sorters['processed_by'] = g.make_sorter(model.User.username)
return q.filter(model.EmailBounce.processed != None) g.default_sortkey = 'bounced'
g.default_sortdir = 'desc'
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()
g.configure( g.configure(
include=[ include=[
g.config_key.label("Source"), g.config_key.label("Source"),
@ -113,44 +83,10 @@ class EmailBouncesGrid(SearchableAlchemyGridView):
g.processed_by, g.processed_by,
], ],
readonly=True) 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
def configure_fieldset(self, fs):
class LinksFieldRenderer(formalchemy.FieldRenderer): bounce = fs.model
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)
handler = self.get_handler(bounce) handler = self.get_handler(bounce)
fs = self.make_fieldset(bounce)
fs.append(formalchemy.Field('message', fs.append(formalchemy.Field('message',
value=handler.msgpath(bounce), value=handler.msgpath(bounce),
renderer=BounceMessageFieldRenderer.new(self.request, handler))) renderer=BounceMessageFieldRenderer.new(self.request, handler)))
@ -172,11 +108,10 @@ class EmailBounceCrud(CrudView):
if not bounce.processed: if not bounce.processed:
del fs.processed del fs.processed
del fs.processed_by del fs.processed_by
return fs
def template_kwargs(self, form): def template_kwargs_view(self, **kwargs):
kwargs = super(EmailBounceCrud, self).template_kwargs(form) bounce = kwargs['instance']
bounce = form.fieldset.model kwargs['bounce'] = bounce
handler = self.get_handler(bounce) handler = self.get_handler(bounce)
kwargs['handler'] = handler kwargs['handler'] = handler
with open(handler.msgpath(bounce), 'rb') as f: with open(handler.msgpath(bounce), 'rb') as f:
@ -187,33 +122,27 @@ class EmailBounceCrud(CrudView):
""" """
View for marking a bounce as processed. View for marking a bounce as processed.
""" """
bounce = self.get_model_from_request() bounce = self.get_instance()
if not bounce:
return HTTPNotFound()
bounce.processed = datetime.datetime.utcnow() bounce.processed = datetime.datetime.utcnow()
bounce.processed_by = self.request.user bounce.processed_by = self.request.user
self.request.session.flash("Email bounce has been marked processed.") 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): def unprocess(self):
""" """
View for marking a bounce as *unprocessed*. View for marking a bounce as *unprocessed*.
""" """
bounce = self.get_model_from_request() bounce = self.get_instance()
if not bounce:
return HTTPNotFound()
bounce.processed = None bounce.processed = None
bounce.processed_by = None bounce.processed_by = None
self.request.session.flash("Email bounce has been marked UN-processed.") 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): def download(self):
""" """
View for downloading the message file associated with a bounce. View for downloading the message file associated with a bounce.
""" """
bounce = self.get_model_from_request() bounce = self.get_instance()
if not bounce:
return HTTPNotFound()
handler = self.get_handler(bounce) handler = self.get_handler(bounce)
path = handler.msgpath(bounce) path = handler.msgpath(bounce)
response = FileResponse(path, request=self.request) response = FileResponse(path, request=self.request)
@ -221,35 +150,46 @@ class EmailBounceCrud(CrudView):
response.headers[b'Content-Disposition'] = b'attachment; filename="bounce.eml"' response.headers[b'Content-Disposition'] = b'attachment; filename="bounce.eml"'
return response return response
@classmethod
def defaults(cls, config):
def add_routes(config): # mark bounce as processed
config.add_route('emailbounces', '/emailbounces/') config.add_route('emailbounces.process', '/email-bounces/{uuid}/process')
config.add_route('emailbounce', '/emailbounces/{uuid}') config.add_view(cls, attr='process', route_name='emailbounces.process',
config.add_route('emailbounce.process', '/emailbounces/{uuid}/process') permission='emailbounces.process')
config.add_route('emailbounce.unprocess', '/emailbounces/{uuid}/unprocess') config.add_tailbone_permission('emailbounces', 'emailbounces.process',
config.add_route('emailbounce.delete', '/emailbounces/{uuid}/delete') "Mark Email Bounce as processed")
config.add_route('emailbounce.download', '/emailbounces/{uuid}/download')
# 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): def includeme(config):
add_routes(config) EmailBouncesView.defaults(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')

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar # Copyright © 2010-2016 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -24,29 +24,24 @@
Deposit Link Views Deposit Link Views
""" """
from __future__ import unicode_literals from __future__ import unicode_literals, absolute_import
from rattail.db import model 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 def configure_grid(self, g):
config_prefix = 'depositlinks' g.filters['description'].default_active = True
sort = 'code' g.filters['description'].default_verb = 'contains'
g.default_sortkey = '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()
g.configure( g.configure(
include=[ include=[
g.code, g.code,
@ -54,55 +49,15 @@ class DepositLinksGrid(SearchableAlchemyGridView):
g.amount, g.amount,
], ],
readonly=True) 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
def configure_fieldset(self, fs):
class DepositLinkCrud(CrudView):
mapped_class = model.DepositLink
home_route = 'depositlinks'
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.configure( fs.configure(
include=[ include=[
fs.code, fs.code,
fs.description, fs.description,
fs.amount, 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): def includeme(config):
add_routes(config) DepositLinksView.defaults(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')

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar # Copyright © 2010-2016 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -24,28 +24,25 @@
Tax Views Tax Views
""" """
from __future__ import unicode_literals from __future__ import unicode_literals, absolute_import
from rattail.db import model 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 def configure_grid(self, g):
config_prefix = 'taxes' g.filters['description'].default_active = True
sort = 'code' g.filters['description'].default_verb = 'contains'
g.default_sortkey = '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()
g.configure( g.configure(
include=[ include=[
g.code, g.code,
@ -53,55 +50,15 @@ class TaxesGrid(SearchableAlchemyGridView):
g.rate, g.rate,
], ],
readonly=True) 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
def configure_fieldset(self, fs):
class TaxCrud(CrudView):
mapped_class = model.Tax
home_route = 'taxes'
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.configure( fs.configure(
include=[ include=[
fs.code, fs.code,
fs.description, fs.description,
fs.rate, 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): def includeme(config):
add_routes(config) TaxesView.defaults(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')