Major overhaul for standalone operation.
This removes some of the `edbob` reliance, as well as borrowing some templates and styling etc. from Dtail.
This commit is contained in:
parent
b9f61e6a47
commit
2a50e704ef
59 changed files with 1969 additions and 39 deletions
|
@ -32,16 +32,39 @@ from .crud import *
|
|||
from .autocomplete import *
|
||||
|
||||
|
||||
def home(request):
|
||||
"""
|
||||
Default home view.
|
||||
"""
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('home', '/')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_forbidden_view('edbob.pyramid.views.forbidden')
|
||||
|
||||
config.add_view(home, route_name='home',
|
||||
renderer='/home.mako')
|
||||
|
||||
config.include('tailbone.views.auth')
|
||||
config.include('tailbone.views.batches')
|
||||
# config.include('tailbone.views.categories')
|
||||
config.include('tailbone.views.brands')
|
||||
config.include('tailbone.views.categories')
|
||||
config.include('tailbone.views.customergroups')
|
||||
config.include('tailbone.views.customers')
|
||||
config.include('tailbone.views.departments')
|
||||
config.include('tailbone.views.employees')
|
||||
config.include('tailbone.views.labels')
|
||||
config.include('tailbone.views.people')
|
||||
config.include('tailbone.views.products')
|
||||
config.include('tailbone.views.roles')
|
||||
config.include('tailbone.views.stores')
|
||||
config.include('tailbone.views.subdepartments')
|
||||
config.include('tailbone.views.users')
|
||||
config.include('tailbone.views.vendors')
|
||||
|
|
152
tailbone/views/auth.py
Normal file
152
tailbone/views/auth.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Auth Views
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from pyramid.security import remember, forget
|
||||
|
||||
import formencode
|
||||
from pyramid_simpleform import Form
|
||||
from ..forms.simpleform import FormRenderer
|
||||
|
||||
import edbob
|
||||
from ..db import Session
|
||||
from edbob.db.auth import authenticate_user, set_user_password
|
||||
|
||||
|
||||
class UserLogin(formencode.Schema):
|
||||
allow_extra_fields = True
|
||||
filter_extra_fields = True
|
||||
username = formencode.validators.NotEmpty()
|
||||
password = formencode.validators.NotEmpty()
|
||||
|
||||
|
||||
def login(request):
|
||||
"""
|
||||
The login view, responsible for displaying and handling the login form.
|
||||
"""
|
||||
|
||||
referrer = request.get_referrer()
|
||||
|
||||
# Redirect if already logged in.
|
||||
if request.user:
|
||||
return HTTPFound(location=referrer)
|
||||
|
||||
form = Form(request, schema=UserLogin)
|
||||
if form.validate():
|
||||
user = authenticate_user(form.data['username'],
|
||||
form.data['password'],
|
||||
session=Session())
|
||||
if user:
|
||||
request.session.flash("%s logged in at %s" % (
|
||||
user.display_name,
|
||||
edbob.local_time().strftime('%I:%M %p')))
|
||||
headers = remember(request, user.uuid)
|
||||
return HTTPFound(location=referrer, headers=headers)
|
||||
request.session.flash("Invalid username or password")
|
||||
|
||||
url = edbob.config.get('edbob.pyramid', 'login.logo_url',
|
||||
default=request.static_url('edbob.pyramid:static/img/logo.jpg'))
|
||||
kwargs = eval(edbob.config.get('edbob.pyramid', 'login.logo_kwargs',
|
||||
default="dict(width=500)"))
|
||||
|
||||
return {'form': FormRenderer(form), 'referrer': referrer,
|
||||
'logo_url': url, 'logo_kwargs': kwargs}
|
||||
|
||||
|
||||
def logout(request):
|
||||
"""
|
||||
View responsible for logging out the current user.
|
||||
|
||||
This deletes/invalidates the current session and then redirects to the
|
||||
login page.
|
||||
"""
|
||||
|
||||
request.session.delete()
|
||||
request.session.invalidate()
|
||||
headers = forget(request)
|
||||
referrer = request.get_referrer()
|
||||
return HTTPFound(location=referrer, headers=headers)
|
||||
|
||||
|
||||
class CurrentPasswordCorrect(formencode.validators.FancyValidator):
|
||||
|
||||
def _to_python(self, value, state):
|
||||
user = state
|
||||
if not authenticate_user(user.username, value, session=Session()):
|
||||
raise formencode.Invalid("The password is incorrect.", value, state)
|
||||
return value
|
||||
|
||||
|
||||
class ChangePassword(formencode.Schema):
|
||||
|
||||
allow_extra_fields = True
|
||||
filter_extra_fields = True
|
||||
|
||||
current_password = formencode.All(
|
||||
formencode.validators.NotEmpty(),
|
||||
CurrentPasswordCorrect())
|
||||
|
||||
new_password = formencode.validators.NotEmpty()
|
||||
confirm_password = formencode.validators.NotEmpty()
|
||||
|
||||
chained_validators = [formencode.validators.FieldsMatch(
|
||||
'new_password', 'confirm_password')]
|
||||
|
||||
|
||||
def change_password(request):
|
||||
"""
|
||||
Allows a user to change his or her password.
|
||||
"""
|
||||
|
||||
if not request.user:
|
||||
return HTTPFound(location=request.route_url('home'))
|
||||
|
||||
form = Form(request, schema=ChangePassword, state=request.user)
|
||||
if form.validate():
|
||||
set_user_password(request.user, form.data['new_password'])
|
||||
return HTTPFound(location=request.get_referrer())
|
||||
|
||||
return {'form': FormRenderer(form)}
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('login', '/login')
|
||||
config.add_route('logout', '/logout')
|
||||
config.add_route('change_password', '/change-password')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(login, route_name='login',
|
||||
renderer='/login.mako')
|
||||
|
||||
config.add_view(logout, route_name='logout')
|
||||
|
||||
config.add_view(change_password, route_name='change_password',
|
||||
renderer='/change_password.mako')
|
|
@ -27,7 +27,7 @@ Autocomplete View
|
|||
"""
|
||||
|
||||
from .core import View
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
|
||||
|
||||
__all__ = ['AutocompleteView']
|
||||
|
|
|
@ -39,7 +39,7 @@ from .. import SearchableAlchemyGridView, CrudView, View
|
|||
|
||||
import rattail
|
||||
from rattail import batches
|
||||
from ... import Session
|
||||
from ...db import Session
|
||||
from rattail.db.model import Batch
|
||||
from rattail.threads import Thread
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
Print Labels Batch
|
||||
"""
|
||||
|
||||
from .... import Session
|
||||
from ....db import Session
|
||||
|
||||
import rattail
|
||||
from . import BatchParamsView
|
||||
|
|
|
@ -28,7 +28,7 @@ Batch Row Views
|
|||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from ... import Session
|
||||
from ...db import Session
|
||||
from .. import SearchableAlchemyGridView, CrudView
|
||||
|
||||
import rattail
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
Core View
|
||||
"""
|
||||
|
||||
|
||||
__all__ = ['View']
|
||||
|
||||
|
||||
class View(object):
|
||||
"""
|
||||
Base for all class-based views.
|
||||
|
|
|
@ -30,10 +30,10 @@ from pyramid.httpexceptions import HTTPFound
|
|||
|
||||
import formalchemy
|
||||
|
||||
from .. import Session
|
||||
from edbob.pyramid.forms.formalchemy import AlchemyForm
|
||||
from .core import View
|
||||
from edbob.util import requires_impl, prettify
|
||||
from ..db import Session
|
||||
|
||||
|
||||
__all__ = ['CrudView']
|
||||
|
|
|
@ -28,7 +28,7 @@ CustomerGroup Views
|
|||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from rattail.db.model import CustomerGroup, CustomerGroupAssignment
|
||||
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ from . import SearchableAlchemyGridView
|
|||
from ..forms import EnumFieldRenderer
|
||||
|
||||
import rattail
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from rattail.db.model import (
|
||||
Customer, CustomerPerson, CustomerGroupAssignment,
|
||||
CustomerEmailAddress, CustomerPhoneNumber)
|
||||
|
|
|
@ -30,7 +30,7 @@ from webhelpers import paginate
|
|||
|
||||
from .core import GridView
|
||||
from ... import grids
|
||||
from ... import Session
|
||||
from ...db import Session
|
||||
|
||||
|
||||
__all__ = ['AlchemyGridView', 'SortableAlchemyGridView',
|
||||
|
|
|
@ -32,7 +32,7 @@ import formalchemy
|
|||
|
||||
from webhelpers.html import HTML
|
||||
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from ..grids.search import BooleanSearchFilter
|
||||
from edbob.pyramid.forms import StrippingFieldRenderer
|
||||
|
|
|
@ -30,7 +30,7 @@ from sqlalchemy import and_
|
|||
|
||||
from . import SearchableAlchemyGridView, CrudView, AutocompleteView
|
||||
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from rattail.db.model import (Person, PersonEmailAddress, PersonPhoneNumber,
|
||||
VendorContact)
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ from rattail.db.model import (
|
|||
Brand, Vendor, Department, Subdepartment, LabelProfile)
|
||||
from rattail.gpc import GPC
|
||||
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from ..forms import AutocompleteFieldRenderer, GPCFieldRenderer, PriceFieldRenderer
|
||||
from . import CrudView
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ from .core import View
|
|||
from mako.template import Template
|
||||
from pyramid.response import Response
|
||||
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from rattail.db.model import Vendor, Department, Product, ProductCost
|
||||
|
||||
import re
|
||||
|
|
|
@ -34,7 +34,7 @@ from webhelpers.html.builder import HTML
|
|||
|
||||
from edbob.db import auth
|
||||
|
||||
from .. import Session
|
||||
from ..db import Session
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from rattail.db.model import Role
|
||||
|
||||
|
|
|
@ -26,13 +26,19 @@
|
|||
User Views
|
||||
"""
|
||||
|
||||
import formalchemy
|
||||
from formalchemy import Field
|
||||
from formalchemy.fields import SelectFieldRenderer
|
||||
|
||||
from edbob.pyramid.views import users
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from ..forms import PersonFieldRenderer
|
||||
from rattail.db.model import User, Person
|
||||
from ..db import Session
|
||||
from rattail.db.model import User, Person, Role
|
||||
from edbob.db.auth import guest_role
|
||||
|
||||
from webhelpers.html import tags
|
||||
from webhelpers.html import HTML
|
||||
|
||||
|
||||
class UsersGrid(SearchableAlchemyGridView):
|
||||
|
@ -84,6 +90,49 @@ class UsersGrid(SearchableAlchemyGridView):
|
|||
return g
|
||||
|
||||
|
||||
class RolesField(Field):
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
kwargs.setdefault('value', self.get_value)
|
||||
kwargs.setdefault('options', self.get_options())
|
||||
kwargs.setdefault('multiple', True)
|
||||
super(RolesField, self).__init__(name, **kwargs)
|
||||
|
||||
def get_value(self, user):
|
||||
return [x.uuid for x in user.roles]
|
||||
|
||||
def get_options(self):
|
||||
return Session.query(Role.name, Role.uuid)\
|
||||
.filter(Role.uuid != guest_role(Session()).uuid)\
|
||||
.order_by(Role.name)\
|
||||
.all()
|
||||
|
||||
def sync(self):
|
||||
if not self.is_readonly():
|
||||
user = self.model
|
||||
roles = Session.query(Role)
|
||||
data = self.renderer.deserialize()
|
||||
user.roles = [roles.get(x) for x in data]
|
||||
|
||||
|
||||
def RolesFieldRenderer(request):
|
||||
|
||||
class RolesFieldRenderer(SelectFieldRenderer):
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
roles = Session.query(Role)
|
||||
html = ''
|
||||
for uuid in self.value:
|
||||
role = roles.get(uuid)
|
||||
link = tags.link_to(
|
||||
role.name, request.route_url('role.read', uuid=role.uuid))
|
||||
html += HTML.tag('li', c=link)
|
||||
html = HTML.tag('ul', c=html)
|
||||
return html
|
||||
|
||||
return RolesFieldRenderer
|
||||
|
||||
|
||||
class UserCrud(CrudView):
|
||||
|
||||
mapped_class = User
|
||||
|
@ -98,10 +147,10 @@ class UserCrud(CrudView):
|
|||
self.request.route_url('people.autocomplete')))
|
||||
|
||||
fs.append(users.PasswordField('password'))
|
||||
fs.append(formalchemy.Field(
|
||||
'confirm_password', renderer=users.PasswordFieldRenderer))
|
||||
fs.append(users.RolesField(
|
||||
'roles', renderer=users.RolesFieldRenderer(self.request)))
|
||||
fs.append(Field('confirm_password',
|
||||
renderer=users.PasswordFieldRenderer))
|
||||
fs.append(RolesField(
|
||||
'roles', renderer=RolesFieldRenderer(self.request)))
|
||||
|
||||
fs.configure(
|
||||
include=[
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue