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:
Lance Edgar 2013-09-01 15:31:50 -07:00
parent b9f61e6a47
commit 2a50e704ef
59 changed files with 1969 additions and 39 deletions

View file

@ -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
View 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')

View file

@ -27,7 +27,7 @@ Autocomplete View
"""
from .core import View
from .. import Session
from ..db import Session
__all__ = ['AutocompleteView']

View file

@ -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

View file

@ -26,7 +26,7 @@
Print Labels Batch
"""
from .... import Session
from ....db import Session
import rattail
from . import BatchParamsView

View file

@ -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

View file

@ -26,6 +26,10 @@
Core View
"""
__all__ = ['View']
class View(object):
"""
Base for all class-based views.

View file

@ -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']

View file

@ -28,7 +28,7 @@ CustomerGroup Views
from . import SearchableAlchemyGridView, CrudView
from .. import Session
from ..db import Session
from rattail.db.model import CustomerGroup, CustomerGroupAssignment

View file

@ -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)

View file

@ -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',

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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=[