From 7d19700c3c87a25b7097d125a0cc93e7f1986b6c Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sun, 1 Sep 2013 20:25:34 -0700 Subject: [PATCH] More standalone operation stuff. Stop using `edbob.db.engine`, stop using all edbob templates, etc. --- tailbone/app.py | 11 +- tailbone/auth.py | 6 +- tailbone/static/css/login.css | 34 +++++ tailbone/static/js/login.js | 24 +++ tailbone/static/js/tailbone.js | 7 + tailbone/subscribers.py | 6 +- tailbone/templates/form.mako | 13 ++ tailbone/templates/forms/fieldset.mako | 39 +++++ .../templates/forms/fieldset_readonly.mako | 12 ++ tailbone/templates/forms/form.mako | 15 ++ tailbone/templates/forms/form_readonly.mako | 3 + tailbone/templates/grid.mako | 37 +++++ tailbone/templates/grids/search.mako | 36 +++++ tailbone/templates/login.mako | 37 +++++ tailbone/templates/people/index.mako | 11 ++ tailbone/templates/roles/crud.mako | 18 +++ tailbone/templates/roles/index.mako | 11 ++ tailbone/templates/users/index.mako | 11 ++ tailbone/views/auth.py | 8 +- tailbone/views/batches/rows.py | 13 +- tailbone/views/roles.py | 138 ++++++++++++++---- tailbone/views/users.py | 2 +- 22 files changed, 438 insertions(+), 54 deletions(-) create mode 100644 tailbone/static/css/login.css create mode 100644 tailbone/static/js/login.js create mode 100644 tailbone/templates/form.mako create mode 100644 tailbone/templates/forms/fieldset.mako create mode 100644 tailbone/templates/forms/fieldset_readonly.mako create mode 100644 tailbone/templates/forms/form.mako create mode 100644 tailbone/templates/forms/form_readonly.mako create mode 100644 tailbone/templates/grid.mako create mode 100644 tailbone/templates/grids/search.mako create mode 100644 tailbone/templates/login.mako create mode 100644 tailbone/templates/people/index.mako create mode 100644 tailbone/templates/roles/crud.mako create mode 100644 tailbone/templates/roles/index.mako create mode 100644 tailbone/templates/users/index.mako diff --git a/tailbone/app.py b/tailbone/app.py index 4ac508a4..55f85c7b 100644 --- a/tailbone/app.py +++ b/tailbone/app.py @@ -31,7 +31,7 @@ from pyramid.config import Configurator import os.path import edbob -import edbob.db +from sqlalchemy import engine_from_config from .db import Session from zope.sqlalchemy import ZopeTransactionExtension @@ -55,12 +55,11 @@ def main(global_config, **settings): # Initialize edbob, dammit. edbob.init('rattail', os.path.abspath(settings['edbob.config'])) - edbob.init_modules(['edbob.time', 'edbob.db', 'rattail.db']) + edbob.init_modules(['edbob.time']) - # Configure the primary database session. For now, this leverages edbob's - # initialization to define the engine connection. - assert edbob.db.engine - Session.configure(bind=edbob.db.engine) + # Configure the primary database session. + engine = engine_from_config(settings) + Session.configure(bind=engine) Session.configure(extension=ZopeTransactionExtension()) # Configure user authentication / authorization. diff --git a/tailbone/auth.py b/tailbone/auth.py index 84bc9489..fe7c06d6 100644 --- a/tailbone/auth.py +++ b/tailbone/auth.py @@ -32,7 +32,7 @@ from pyramid.security import Everyone, Authenticated from .db import Session from rattail.db.model import User -from edbob.db.auth import has_permission +from rattail.db.auth import has_permission @implementer(IAuthorizationPolicy) @@ -43,9 +43,9 @@ class TailboneAuthorizationPolicy(object): if userid not in (Everyone, Authenticated): user = Session.query(User).get(userid) assert user - return has_permission(user, permission) + return has_permission(Session(), user, permission) if Everyone in principals: - return has_permission(None, permission, session=Session()) + return has_permission(Session(), None, permission) return False def principals_allowed_by_permission(self, context, permission): diff --git a/tailbone/static/css/login.css b/tailbone/static/css/login.css new file mode 100644 index 00000000..19efe6b2 --- /dev/null +++ b/tailbone/static/css/login.css @@ -0,0 +1,34 @@ + +/****************************** + * login.css + ******************************/ + +#logo { + margin: auto; +} + +div.form { + margin: auto; + float: none; + text-align: center; +} + +div.field-wrapper { + margin: 10px auto; + width: 300px; +} + +div.field-wrapper label { + margin: 0px; + padding-top: 3px; + text-align: right; + width: 100px; +} + +div.field-wrapper input { + width: 150px; +} + +div.buttons input { + margin: auto 5px; +} diff --git a/tailbone/static/js/login.js b/tailbone/static/js/login.js new file mode 100644 index 00000000..ec3bcd8e --- /dev/null +++ b/tailbone/static/js/login.js @@ -0,0 +1,24 @@ + +$(function() { + + $('form').submit(function() { + if (! $('#username').val()) { + with ($('#username').get(0)) { + select(); + focus(); + } + return false; + } + if (! $('#password').val()) { + with ($('#password').get(0)) { + select(); + focus(); + } + return false; + } + return true; + }); + + $('#username').focus(); + +}); diff --git a/tailbone/static/js/tailbone.js b/tailbone/static/js/tailbone.js index 74c7cece..5ac3c049 100644 --- a/tailbone/static/js/tailbone.js +++ b/tailbone/static/js/tailbone.js @@ -105,6 +105,13 @@ $(function() { autoExpand: true }); + /* + * Fix buttons. + */ + $('button').button(); + $('input[type=submit]').button(); + $('input[type=reset]').button(); + /* * When filter labels are clicked, (un)check the associated checkbox. */ diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py index b02800ad..b9f6557f 100644 --- a/tailbone/subscribers.py +++ b/tailbone/subscribers.py @@ -33,7 +33,7 @@ from . import helpers from pyramid.security import authenticated_userid from .db import Session from rattail.db.model import User -from edbob.db.auth import has_permission +from rattail.db.auth import has_permission def before_render(event): @@ -70,12 +70,12 @@ def context_found(event): request.user = Session.query(User).get(uuid) def has_perm(perm): - return has_permission(request.user, perm, session=Session()) + return has_permission(Session(), request.user, perm) request.has_perm = has_perm def has_any_perm(perms): for perm in perms: - if has_permission(request.user, perm, session=Session()): + if has_permission(Session(), request.user, perm): return True return False request.has_any_perm = has_any_perm diff --git a/tailbone/templates/form.mako b/tailbone/templates/form.mako new file mode 100644 index 00000000..095d6384 --- /dev/null +++ b/tailbone/templates/form.mako @@ -0,0 +1,13 @@ +<%inherit file="/base.mako" /> + +<%def name="context_menu_items()"> + +
+ + + + ${form.render()|n} + +
diff --git a/tailbone/templates/forms/fieldset.mako b/tailbone/templates/forms/fieldset.mako new file mode 100644 index 00000000..0e5a6d4a --- /dev/null +++ b/tailbone/templates/forms/fieldset.mako @@ -0,0 +1,39 @@ +<% _focus_rendered = False %> + +% for error in fieldset.errors.get(None, []): +
${error}
+% endfor + +% for field in fieldset.render_fields.itervalues(): + + % if field.requires_label: +
+ % for error in field.errors: +
${error}
+ % endfor + ${field.label_tag()|n} +
+ ${field.render()|n} +
+ % if 'instructions' in field.metadata: + ${field.metadata['instructions']} + % endif +
+ + % if not _focus_rendered and (fieldset.focus == field or fieldset.focus is True): + % if not field.is_readonly() and getattr(field.renderer, 'needs_focus', True): + + <% _focus_rendered = True %> + % endif + % endif + % endif + +% endfor diff --git a/tailbone/templates/forms/fieldset_readonly.mako b/tailbone/templates/forms/fieldset_readonly.mako new file mode 100644 index 00000000..350a3151 --- /dev/null +++ b/tailbone/templates/forms/fieldset_readonly.mako @@ -0,0 +1,12 @@ +
+ % for field in fieldset.render_fields.itervalues(): + % if field.requires_label: +
+ ${field.label_tag()|n} +
+ ${field.render_readonly()} +
+
+ % endif + % endfor +
diff --git a/tailbone/templates/forms/form.mako b/tailbone/templates/forms/form.mako new file mode 100644 index 00000000..ea25d01b --- /dev/null +++ b/tailbone/templates/forms/form.mako @@ -0,0 +1,15 @@ +
+ ${h.form(form.action_url, enctype='multipart/form-data')} + + ${form.fieldset.render()|n} + +
+ ${h.submit('create', form.create_label if form.creating else form.update_label)} + % if form.creating and form.allow_successive_creates: + ${h.submit('create_and_continue', form.successive_create_label)} + % endif + +
+ + ${h.end_form()} +
diff --git a/tailbone/templates/forms/form_readonly.mako b/tailbone/templates/forms/form_readonly.mako new file mode 100644 index 00000000..96920421 --- /dev/null +++ b/tailbone/templates/forms/form_readonly.mako @@ -0,0 +1,3 @@ +
+ ${form.fieldset.render()|n} +
diff --git a/tailbone/templates/grid.mako b/tailbone/templates/grid.mako new file mode 100644 index 00000000..9c78db07 --- /dev/null +++ b/tailbone/templates/grid.mako @@ -0,0 +1,37 @@ +<%inherit file="/base.mako" /> + +<%def name="context_menu_items()"> + +<%def name="form()"> + % if search: + ${search.render()} + % else: +   + % endif + + +<%def name="tools()"> + +
+ + + + + + + + + +
+ ${self.form()} + +
    + ${self.context_menu_items()} +
