Add basic "mobile index" master view, plus support for demo mode
This commit is contained in:
parent
9808bb3a91
commit
581a21bd9d
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8; -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2015 Lance Edgar
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,6 +24,9 @@
|
||||||
Grids and Friends
|
Grids and Friends
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
from . import filters
|
from . import filters
|
||||||
from .core import Grid, GridColumn, GridAction
|
from .core import Grid, GridColumn, GridAction
|
||||||
from .alchemy import AlchemyGrid
|
from .alchemy import AlchemyGrid
|
||||||
|
from .mobile import MobileGrid
|
||||||
|
|
71
tailbone/newgrids/mobile.py
Normal file
71
tailbone/newgrids/mobile.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Mobile Grids
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from webhelpers.html import tags
|
||||||
|
|
||||||
|
|
||||||
|
class Grid(object):
|
||||||
|
"""
|
||||||
|
Base class for all grids
|
||||||
|
"""
|
||||||
|
configured = False
|
||||||
|
|
||||||
|
def __init__(self, key, data=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Grid constructor
|
||||||
|
"""
|
||||||
|
self.key = key
|
||||||
|
self.data = data
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""
|
||||||
|
This grid supports iteration, over its data
|
||||||
|
"""
|
||||||
|
return iter(self.data)
|
||||||
|
|
||||||
|
def configure(self, include=None):
|
||||||
|
"""
|
||||||
|
Configure the grid. This must define which columns to display and in
|
||||||
|
which order, etc.
|
||||||
|
"""
|
||||||
|
self.configured = True
|
||||||
|
|
||||||
|
def view_url(self, obj, mobile=False):
|
||||||
|
route = '{}{}.view'.format('mobile.' if mobile else '', self.route_prefix)
|
||||||
|
return self.request.route_url(route, uuid=obj.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
class MobileGrid(Grid):
|
||||||
|
"""
|
||||||
|
Base class for all mobile grids
|
||||||
|
"""
|
||||||
|
|
||||||
|
def render_object(self, obj):
|
||||||
|
return tags.link_to(obj, self.view_url(obj, mobile=True))
|
|
@ -32,3 +32,9 @@ div.field-wrapper div.field input[type="password"] {
|
||||||
div.buttons input {
|
div.buttons input {
|
||||||
margin: auto 5px;
|
margin: auto 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* this is for "login as chuck" tip in demo mode */
|
||||||
|
.tips {
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
@ -38,3 +38,9 @@
|
||||||
${self.logo()}
|
${self.logo()}
|
||||||
|
|
||||||
${self.login_form()}
|
${self.login_form()}
|
||||||
|
|
||||||
|
% if request.rattail_config.demo():
|
||||||
|
<p class="tips">
|
||||||
|
Login with <strong>chuck / admin</strong> for full demo access
|
||||||
|
</p>
|
||||||
|
% endif
|
||||||
|
|
16
tailbone/templates/mobile/master/index.mako
Normal file
16
tailbone/templates/mobile/master/index.mako
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
## ##############################################################################
|
||||||
|
##
|
||||||
|
## Default master 'index' template for mobile. Features a somewhat abbreviated
|
||||||
|
## data table and (hopefully) exposes a way to filter and sort the data, etc.
|
||||||
|
##
|
||||||
|
## ##############################################################################
|
||||||
|
<%inherit file="/mobile/base.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">${model_title_plural}</%def>
|
||||||
|
|
||||||
|
<ul data-role="listview">
|
||||||
|
% for obj in grid:
|
||||||
|
<li>${grid.render_object(obj)}</li>
|
||||||
|
% endfor
|
||||||
|
</ul>
|
12
tailbone/templates/mobile/master/view.mako
Normal file
12
tailbone/templates/mobile/master/view.mako
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
## ##############################################################################
|
||||||
|
##
|
||||||
|
## Default master 'view' template for mobile. Features a basic field list, and
|
||||||
|
## links to edit/delete the object when appropriate.
|
||||||
|
##
|
||||||
|
## ##############################################################################
|
||||||
|
<%inherit file="/mobile/base.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">${model_title}: ${instance_title}</%def>
|
||||||
|
|
||||||
|
<p>TODO: display fieldset for object</p>
|
|
@ -162,6 +162,10 @@ class AuthenticationView(View):
|
||||||
if not self.request.user:
|
if not self.request.user:
|
||||||
return self.redirect(self.request.route_url('home'))
|
return self.redirect(self.request.route_url('home'))
|
||||||
|
|
||||||
|
if self.rattail_config.demo() and self.request.user.username == 'chuck':
|
||||||
|
self.request.session.flash("Cannot change password for 'chuck' in demo mode", 'error')
|
||||||
|
return self.redirect(self.request.get_referrer())
|
||||||
|
|
||||||
form = Form(self.request, schema=ChangePassword, state=self.request.user)
|
form = Form(self.request, schema=ChangePassword, state=self.request.user)
|
||||||
if form.validate():
|
if form.validate():
|
||||||
set_user_password(self.request.user, form.data['new_password'])
|
set_user_password(self.request.user, form.data['new_password'])
|
||||||
|
|
|
@ -401,7 +401,7 @@ class BatchMasterView(MasterView):
|
||||||
del batch.data_rows[:]
|
del batch.data_rows[:]
|
||||||
super(BatchMasterView, self).delete_instance(batch)
|
super(BatchMasterView, self).delete_instance(batch)
|
||||||
|
|
||||||
def get_fallback_templates(self, template):
|
def get_fallback_templates(self, template, mobile=False):
|
||||||
return [
|
return [
|
||||||
'/newbatch/{}.mako'.format(template),
|
'/newbatch/{}.mako'.format(template),
|
||||||
'/master/{}.mako'.format(template),
|
'/master/{}.mako'.format(template),
|
||||||
|
|
|
@ -45,6 +45,7 @@ class CustomersView(MasterView):
|
||||||
Master view for the Customer class.
|
Master view for the Customer class.
|
||||||
"""
|
"""
|
||||||
model_class = model.Customer
|
model_class = model.Customer
|
||||||
|
supports_mobile = True
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
|
|
||||||
|
@ -81,6 +82,10 @@ class CustomersView(MasterView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
|
|
||||||
|
def get_mobile_data(self, session=None):
|
||||||
|
# TODO: hacky!
|
||||||
|
return self.get_data(session=session).order_by(model.Customer.name)
|
||||||
|
|
||||||
def get_instance(self):
|
def get_instance(self):
|
||||||
try:
|
try:
|
||||||
instance = super(CustomersView, self).get_instance()
|
instance = super(CustomersView, self).get_instance()
|
||||||
|
|
|
@ -140,6 +140,16 @@ class ProfilesView(MasterView):
|
||||||
def get_instance_title(self, email):
|
def get_instance_title(self, email):
|
||||||
return email['_email'].get_complete_subject(render=False)
|
return email['_email'].get_complete_subject(render=False)
|
||||||
|
|
||||||
|
def editable_instance(self, profile):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return profile['key'] != 'user_feedback'
|
||||||
|
return True
|
||||||
|
|
||||||
|
def deletable_instance(self, profile):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return profile['key'] != 'user_feedback'
|
||||||
|
return True
|
||||||
|
|
||||||
def make_form(self, email, **kwargs):
|
def make_form(self, email, **kwargs):
|
||||||
"""
|
"""
|
||||||
Make a simple form for use with CRUD views.
|
Make a simple form for use with CRUD views.
|
||||||
|
|
|
@ -111,6 +111,16 @@ class EmployeesView(MasterView):
|
||||||
q = q.filter(model.Employee.status == self.enum.EMPLOYEE_STATUS_CURRENT)
|
q = q.filter(model.Employee.status == self.enum.EMPLOYEE_STATUS_CURRENT)
|
||||||
return q
|
return q
|
||||||
|
|
||||||
|
def editable_instance(self, employee):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return not bool(employee.user and employee.username == 'chuck')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def deletable_instance(self, employee):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return not bool(employee.user and employee.username == 'chuck')
|
||||||
|
return True
|
||||||
|
|
||||||
def _preconfigure_fieldset(self, fs):
|
def _preconfigure_fieldset(self, fs):
|
||||||
fs.append(forms.AssociationProxyField('first_name'))
|
fs.append(forms.AssociationProxyField('first_name'))
|
||||||
fs.append(forms.AssociationProxyField('last_name'))
|
fs.append(forms.AssociationProxyField('last_name'))
|
||||||
|
|
|
@ -38,7 +38,7 @@ from webhelpers.html import HTML, tags
|
||||||
|
|
||||||
from tailbone import forms, newgrids as grids
|
from tailbone import forms, newgrids as grids
|
||||||
from tailbone.views import View
|
from tailbone.views import View
|
||||||
from tailbone.newgrids import filters, AlchemyGrid, GridAction
|
from tailbone.newgrids import filters, AlchemyGrid, GridAction, MobileGrid
|
||||||
|
|
||||||
|
|
||||||
class MasterView(View):
|
class MasterView(View):
|
||||||
|
@ -57,6 +57,8 @@ class MasterView(View):
|
||||||
bulk_deletable = False
|
bulk_deletable = False
|
||||||
mergeable = False
|
mergeable = False
|
||||||
|
|
||||||
|
supports_mobile = False
|
||||||
|
|
||||||
listing = False
|
listing = False
|
||||||
creating = False
|
creating = False
|
||||||
viewing = False
|
viewing = False
|
||||||
|
@ -122,6 +124,83 @@ class MasterView(View):
|
||||||
|
|
||||||
return self.render_to_response('index', {'grid': grid})
|
return self.render_to_response('index', {'grid': grid})
|
||||||
|
|
||||||
|
def mobile_index(self):
|
||||||
|
"""
|
||||||
|
Mobile "home" page for the data model
|
||||||
|
"""
|
||||||
|
self.listing = True
|
||||||
|
grid = self.make_mobile_grid()
|
||||||
|
return self.render_to_response('index', {'grid': grid}, mobile=True)
|
||||||
|
|
||||||
|
def make_mobile_grid(self, **kwargs):
|
||||||
|
factory = self.get_mobile_grid_factory()
|
||||||
|
key = self.get_mobile_grid_key()
|
||||||
|
data = self.get_mobile_data(session=kwargs.get('session'))
|
||||||
|
kwargs = self.make_mobile_grid_kwargs(**kwargs)
|
||||||
|
kwargs.setdefault('key', key)
|
||||||
|
kwargs.setdefault('request', self.request)
|
||||||
|
kwargs.setdefault('data', data)
|
||||||
|
kwargs.setdefault('model_class', self.get_model_class(error=False))
|
||||||
|
grid = factory(**kwargs)
|
||||||
|
self.preconfigure_mobile_grid(grid)
|
||||||
|
self.configure_mobile_grid(grid)
|
||||||
|
return grid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_mobile_grid_factory(cls):
|
||||||
|
"""
|
||||||
|
Must return a callable to be used when creating new mobile grid
|
||||||
|
instances. Instead of overriding this, you can set
|
||||||
|
:attr:`mobile_grid_factory`. Default factory is :class:`MobileGrid`.
|
||||||
|
"""
|
||||||
|
return getattr(cls, 'mobile_grid_factory', MobileGrid)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_mobile_grid_key(cls):
|
||||||
|
"""
|
||||||
|
Must return a unique "config key" for the mobile grid, for sort/filter
|
||||||
|
purposes etc. (It need only be unique among *mobile* grids.) Instead
|
||||||
|
of overriding this, you can set :attr:`mobile_grid_key`. Default is
|
||||||
|
the value returned by :meth:`get_route_prefix()`.
|
||||||
|
"""
|
||||||
|
if hasattr(cls, 'mobile_grid_key'):
|
||||||
|
return cls.mobile_grid_key
|
||||||
|
return cls.get_route_prefix()
|
||||||
|
|
||||||
|
def get_mobile_data(self, session=None):
|
||||||
|
"""
|
||||||
|
Must return the "raw" / full data set for the mobile grid. This data
|
||||||
|
should *not* yet be sorted or filtered in any way; that happens later.
|
||||||
|
Default is the value returned by :meth:`get_data()`, in which case all
|
||||||
|
records visible in the traditional view, are visible in mobile too.
|
||||||
|
"""
|
||||||
|
return self.get_data(session=session)
|
||||||
|
|
||||||
|
def make_mobile_grid_kwargs(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Must return a dictionary of kwargs to be passed to the factory when
|
||||||
|
creating new mobile grid instances.
|
||||||
|
"""
|
||||||
|
defaults = {
|
||||||
|
'route_prefix': self.get_route_prefix(),
|
||||||
|
}
|
||||||
|
defaults.update(kwargs)
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
def preconfigure_mobile_grid(self, grid):
|
||||||
|
"""
|
||||||
|
Optionally perform pre-configuration for the mobile grid, to establish
|
||||||
|
some sane defaults etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def configure_mobile_grid(self, grid):
|
||||||
|
"""
|
||||||
|
Configure the mobile grid. The primary objective here is to define
|
||||||
|
which columns to show and in which order etc. Along the way you're
|
||||||
|
free to customize any column(s) you like, as needed.
|
||||||
|
"""
|
||||||
|
grid.configure()
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
"""
|
"""
|
||||||
View for creating a new model record.
|
View for creating a new model record.
|
||||||
|
@ -185,6 +264,23 @@ class MasterView(View):
|
||||||
tools=self.make_row_grid_tools(instance))
|
tools=self.make_row_grid_tools(instance))
|
||||||
return self.render_to_response('view', context)
|
return self.render_to_response('view', context)
|
||||||
|
|
||||||
|
def mobile_view(self):
|
||||||
|
"""
|
||||||
|
Mobile view for displaying a single object's details
|
||||||
|
"""
|
||||||
|
self.viewing = True
|
||||||
|
instance = self.get_instance()
|
||||||
|
# form = self.make_form(instance)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'instance': instance,
|
||||||
|
'instance_title': self.get_instance_title(instance),
|
||||||
|
# 'instance_editable': self.editable_instance(instance),
|
||||||
|
# 'instance_deletable': self.deletable_instance(instance),
|
||||||
|
# 'form': form,
|
||||||
|
}
|
||||||
|
return self.render_to_response('view', context, mobile=True)
|
||||||
|
|
||||||
def make_default_row_grid_tools(self, obj):
|
def make_default_row_grid_tools(self, obj):
|
||||||
if self.rows_creatable:
|
if self.rows_creatable:
|
||||||
link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
|
link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
|
||||||
|
@ -614,7 +710,7 @@ class MasterView(View):
|
||||||
return self.request.route_url('{0}.{1}'.format(self.get_route_prefix(), action),
|
return self.request.route_url('{0}.{1}'.format(self.get_route_prefix(), action),
|
||||||
**self.get_action_route_kwargs(instance))
|
**self.get_action_route_kwargs(instance))
|
||||||
|
|
||||||
def render_to_response(self, template, data):
|
def render_to_response(self, template, data, mobile=False):
|
||||||
"""
|
"""
|
||||||
Return a response with the given template rendered with the given data.
|
Return a response with the given template rendered with the given data.
|
||||||
Note that ``template`` must only be a "key" (e.g. 'index' or 'view').
|
Note that ``template`` must only be a "key" (e.g. 'index' or 'view').
|
||||||
|
@ -650,14 +746,17 @@ class MasterView(View):
|
||||||
context.update(getattr(self, 'template_kwargs_{}'.format(template))(**context))
|
context.update(getattr(self, 'template_kwargs_{}'.format(template))(**context))
|
||||||
|
|
||||||
# First try the template path most specific to the view.
|
# First try the template path most specific to the view.
|
||||||
|
if mobile:
|
||||||
|
mako_path = '/mobile{}/{}.mako'.format(self.get_template_prefix(), template)
|
||||||
|
else:
|
||||||
|
mako_path = '{}/{}.mako'.format(self.get_template_prefix(), template)
|
||||||
try:
|
try:
|
||||||
return render_to_response('{}/{}.mako'.format(self.get_template_prefix(), template),
|
return render_to_response(mako_path, context, request=self.request)
|
||||||
context, request=self.request)
|
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|
||||||
# Failing that, try one or more fallback templates.
|
# Failing that, try one or more fallback templates.
|
||||||
for fallback in self.get_fallback_templates(template):
|
for fallback in self.get_fallback_templates(template, mobile=mobile):
|
||||||
try:
|
try:
|
||||||
return render_to_response(fallback, context, request=self.request)
|
return render_to_response(fallback, context, request=self.request)
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -704,7 +803,9 @@ class MasterView(View):
|
||||||
return render('{}/{}.mako'.format(self.get_template_prefix(), template),
|
return render('{}/{}.mako'.format(self.get_template_prefix(), template),
|
||||||
context, request=self.request)
|
context, request=self.request)
|
||||||
|
|
||||||
def get_fallback_templates(self, template):
|
def get_fallback_templates(self, template, mobile=False):
|
||||||
|
if mobile:
|
||||||
|
return ['/mobile/master/{}.mako'.format(template)]
|
||||||
return ['/master/{}.mako'.format(template)]
|
return ['/master/{}.mako'.format(template)]
|
||||||
|
|
||||||
def template_kwargs(self, **kwargs):
|
def template_kwargs(self, **kwargs):
|
||||||
|
@ -1297,11 +1398,15 @@ class MasterView(View):
|
||||||
|
|
||||||
# list/search
|
# list/search
|
||||||
if cls.listable:
|
if cls.listable:
|
||||||
|
config.add_tailbone_permission(permission_prefix, '{}.list'.format(permission_prefix),
|
||||||
|
"List / search {}".format(model_title_plural))
|
||||||
config.add_route(route_prefix, '{}/'.format(url_prefix))
|
config.add_route(route_prefix, '{}/'.format(url_prefix))
|
||||||
config.add_view(cls, attr='index', route_name=route_prefix,
|
config.add_view(cls, attr='index', route_name=route_prefix,
|
||||||
permission='{}.list'.format(permission_prefix))
|
permission='{}.list'.format(permission_prefix))
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.list'.format(permission_prefix),
|
if cls.supports_mobile:
|
||||||
"List / search {}".format(model_title_plural))
|
config.add_route('mobile.{}'.format(route_prefix), '/mobile{}/'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='mobile_index', route_name='mobile.{}'.format(route_prefix),
|
||||||
|
permission='{}.list'.format(permission_prefix))
|
||||||
|
|
||||||
# create
|
# create
|
||||||
if cls.creatable:
|
if cls.creatable:
|
||||||
|
@ -1344,6 +1449,10 @@ class MasterView(View):
|
||||||
config.add_route('{}.view'.format(route_prefix), '{}/{{{}}}'.format(url_prefix, model_key))
|
config.add_route('{}.view'.format(route_prefix), '{}/{{{}}}'.format(url_prefix, model_key))
|
||||||
config.add_view(cls, attr='view', route_name='{}.view'.format(route_prefix),
|
config.add_view(cls, attr='view', route_name='{}.view'.format(route_prefix),
|
||||||
permission='{}.view'.format(permission_prefix))
|
permission='{}.view'.format(permission_prefix))
|
||||||
|
if cls.supports_mobile:
|
||||||
|
config.add_route('mobile.{}.view'.format(route_prefix), '/mobile{}/{{{}}}'.format(url_prefix, model_key))
|
||||||
|
config.add_view(cls, attr='mobile_view', route_name='mobile.{}.view'.format(route_prefix),
|
||||||
|
permission='{}.view'.format(permission_prefix))
|
||||||
|
|
||||||
# edit
|
# edit
|
||||||
if cls.editable:
|
if cls.editable:
|
||||||
|
|
|
@ -107,6 +107,16 @@ class PeopleView(MasterView):
|
||||||
return instance.person
|
return instance.person
|
||||||
raise HTTPNotFound
|
raise HTTPNotFound
|
||||||
|
|
||||||
|
def editable_instance(self, person):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return not bool(person.user and person.user.username == 'chuck')
|
||||||
|
return True
|
||||||
|
|
||||||
|
def deletable_instance(self, person):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return not bool(person.user and person.user.username == 'chuck')
|
||||||
|
return True
|
||||||
|
|
||||||
def _preconfigure_fieldset(self, fs):
|
def _preconfigure_fieldset(self, fs):
|
||||||
fs.display_name.set(label="Full Name")
|
fs.display_name.set(label="Full Name")
|
||||||
fs.phone.set(label="Phone Number", readonly=True)
|
fs.phone.set(label="Phone Number", readonly=True)
|
||||||
|
|
|
@ -38,10 +38,10 @@ class PrincipalMasterView(MasterView):
|
||||||
Master view base class for security principal models, i.e. User and Role.
|
Master view base class for security principal models, i.e. User and Role.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_fallback_templates(self, template):
|
def get_fallback_templates(self, template, mobile=False):
|
||||||
return [
|
return [
|
||||||
'/principal/{}.mako'.format(template),
|
'/principal/{}.mako'.format(template),
|
||||||
] + super(PrincipalMasterView, self).get_fallback_templates(template)
|
] + super(PrincipalMasterView, self).get_fallback_templates(template, mobile=mobile)
|
||||||
|
|
||||||
def find_by_perm(self):
|
def find_by_perm(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8; -*-
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2015 Lance Edgar
|
# Copyright © 2010-2017 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -24,7 +24,9 @@
|
||||||
Settings Views
|
Settings Views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
|
||||||
|
@ -36,6 +38,7 @@ class SettingsView(MasterView):
|
||||||
Master view for the settings model.
|
Master view for the settings model.
|
||||||
"""
|
"""
|
||||||
model_class = model.Setting
|
model_class = model.Setting
|
||||||
|
feedback = re.compile(r'^rattail\.mail\.user_feedback\..*')
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
g.filters['name'].default_active = True
|
g.filters['name'].default_active = True
|
||||||
|
@ -57,6 +60,16 @@ class SettingsView(MasterView):
|
||||||
if self.editing:
|
if self.editing:
|
||||||
fs.name.set(readonly=True)
|
fs.name.set(readonly=True)
|
||||||
|
|
||||||
|
def editable_instance(self, setting):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return not bool(self.feedback.match(setting.name))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def deletable_instance(self, setting):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return not bool(self.feedback.match(setting.name))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
SettingsView.defaults(config)
|
SettingsView.defaults(config)
|
||||||
|
|
|
@ -201,6 +201,16 @@ class UsersView(PrincipalMasterView):
|
||||||
del fs.password
|
del fs.password
|
||||||
del fs.confirm_password
|
del fs.confirm_password
|
||||||
|
|
||||||
|
def editable_instance(self, user):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return user.username != 'chuck'
|
||||||
|
return True
|
||||||
|
|
||||||
|
def deletable_instance(self, user):
|
||||||
|
if self.rattail_config.demo():
|
||||||
|
return user.username != 'chuck'
|
||||||
|
return True
|
||||||
|
|
||||||
def find_principals_with_permission(self, session, permission):
|
def find_principals_with_permission(self, session, permission):
|
||||||
# TODO: this should search Permission table instead, and work backward to User?
|
# TODO: this should search Permission table instead, and work backward to User?
|
||||||
all_users = session.query(model.User)\
|
all_users = session.query(model.User)\
|
||||||
|
|
Loading…
Reference in a new issue