+
+ ${self.tools()} +
+ + ${grid} + +
diff --git a/tailbone/templates/grids/search.mako b/tailbone/templates/grids/search.mako new file mode 100644 index 00000000..17fbabd2 --- /dev/null +++ b/tailbone/templates/grids/search.mako @@ -0,0 +1,36 @@ +
+ ${search.begin()} + ${search.hidden('filters', 'true')} + <% visible = [] %> + % for f in search.sorted_filters(): + + % if search.config.get('include_filter_'+f.name): + <% visible.append(f.name) %> + % endif + % endfor +
+ ${search.add_filter(visible)} + ${search.submit('submit', "Search", style='display: none;' if not visible else None)} + +
+ ${search.end()} + % if visible: + + % endif +
diff --git a/tailbone/templates/login.mako b/tailbone/templates/login.mako new file mode 100644 index 00000000..b895255a --- /dev/null +++ b/tailbone/templates/login.mako @@ -0,0 +1,37 @@ +<%inherit file="/base.mako" /> + +<%def name="title()">Login + +<%def name="head_tags()"> + ${parent.head_tags()} + ${h.javascript_link(request.static_url('tailbone:static/js/login.js'))} + ${h.stylesheet_link(request.static_url('tailbone:static/css/login.css'))} + + +${h.image(request.static_url('tailbone:static/img/home_logo.png'), "Rattail Logo", id='logo')} + +
+ ${h.form('')} + + + % if error: +
${error}
+ % endif + +
+ + +
+ +
+ + +
+ +
+ ${h.submit('submit', "Login")} + +
+ + ${h.end_form()} +
diff --git a/tailbone/templates/people/index.mako b/tailbone/templates/people/index.mako new file mode 100644 index 00000000..77b7badf --- /dev/null +++ b/tailbone/templates/people/index.mako @@ -0,0 +1,11 @@ +<%inherit file="/grid.mako" /> + +<%def name="title()">People + +<%def name="context_menu_items()"> +## % if request.has_perm('people.create'): +##
  • ${h.link_to("Create a new Person", url('person.new'))}
  • +## % endif + + +${parent.body()} diff --git a/tailbone/templates/roles/crud.mako b/tailbone/templates/roles/crud.mako new file mode 100644 index 00000000..863a773b --- /dev/null +++ b/tailbone/templates/roles/crud.mako @@ -0,0 +1,18 @@ +<%inherit file="edbob.pyramid:templates/crud.mako" /> + +<%def name="head_tags()"> + ${parent.head_tags()} + ${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/perms.css'))} + + +<%def name="context_menu_items()"> +
  • ${h.link_to("Back to Roles", url('roles'))}
  • + % if form.readonly: +
  • ${h.link_to("Edit this Role", url('role.update', uuid=form.fieldset.model.uuid))}
  • + % elif form.updating: +
  • ${h.link_to("View this Role", url('role.read', uuid=form.fieldset.model.uuid))}
  • + % endif +
  • ${h.link_to("Delete this Role", url('role.delete', uuid=form.fieldset.model.uuid), class_='delete')}
  • + + +${parent.body()} diff --git a/tailbone/templates/roles/index.mako b/tailbone/templates/roles/index.mako new file mode 100644 index 00000000..49deacbd --- /dev/null +++ b/tailbone/templates/roles/index.mako @@ -0,0 +1,11 @@ +<%inherit file="/grid.mako" /> + +<%def name="title()">Roles + +<%def name="context_menu_items()"> + % if request.has_perm('roles.create'): +
  • ${h.link_to("Create a new Role", url('role.create'))}
  • + % endif + + +${parent.body()} diff --git a/tailbone/templates/users/index.mako b/tailbone/templates/users/index.mako new file mode 100644 index 00000000..c9f9918d --- /dev/null +++ b/tailbone/templates/users/index.mako @@ -0,0 +1,11 @@ +<%inherit file="/grid.mako" /> + +<%def name="title()">Users + +<%def name="context_menu_items()"> + % if request.has_perm('users.create'): +
  • ${h.link_to("Create a new User", url('user.create'))}
  • + % endif + + +${parent.body()} diff --git a/tailbone/views/auth.py b/tailbone/views/auth.py index d7d1a088..3e5acd1c 100644 --- a/tailbone/views/auth.py +++ b/tailbone/views/auth.py @@ -35,7 +35,7 @@ from ..forms.simpleform import FormRenderer import edbob from ..db import Session -from edbob.db.auth import authenticate_user, set_user_password +from rattail.db.auth import authenticate_user, set_user_password class UserLogin(formencode.Schema): @@ -58,9 +58,9 @@ def login(request): form = Form(request, schema=UserLogin) if form.validate(): - user = authenticate_user(form.data['username'], - form.data['password'], - session=Session()) + user = authenticate_user(Session(), + form.data['username'], + form.data['password']) if user: request.session.flash("%s logged in at %s" % ( user.display_name, diff --git a/tailbone/views/batches/rows.py b/tailbone/views/batches/rows.py index eae3961d..1d5dece0 100644 --- a/tailbone/views/batches/rows.py +++ b/tailbone/views/batches/rows.py @@ -26,12 +26,11 @@ Batch Row Views """ +from .. import SearchableAlchemyGridView, CrudView from pyramid.httpexceptions import HTTPFound from ...db import Session -from .. import SearchableAlchemyGridView, CrudView - -import rattail +from rattail.db.model import Batch, LabelProfile from ...forms import GPCFieldRenderer @@ -41,8 +40,8 @@ def field_with_renderer(field, column): field = field.with_renderer(GPCFieldRenderer) elif column.sil_name == 'F95': # Shelf Tag Type - q = Session.query(rattail.LabelProfile) - q = q.order_by(rattail.LabelProfile.ordinal) + q = Session.query(LabelProfile) + q = q.order_by(LabelProfile.ordinal) field = field.dropdown(options=[(x.description, x.code) for x in q]) return field @@ -50,7 +49,7 @@ def field_with_renderer(field, column): def BatchRowsGrid(request): uuid = request.matchdict['uuid'] - batch = Session.query(rattail.Batch).get(uuid) if uuid else None + batch = Session.query(Batch).get(uuid) if uuid else None if not batch: return HTTPFound(location=request.route_url('batches')) @@ -141,7 +140,7 @@ def batch_rows_delete(request): def batch_row_crud(request, attr): batch_uuid = request.matchdict['batch_uuid'] - batch = Session.query(rattail.Batch).get(batch_uuid) + batch = Session.query(Batch).get(batch_uuid) if not batch: return HTTPFound(location=request.route_url('batches')) diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py index edd6aaa3..ce6520e2 100644 --- a/tailbone/views/roles.py +++ b/tailbone/views/roles.py @@ -26,43 +26,121 @@ Role Views """ +from . import SearchableAlchemyGridView, CrudView from pyramid.httpexceptions import HTTPFound +from ..db import Session +from rattail.db.model import Role +from rattail.db.auth import has_permission, administrator_role, guest_role + import formalchemy from webhelpers.html import tags -from webhelpers.html.builder import HTML - -from edbob.db import auth - -from ..db import Session -from . import SearchableAlchemyGridView, CrudView -from rattail.db.model import Role +from webhelpers.html import HTML default_permissions = [ - + ("Batches", [ + ('batches.list', "List Batches"), + ('batches.read', "View Batches"), + ('batches.create', "Create Batches"), + ('batches.update', "Edit Batches"), + ('batches.delete', "Delete Batches"), + ('batches.execute', "Execute Batches"), + ('batch_rows.read', "View Batch Rows"), + ('batch_rows.update', "Edit Batch Rows"), + ('batch_rows.delete', "Delete Batch Rows"), + ]), + ("Brands", [ + ('brands.list', "List Brands"), + ('brands.read', "View Brands"), + ('brands.create', "Create Brands"), + ('brands.update', "Edit Brands"), + ('brands.delete', "Delete Brands"), + ('brands.force_sync', "Forcibly Sync Brands"), + ]), + ("Customers", [ + ('customers.list', "List Customers"), + ('customers.read', "View Customers"), + ('customers.force_sync', "Forcibly Sync Customers"), + ('customer_groups.list', "List Customer Groups"), + ('customer_groups.read', "View Customer Groups"), + ('customer_groups.force_sync', "Forcibly Sync Customer Groups"), + ]), + ("Departments", [ + ('departments.list', "List Departments"), + ('departments.read', "View Departments"), + ('departments.create', "Create Departments"), + ('departments.update', "Edit Departments"), + ('departments.delete', "Delete Departments"), + ('departments.force_sync', "Forcibly Sync Departments"), + ]), + ("Employees", [ + ('employees.list', "List Employees"), + ('employees.force_sync', "Forcibly Sync Employees"), + ]), + ("Label Profiles", [ + ('label_profiles.list', "List Label Profiles"), + ('label_profiles.view', "View Label Profiles"), + ('label_profiles.create', "Create Label Profiles"), + ('label_profiles.update', "Edit Label Profiles"), + ('label_profiles.delete', "Delete Label Profiles"), + ]), ("People", [ - ('people.list', "List People"), - ('people.read', "View Person"), - ('people.create', "Create Person"), - ('people.update', "Edit Person"), - ('people.delete', "Delete Person"), + ('people.list', "List People"), + ('people.read', "View People"), + ('people.create', "Create People"), + ('people.update', "Edit People"), + ('people.delete', "Delete People"), + ('people.force_sync', "Forcibly Sync People"), + ]), + ("Products", [ + ('products.list', "List Products"), + ('products.read', "View Products"), + ('products.create', "Create Products"), + ('products.update', "Edit Products"), + ('products.delete', "Delete Products"), + ('products.print_labels', "Print Product Labels"), + ('products.force_sync', "Forcibly Sync Products"), ]), - ("Roles", [ - ('roles.list', "List Roles"), - ('roles.read', "View Role"), - ('roles.create', "Create Role"), - ('roles.update', "Edit Role"), - ('roles.delete', "Delete Role"), + ('roles.list', "List Roles"), + ('roles.read', "View Roles"), + ('roles.create', "Create Roles"), + ('roles.update', "Edit Roles"), + ('roles.delete', "Delete Roles"), + ]), + ("Stores", [ + ('stores.list', "List Stores"), + ('stores.read', "View Stores"), + ('stores.create', "Create Stores"), + ('stores.update', "Edit Stores"), + ('stores.delete', "Delete Stores"), + ('stores.force_sync', "Forcibly Sync Stores"), + ]), + ("Subdepartments", [ + ('subdepartments.list', "List Subdepartments"), + ('subdepartments.read', "View Subdepartments"), + ('subdepartments.create', "Create Subdepartments"), + ('subdepartments.update', "Edit Subdepartments"), + ('subdepartments.delete', "Delete Subdepartments"), + ('subdepartments.force_sync', "Forcibly Sync Subdepartments"), ]), - ("Users", [ - ('users.list', "List Users"), - ('users.read', "View User"), - ('users.create', "Create User"), - ('users.update', "Edit User"), - ('users.delete', "Delete User"), + ('users.list', "List Users"), + ('users.read', "View Users"), + ('users.create', "Create Users"), + ('users.update', "Edit Users"), + ('users.delete', "Delete Users"), + ('users.force_sync', "Forcibly Sync Users"), + ]), + ("Vendors", [ + ('vendors.list', "List Vendors"), + ('vendors.read', "View Vendors"), + ('vendors.create', "Create Vendors"), + ('vendors.update', "Edit Vendors"), + ('vendors.delete', "Delete Vendors"), + ('vendors.import_catalog', "Import Vendor Catalogs"), + ('vendors.force_sync', "Forcibly Sync Vendors"), ]), ] @@ -129,7 +207,7 @@ def PermissionsFieldRenderer(permissions, *args, **kwargs): def _render(self, readonly=False, **kwargs): role = self.field.model - admin = auth.administrator_role(Session()) + admin = administrator_role(Session()) if role is admin: html = HTML.tag('p', c="This is the administrative role; " "it has full access to the entire system.") @@ -140,8 +218,8 @@ def PermissionsFieldRenderer(permissions, *args, **kwargs): for group, perms in self.permissions: inner = HTML.tag('p', c=group) for perm, title in perms: - checked = auth.has_permission( - role, perm, include_guest=False, session=Session()) + checked = has_permission( + Session(), role, perm, include_guest=False) if readonly: span = HTML.tag('span', c="[X]" if checked else "[ ]") inner += HTML.tag('p', class_='perm', c=span + ' ' + title) @@ -179,8 +257,8 @@ class RoleCrud(CrudView): return fs def pre_delete(self, model): - admin = auth.administrator_role(Session()) - guest = auth.guest_role(Session()) + admin = administrator_role(Session()) + guest = guest_role(Session()) if model in (admin, guest): self.request.session.flash("You may not delete the %s role." % str(model), 'error') return HTTPFound(location=self.request.get_referrer()) diff --git a/tailbone/views/users.py b/tailbone/views/users.py index 6d537aed..e23dbb01 100644 --- a/tailbone/views/users.py +++ b/tailbone/views/users.py @@ -35,7 +35,7 @@ from . import SearchableAlchemyGridView, CrudView from ..forms import PersonFieldRenderer from ..db import Session from rattail.db.model import User, Person, Role -from edbob.db.auth import guest_role +from rattail.db.auth import guest_role from webhelpers.html import tags from webhelpers.html import HTML