initial users/roles/perms admin stuff
This commit is contained in:
		
							parent
							
								
									02d4cfc6c5
								
							
						
					
					
						commit
						ba94a015a6
					
				
					 24 changed files with 1061 additions and 110 deletions
				
			
		| 
						 | 
				
			
			@ -70,10 +70,10 @@ def administrator_role(session):
 | 
			
		|||
    """
 | 
			
		||||
 | 
			
		||||
    uuid = 'd937fa8a965611dfa0dd001143047286'
 | 
			
		||||
    admin = session.query(Role).get(uuid)
 | 
			
		||||
    admin = session.query(edbob.Role).get(uuid)
 | 
			
		||||
    if admin:
 | 
			
		||||
        return admin
 | 
			
		||||
    admin = Role(uuid=uuid, name='Administrator')
 | 
			
		||||
    admin = edbob.Role(uuid=uuid, name='Administrator')
 | 
			
		||||
    session.add(admin)
 | 
			
		||||
    return admin
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,15 +86,15 @@ def has_permission(obj, perm):
 | 
			
		|||
    fully-qualified permission name, e.g. ``'users.create'``.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if isinstance(obj, User):
 | 
			
		||||
    if isinstance(obj, edbob.User):
 | 
			
		||||
        roles = obj.roles
 | 
			
		||||
    elif isinstance(obj, Role):
 | 
			
		||||
    elif isinstance(obj, edbob.Role):
 | 
			
		||||
        roles = [obj]
 | 
			
		||||
    else:
 | 
			
		||||
        raise TypeError("You must pass either a User or Role for 'obj'; got: %s" % repr(obj))
 | 
			
		||||
    session = object_session(obj)
 | 
			
		||||
    assert session
 | 
			
		||||
    admin = get_administrator(session)
 | 
			
		||||
    admin = administrator_role(session)
 | 
			
		||||
    for role in roles:
 | 
			
		||||
        if role is admin:
 | 
			
		||||
            return True
 | 
			
		||||
| 
						 | 
				
			
			@ -107,15 +107,18 @@ def has_permission(obj, perm):
 | 
			
		|||
def init_database(engine, session):
 | 
			
		||||
    """
 | 
			
		||||
    Initialize the auth system within an ``edbob`` database.
 | 
			
		||||
 | 
			
		||||
    Currently this only creates an :class:`edbob.User` instance with username
 | 
			
		||||
    ``'admin'`` (and password the same), and assigns the user to the built-in
 | 
			
		||||
    administrative role (see :func:`administrator_role()`).
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Create 'admin' user with full rights.
 | 
			
		||||
    admin = edbob.User()
 | 
			
		||||
    admin.username = 'admin'
 | 
			
		||||
    admin = edbob.User(username='admin')
 | 
			
		||||
    set_user_password(admin, 'admin')
 | 
			
		||||
    # admin.roles.append(administrator_role(session))
 | 
			
		||||
    admin.roles.append(administrator_role(session))
 | 
			
		||||
    session.add(admin)
 | 
			
		||||
    session.flush()
 | 
			
		||||
    print "Created 'admin' user with password 'admin'"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_user_password(user, password):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,9 +32,10 @@ from sqlalchemy.ext.associationproxy import association_proxy
 | 
			
		|||
 | 
			
		||||
import edbob
 | 
			
		||||
from edbob.db.model import Base, uuid_column
 | 
			
		||||
from edbob.sqlalchemy import getset_factory
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['Person', 'User']
 | 
			
		||||
__all__ = ['Person', 'Role', 'User', 'UserRole', 'Permission']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_person_display_name(context):
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +66,68 @@ class Person(Base):
 | 
			
		|||
        return str(self.display_name or '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Permission(Base):
 | 
			
		||||
    """
 | 
			
		||||
    Represents the fact that a particular :class:`Role` is allowed to do a
 | 
			
		||||
    particular type of thing.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __tablename__ = 'permissions'
 | 
			
		||||
 | 
			
		||||
    role_uuid = Column(String(32), ForeignKey('roles.uuid'), primary_key=True)
 | 
			
		||||
    permission = Column(String(50), primary_key=True)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Permission: %s, %s>" % (self.role, self.permission)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return str(self.permission or '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserRole(Base):
 | 
			
		||||
    """
 | 
			
		||||
    Represents the association between a :class:`User` and a :class:`Role`.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __tablename__ = 'users_roles'
 | 
			
		||||
 | 
			
		||||
    uuid = uuid_column()
 | 
			
		||||
    user_uuid = Column(String(32), ForeignKey('users.uuid'))
 | 
			
		||||
    role_uuid = Column(String(32), ForeignKey('roles.uuid'))
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<UserRole: %s : %s>" % (self.user, self.role)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Role(Base):
 | 
			
		||||
    """
 | 
			
		||||
    Represents a role within the system; used to manage permissions.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __tablename__ = 'roles'
 | 
			
		||||
 | 
			
		||||
    uuid = uuid_column()
 | 
			
		||||
    name = Column(String(25), nullable=False, unique=True)
 | 
			
		||||
 | 
			
		||||
    _permissions = relationship(
 | 
			
		||||
        Permission, backref='role',
 | 
			
		||||
        cascade='save-update, merge, delete, delete-orphan')
 | 
			
		||||
    permissions = association_proxy('_permissions', 'permission',
 | 
			
		||||
                                    creator=lambda x: Permission(permission=x),
 | 
			
		||||
                                    getset_factory=getset_factory)
 | 
			
		||||
 | 
			
		||||
    _users = relationship(UserRole, backref='role')
 | 
			
		||||
    users = association_proxy('_users', 'user',
 | 
			
		||||
                              creator=lambda x: UserRole(user=x),
 | 
			
		||||
                              getset_factory=getset_factory)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Role: %s>" % self.name
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return str(self.name or '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(Base):
 | 
			
		||||
    """
 | 
			
		||||
    Represents a user of the system.  This may or may not correspond to a real
 | 
			
		||||
| 
						 | 
				
			
			@ -79,12 +142,11 @@ class User(Base):
 | 
			
		|||
    salt = Column(String(29))
 | 
			
		||||
    person_uuid = Column(String(32), ForeignKey('people.uuid'))
 | 
			
		||||
 | 
			
		||||
    person = relationship(Person, backref='user')
 | 
			
		||||
    # display_name = association_proxy('person', 'display_name')
 | 
			
		||||
 | 
			
		||||
    # roles = association_proxy('_roles', 'role',
 | 
			
		||||
    #                           creator=lambda x: UserRole(role=x),
 | 
			
		||||
    #                           getset_factory=getset_factory)
 | 
			
		||||
    _roles = relationship(UserRole, backref='user')
 | 
			
		||||
    roles = association_proxy(
 | 
			
		||||
        '_roles', 'role',
 | 
			
		||||
        creator=lambda x: UserRole(role=x),
 | 
			
		||||
        getset_factory=getset_factory)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<User: %s>" % self.username
 | 
			
		||||
| 
						 | 
				
			
			@ -101,3 +163,14 @@ class User(Base):
 | 
			
		|||
        if self.person and self.person.display_name:
 | 
			
		||||
            return self.person.display_name
 | 
			
		||||
        return self.username
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Person.user = relationship(
 | 
			
		||||
    User,
 | 
			
		||||
    back_populates='person',
 | 
			
		||||
    uselist=False)
 | 
			
		||||
 | 
			
		||||
User.person = relationship(
 | 
			
		||||
    Person,
 | 
			
		||||
    back_populates='user',
 | 
			
		||||
    uselist=False)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,21 +29,12 @@
 | 
			
		|||
from sqlalchemy import Column, String, Text
 | 
			
		||||
 | 
			
		||||
import edbob
 | 
			
		||||
# from edbob import Object, get_uuid
 | 
			
		||||
from edbob.db import Base
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = ['ActiveExtension', 'Setting']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# class ClassWithUuid(Object):
 | 
			
		||||
#     """
 | 
			
		||||
#     Simple mixin class which defines a ``uuid`` column as primary key.
 | 
			
		||||
#     """
 | 
			
		||||
 | 
			
		||||
#     Column('uuid', String(32), primary_key=True, default=get_uuid)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def uuid_column(*args):
 | 
			
		||||
    """
 | 
			
		||||
    Convenience function which returns a ``uuid`` column for use as a table's
 | 
			
		||||
| 
						 | 
				
			
			@ -81,77 +72,3 @@ class Setting(Base):
 | 
			
		|||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Setting: %s>" % self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# def get_metadata(*args, **kwargs):
 | 
			
		||||
#     """
 | 
			
		||||
#     Returns the core ``edbob`` schema definition.
 | 
			
		||||
 | 
			
		||||
#     Note that when :func:`edbob.init()` is called, the ``sqlalchemy.MetaData``
 | 
			
		||||
#     instance which is returned from this function will henceforth be available
 | 
			
		||||
#     as ``edbob.metadata``.  However, ``edbob.init()`` may extend
 | 
			
		||||
#     ``edbob.metadata`` as well, depending on which extensions are activated
 | 
			
		||||
#     within the primary database.
 | 
			
		||||
 | 
			
		||||
#     This function then serves two purposes: First, it provides the core
 | 
			
		||||
#     metadata instance.  Secondly, it allows edbob to always know what its core
 | 
			
		||||
#     schema looks like, as opposed to what's held in the current
 | 
			
		||||
#     ``edbob.metadata`` instance, which may have been extended locally.  (The
 | 
			
		||||
#     latter use is necessary in order for edbob to properly manage its
 | 
			
		||||
#     extensions.)
 | 
			
		||||
 | 
			
		||||
#     All arguments (positional and keyword) are passed directly to the
 | 
			
		||||
#     ``sqlalchemy.MetaData()`` constructor.
 | 
			
		||||
#     """
 | 
			
		||||
 | 
			
		||||
#     metadata = MetaData(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
#     active_extensions = Table(
 | 
			
		||||
#         'active_extensions', metadata,
 | 
			
		||||
#         Column('name', String(50), primary_key=True),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     def get_person_display_name(context):
 | 
			
		||||
#         first_name = context.current_parameters['first_name']
 | 
			
		||||
#         last_name = context.current_parameters['last_name']
 | 
			
		||||
#         if not (first_name or last_name):
 | 
			
		||||
#             return None
 | 
			
		||||
#         return '%(first_name)s %(last_name)s' % locals()
 | 
			
		||||
 | 
			
		||||
#     people = table_with_uuid(
 | 
			
		||||
#         'people', metadata,
 | 
			
		||||
#         Column('first_name', String(50)),
 | 
			
		||||
#         Column('last_name', String(50)),
 | 
			
		||||
#         Column('display_name', String(100), default=get_person_display_name),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     permissions = Table(
 | 
			
		||||
#         'permissions', metadata,
 | 
			
		||||
#         Column('role_uuid', String(32), ForeignKey('roles.uuid'), primary_key=True),
 | 
			
		||||
#         Column('permission', String(50), primary_key=True),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     roles = table_with_uuid(
 | 
			
		||||
#         'roles', metadata,
 | 
			
		||||
#         Column('name', String(25), nullable=False, unique=True),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     settings = Table(
 | 
			
		||||
#         'settings', metadata,
 | 
			
		||||
#         Column('name', String(255), primary_key=True),
 | 
			
		||||
#         Column('value', Text),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     users = table_with_uuid(
 | 
			
		||||
#         'users', metadata,
 | 
			
		||||
#         Column('username', String(25), nullable=False, unique=True),
 | 
			
		||||
#         Column('person_uuid', String(32), ForeignKey('people.uuid')),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     users_roles = table_with_uuid(
 | 
			
		||||
#         'users_roles', metadata,
 | 
			
		||||
#         Column('user_uuid', String(32), ForeignKey('users.uuid')),
 | 
			
		||||
#         Column('role_uuid', String(32), ForeignKey('roles.uuid')),
 | 
			
		||||
#         )
 | 
			
		||||
 | 
			
		||||
#     return metadata
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,10 +35,29 @@ import edbob.db
 | 
			
		|||
__all__ = ['Session']
 | 
			
		||||
 | 
			
		||||
Session = scoped_session(edbob.db.Session)
 | 
			
		||||
Session.configure(extension=ZopeTransactionExtension())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
    """
 | 
			
		||||
    Adds ``edbob``-specific features to the application.  Currently this does
 | 
			
		||||
    two things:
 | 
			
		||||
 | 
			
		||||
    It adds a ``ZopeTransactionExtension`` instance as an extension to the
 | 
			
		||||
    SQLAlchemy scoped ``Session`` class.  This is necessary for most view code
 | 
			
		||||
    that ships with ``edbob``, so you will most likely need to specify
 | 
			
		||||
    ``config.include('edbob.pyramid')`` somewhere in your app config (i.e. your
 | 
			
		||||
    ``main()`` function).
 | 
			
		||||
 | 
			
		||||
    The other thing added is the ``edbob`` static view for CSS files etc.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Session is extended here instead of at module scope to prevent import
 | 
			
		||||
    # side-effects.
 | 
			
		||||
    Session.configure(extension=ZopeTransactionExtension())
 | 
			
		||||
 | 
			
		||||
    # Forbidden view is configured here instead of within edbob.pyramid.views
 | 
			
		||||
    # since it's so "important."
 | 
			
		||||
    config.add_forbidden_view('edbob.pyramid.views.forbidden')
 | 
			
		||||
 | 
			
		||||
    # Same goes with the edbob static route; we need that JS.
 | 
			
		||||
    config.include('edbob.pyramid.static')
 | 
			
		||||
    config.include('edbob.pyramid.subscribers')
 | 
			
		||||
    # config.include('edbob.pyramid.views')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										57
									
								
								edbob/pyramid/auth.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								edbob/pyramid/auth.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8  -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  edbob -- Pythonic Software Framework
 | 
			
		||||
#  Copyright © 2010-2012 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of edbob.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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 edbob.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
``edbob.pyramid.auth`` -- Authentication & Authorization
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from zope.interface import implementer
 | 
			
		||||
 | 
			
		||||
from pyramid.interfaces import IAuthorizationPolicy
 | 
			
		||||
from pyramid.security import Everyone, Authenticated
 | 
			
		||||
 | 
			
		||||
import edbob
 | 
			
		||||
from edbob.db.auth import has_permission
 | 
			
		||||
from edbob.pyramid import Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# def groupfinder(userid, request):
 | 
			
		||||
#     q = Session.query(edbob.UserRole)
 | 
			
		||||
#     q = q.filter(edbob.UserRole.user_uuid == userid)
 | 
			
		||||
#     return [x.role_uuid for x in q]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@implementer(IAuthorizationPolicy)
 | 
			
		||||
class EdbobAuthorizationPolicy(object):
 | 
			
		||||
 | 
			
		||||
    def permits(self, context, principals, permission):
 | 
			
		||||
        for userid in principals:
 | 
			
		||||
            if userid not in (Everyone, Authenticated):
 | 
			
		||||
                user = Session.query(edbob.User).get(userid)
 | 
			
		||||
                assert user
 | 
			
		||||
                return has_permission(user, permission)
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def principals_allowed_by_permission(self, context, permission):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +57,6 @@ class InitCommand(commands.Subcommand):
 | 
			
		|||
        # activate_extension('shrubbery')
 | 
			
		||||
 | 
			
		||||
        # Okay, on to bootstrapping...
 | 
			
		||||
 | 
			
		||||
        session = Session()
 | 
			
		||||
 | 
			
		||||
        # This creates an 'admin' user with 'admin' password.        
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -385,6 +385,7 @@ div.field-couple div.field {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
div.field-couple div.field input[type=text],
 | 
			
		||||
div.field-couple div.field input[type=password],
 | 
			
		||||
div.field-couple div.field select {
 | 
			
		||||
    width: 320px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -393,11 +394,12 @@ div.checkbox {
 | 
			
		|||
    margin: 15px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.fieldset tr {
 | 
			
		||||
table.fieldset tbody tr {
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.fieldset td {
 | 
			
		||||
table.fieldset tbody td {
 | 
			
		||||
    height: 30px;
 | 
			
		||||
    padding: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -406,6 +408,16 @@ table.fieldset td.label {
 | 
			
		|||
    width: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.fieldset tbody td ul {
 | 
			
		||||
    padding-left: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.fieldset tbody td ul li {
 | 
			
		||||
    line-height: 1em;
 | 
			
		||||
    margin-bottom: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/******************************
 | 
			
		||||
 * Sub-Grids
 | 
			
		||||
 ******************************/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								edbob/pyramid/static/css/perms.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								edbob/pyramid/static/css/perms.css
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
 | 
			
		||||
/******************************
 | 
			
		||||
 * perms.css
 | 
			
		||||
 ******************************/
 | 
			
		||||
 | 
			
		||||
div.field-couple.permissions div.field p.group {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.field-couple.permissions div.field label {
 | 
			
		||||
    float: none;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.field-couple.permissions div.field label input {
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -69,9 +69,9 @@ def context_found(event):
 | 
			
		|||
        return has_perm
 | 
			
		||||
 | 
			
		||||
    request = event.request
 | 
			
		||||
    request.user = None
 | 
			
		||||
    request.has_perm = has_perm_func(request)
 | 
			
		||||
 | 
			
		||||
    request.user = None
 | 
			
		||||
    uuid = authenticated_userid(request)
 | 
			
		||||
    if uuid:
 | 
			
		||||
        request.user = Session.query(edbob.User).get(uuid)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@
 | 
			
		|||
  ${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/login.css'))}
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${h.image(logo_url, "${self.global_title()} logo", id='login-logo')}
 | 
			
		||||
${h.image(logo_url, "${self.global_title()} logo", id='login-logo', **logo_kwargs)}
 | 
			
		||||
 | 
			
		||||
<div class="fieldset">
 | 
			
		||||
  ${h.form('')}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								edbob/pyramid/templates/people/base.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								edbob/pyramid/templates/people/base.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
<%inherit file="/base.mako" />
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										12
									
								
								edbob/pyramid/templates/people/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								edbob/pyramid/templates/people/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<%inherit file="/people/base.mako" />
 | 
			
		||||
<%inherit file="/index.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="title()">People</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="menu()">
 | 
			
		||||
  % if request.has_perm('people.create'):
 | 
			
		||||
      <p>${h.link_to("Create a new Person", url('person.new'))}</p>
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										29
									
								
								edbob/pyramid/templates/people/person.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								edbob/pyramid/templates/people/person.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
<%inherit file="/people/base.mako" />
 | 
			
		||||
<%inherit file="/crud.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="crud_name()">Person</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="menu()">
 | 
			
		||||
  <p>${h.link_to("Back to People", url('people.list'))}</p>
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
 | 
			
		||||
% if fieldset.edit:
 | 
			
		||||
    <h2>User Info</h2>
 | 
			
		||||
    % if user:
 | 
			
		||||
	${user.render()|n}
 | 
			
		||||
	<div class="buttons">
 | 
			
		||||
	  <button type="button" onclick="location.href = '${url('user.edit', uuid=user.model.uuid)}';">Edit User</button>
 | 
			
		||||
	</div>
 | 
			
		||||
    % else:
 | 
			
		||||
	<p>This person does not have a user account.</p>
 | 
			
		||||
	${h.form(url('user.new'))}
 | 
			
		||||
	${h.hidden('User--person_uuid', value=fieldset.model.uuid)}
 | 
			
		||||
	${h.hidden('User--username')}
 | 
			
		||||
	<div class="buttons">
 | 
			
		||||
	  ${h.submit('submit', "Create User")}
 | 
			
		||||
	</div>
 | 
			
		||||
	${h.end_form()}
 | 
			
		||||
    % endif
 | 
			
		||||
% endif
 | 
			
		||||
							
								
								
									
										2
									
								
								edbob/pyramid/templates/roles/base.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								edbob/pyramid/templates/roles/base.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
<%inherit file="/base.mako" />
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										10
									
								
								edbob/pyramid/templates/roles/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								edbob/pyramid/templates/roles/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<%inherit file="/roles/base.mako" />
 | 
			
		||||
<%inherit file="/index.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="title()">Roles</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="menu()">
 | 
			
		||||
  <p>${h.link_to("Create a new Role", url('role.new'))}</p>
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										15
									
								
								edbob/pyramid/templates/roles/role.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								edbob/pyramid/templates/roles/role.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
<%inherit file="/roles/base.mako" />
 | 
			
		||||
<%inherit file="/crud.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="crud_name()">Role</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="head_tags()">
 | 
			
		||||
  ${parent.head_tags()}
 | 
			
		||||
  ${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/perms.css'))}
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="menu()">
 | 
			
		||||
  <p>${h.link_to("Back to Roles", url('roles.list'))}</p>
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										2
									
								
								edbob/pyramid/templates/users/base.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								edbob/pyramid/templates/users/base.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
<%inherit file="/base.mako" />
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										10
									
								
								edbob/pyramid/templates/users/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								edbob/pyramid/templates/users/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<%inherit file="/users/base.mako" />
 | 
			
		||||
<%inherit file="/index.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="title()">Users</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="menu()">
 | 
			
		||||
  <p>${h.link_to("Create a new User", url('user.new'))}</p>
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
							
								
								
									
										10
									
								
								edbob/pyramid/templates/users/user.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								edbob/pyramid/templates/users/user.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
<%inherit file="/users/base.mako" />
 | 
			
		||||
<%inherit file="/crud.mako" />
 | 
			
		||||
 | 
			
		||||
<%def name="crud_name()">User</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="menu()">
 | 
			
		||||
  <p>${h.link_to("Back to Users", url('users.list'))}</p>
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
${parent.body()}
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +26,32 @@
 | 
			
		|||
``edbob.pyramid.views`` -- Views
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from pyramid.httpexceptions import HTTPFound
 | 
			
		||||
from pyramid.security import authenticated_userid
 | 
			
		||||
 | 
			
		||||
# def includeme(config):
 | 
			
		||||
#     config.include('edbob.pyramid.views.auth')
 | 
			
		||||
from webhelpers.html import literal
 | 
			
		||||
from webhelpers.html.tags import link_to
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def forbidden(request):
 | 
			
		||||
    """
 | 
			
		||||
    The forbidden view.  This is triggered whenever access rights are denied
 | 
			
		||||
    for an otherwise-appropriate view.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    msg = literal("You do not have permission to do that.")
 | 
			
		||||
    if not authenticated_userid(request):
 | 
			
		||||
        msg += literal("  (Perhaps you should %s?)" %
 | 
			
		||||
                       link_to("log in", request.route_url('login')))
 | 
			
		||||
    request.session.flash(msg)
 | 
			
		||||
 | 
			
		||||
    url = request.referer
 | 
			
		||||
    if not url or url == request.current_route_url():
 | 
			
		||||
        url = request.route_url('home')
 | 
			
		||||
    return HTTPFound(location=url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
    config.include('edbob.pyramid.views.auth')
 | 
			
		||||
    config.include('edbob.pyramid.views.people')
 | 
			
		||||
    config.include('edbob.pyramid.views.users')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,17 +78,29 @@ def login(context, request):
 | 
			
		|||
 | 
			
		||||
    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), 'referer': referer, 'logo_url': url}
 | 
			
		||||
    return {'form': FormRenderer(form), 'referer': referer,
 | 
			
		||||
            'logo_url': url, 'logo_kwargs': kwargs}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def logout(context, 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)
 | 
			
		||||
    return HTTPFound(location=request.route_url('login'), headers=headers)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
 | 
			
		||||
    config.add_route('login', '/login')
 | 
			
		||||
    config.add_view(login, route_name='login', renderer='/login.mako')
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										180
									
								
								edbob/pyramid/views/people.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								edbob/pyramid/views/people.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,180 @@
 | 
			
		|||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8  -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  edbob -- Pythonic Software Framework
 | 
			
		||||
#  Copyright © 2010-2012 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of edbob.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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 edbob.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
``edbob.pyramid.views.people`` -- Person Views
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import transaction
 | 
			
		||||
from pyramid.httpexceptions import HTTPFound
 | 
			
		||||
 | 
			
		||||
from formalchemy import Field
 | 
			
		||||
 | 
			
		||||
import edbob
 | 
			
		||||
from edbob.pyramid import filters
 | 
			
		||||
from edbob.pyramid import forms
 | 
			
		||||
from edbob.pyramid import grids
 | 
			
		||||
from edbob.pyramid import Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def filter_map():
 | 
			
		||||
    return filters.get_filter_map(
 | 
			
		||||
        edbob.Person,
 | 
			
		||||
        ilike=['first_name', 'last_name', 'display_name'])
 | 
			
		||||
 | 
			
		||||
def search_config(request, fmap):
 | 
			
		||||
    return filters.get_search_config(
 | 
			
		||||
        'people.list', request, fmap,
 | 
			
		||||
        include_filter_display_name=True,
 | 
			
		||||
        filter_type_display_name='lk')
 | 
			
		||||
 | 
			
		||||
def search_form(config):
 | 
			
		||||
    return filters.get_search_form(config)
 | 
			
		||||
 | 
			
		||||
def grid_config(request, search, fmap):
 | 
			
		||||
    return grids.get_grid_config(
 | 
			
		||||
        'people.list', request, search,
 | 
			
		||||
        filter_map=fmap, sort='display_name')
 | 
			
		||||
 | 
			
		||||
def sort_map():
 | 
			
		||||
    return grids.get_sort_map(
 | 
			
		||||
        edbob.Person,
 | 
			
		||||
        ['first_name', 'last_name', 'display_name'])
 | 
			
		||||
 | 
			
		||||
def query(config):
 | 
			
		||||
    smap = sort_map()
 | 
			
		||||
    q = Session.query(edbob.Person)
 | 
			
		||||
    q = filters.filter_query(q, config)
 | 
			
		||||
    q = grids.sort_query(q, config, smap)
 | 
			
		||||
    return q
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def people(context, request):
 | 
			
		||||
 | 
			
		||||
    fmap = filter_map()
 | 
			
		||||
    config = search_config(request, fmap)
 | 
			
		||||
    search = search_form(config)
 | 
			
		||||
    config = grid_config(request, search, fmap)
 | 
			
		||||
    people = grids.get_pager(query, config)
 | 
			
		||||
 | 
			
		||||
    g = forms.AlchemyGrid(
 | 
			
		||||
        edbob.Person, people, config,
 | 
			
		||||
        gridurl=request.route_url('people.list'),
 | 
			
		||||
        objurl='person.edit')
 | 
			
		||||
 | 
			
		||||
    g.configure(
 | 
			
		||||
        include=[
 | 
			
		||||
            g.first_name,
 | 
			
		||||
            g.last_name,
 | 
			
		||||
            g.display_name,
 | 
			
		||||
            ],
 | 
			
		||||
        readonly=True)
 | 
			
		||||
 | 
			
		||||
    grid = g.render(class_='clickable people')
 | 
			
		||||
    return grids.render_grid(request, grid, search)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def person_fieldset(person, request):
 | 
			
		||||
    fs = forms.make_fieldset(person, url=request.route_url,
 | 
			
		||||
                             url_action=request.current_route_url(),
 | 
			
		||||
                             route_name='people.list')
 | 
			
		||||
    fs.configure(
 | 
			
		||||
        include=[
 | 
			
		||||
            fs.first_name,
 | 
			
		||||
            fs.last_name,
 | 
			
		||||
            fs.display_name,
 | 
			
		||||
            ])
 | 
			
		||||
    return fs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def new_person(context, request):
 | 
			
		||||
 | 
			
		||||
    fs = person_fieldset(edbob.Person, request)
 | 
			
		||||
    if not fs.readonly and request.POST:
 | 
			
		||||
        fs.rebind(data=request.params)
 | 
			
		||||
        if fs.validate():
 | 
			
		||||
 | 
			
		||||
            with transaction.manager:
 | 
			
		||||
                fs.sync()
 | 
			
		||||
                Session.add(fs.model)
 | 
			
		||||
                Session.flush()
 | 
			
		||||
                request.session.flash("%s \"%s\" has been %s." % (
 | 
			
		||||
                        fs.crud_title, fs.get_display_text(),
 | 
			
		||||
                        'updated' if fs.edit else 'created'))
 | 
			
		||||
 | 
			
		||||
            return HTTPFound(location=request.route_url('people.list'))
 | 
			
		||||
 | 
			
		||||
    return {'fieldset': fs, 'crud': True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_person(request):
 | 
			
		||||
    """
 | 
			
		||||
    View for editing a :class:`edbob.Person` instance.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    from edbob.pyramid.views.users import user_fieldset
 | 
			
		||||
 | 
			
		||||
    uuid = request.matchdict['uuid']
 | 
			
		||||
    person = Session.query(edbob.Person).get(uuid) if uuid else None
 | 
			
		||||
    assert person
 | 
			
		||||
 | 
			
		||||
    fs = person_fieldset(person, request)
 | 
			
		||||
    if request.POST:
 | 
			
		||||
        fs.rebind(data=request.params)
 | 
			
		||||
        if fs.validate():
 | 
			
		||||
 | 
			
		||||
            with transaction.manager:
 | 
			
		||||
                fs.sync()
 | 
			
		||||
                fs.model = Session.merge(fs.model)
 | 
			
		||||
                request.session.flash("%s \"%s\" has been %s." % (
 | 
			
		||||
                        fs.crud_title, fs.get_display_text(),
 | 
			
		||||
                        'updated' if fs.edit else 'created'))
 | 
			
		||||
                home = request.route_url('people.list')
 | 
			
		||||
 | 
			
		||||
            return HTTPFound(location=home)
 | 
			
		||||
 | 
			
		||||
    user = fs.model.user
 | 
			
		||||
    if user:
 | 
			
		||||
        user = user_fieldset(user, request)
 | 
			
		||||
        user.readonly = True
 | 
			
		||||
        del user.person
 | 
			
		||||
        del user.password
 | 
			
		||||
        del user.confirm_password
 | 
			
		||||
 | 
			
		||||
    return {'fieldset': fs, 'crud': True, 'user': user}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
 | 
			
		||||
    config.add_route('people.list', '/people')
 | 
			
		||||
    config.add_view(people, route_name='people.list', renderer='/people/index.mako',
 | 
			
		||||
                    permission='people.list', http_cache=0)
 | 
			
		||||
 | 
			
		||||
    config.add_route('person.new', '/people/new')
 | 
			
		||||
    config.add_view(new_person, route_name='person.new', renderer='/people/person.mako',
 | 
			
		||||
                    permission='people.create', http_cache=0)
 | 
			
		||||
 | 
			
		||||
    config.add_route('person.edit', '/people/{uuid}/edit')
 | 
			
		||||
    config.add_view(edit_person, route_name='person.edit', renderer='/people/person.mako',
 | 
			
		||||
                    permission='people.edit', http_cache=0)
 | 
			
		||||
							
								
								
									
										255
									
								
								edbob/pyramid/views/roles.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								edbob/pyramid/views/roles.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,255 @@
 | 
			
		|||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8  -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  edbob -- Pythonic Software Framework
 | 
			
		||||
#  Copyright © 2010-2012 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of edbob.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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 edbob.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
``edbob.pyramid.views.roles`` -- Role Views
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import transaction
 | 
			
		||||
from pyramid.httpexceptions import HTTPFound
 | 
			
		||||
 | 
			
		||||
from formalchemy import Field, FieldRenderer
 | 
			
		||||
from webhelpers.html import literal
 | 
			
		||||
from webhelpers.html.tags import checkbox, hidden
 | 
			
		||||
 | 
			
		||||
import edbob
 | 
			
		||||
from edbob.db.auth import administrator_role, has_permission
 | 
			
		||||
from edbob.pyramid import filters
 | 
			
		||||
from edbob.pyramid import forms
 | 
			
		||||
from edbob.pyramid import grids
 | 
			
		||||
from edbob.pyramid import Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def filter_map():
 | 
			
		||||
    return filters.get_filter_map(
 | 
			
		||||
        edbob.Role,
 | 
			
		||||
        ilike=['name'])
 | 
			
		||||
 | 
			
		||||
def search_config(request, fmap):
 | 
			
		||||
    return filters.get_search_config(
 | 
			
		||||
        'roles.list', request, fmap,
 | 
			
		||||
        include_filter_name=True,
 | 
			
		||||
        filter_type_name='lk')
 | 
			
		||||
 | 
			
		||||
def search_form(config):
 | 
			
		||||
    return filters.get_search_form(config)
 | 
			
		||||
 | 
			
		||||
def grid_config(request, search, fmap):
 | 
			
		||||
    return grids.get_grid_config(
 | 
			
		||||
        'roles.list', request, search,
 | 
			
		||||
        filter_map=fmap, sort='name')
 | 
			
		||||
 | 
			
		||||
def sort_map():
 | 
			
		||||
    return grids.get_sort_map(edbob.Role, ['name'])
 | 
			
		||||
 | 
			
		||||
def query(config):
 | 
			
		||||
    smap = sort_map()
 | 
			
		||||
    q = Session.query(edbob.Role)
 | 
			
		||||
    q = filters.filter_query(q, config)
 | 
			
		||||
    q = grids.sort_query(q, config, smap)
 | 
			
		||||
    return q
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def roles(request):
 | 
			
		||||
 | 
			
		||||
    fmap = filter_map()
 | 
			
		||||
    config = search_config(request, fmap)
 | 
			
		||||
    search = search_form(config)
 | 
			
		||||
    config = grid_config(request, search, fmap)
 | 
			
		||||
    roles = grids.get_pager(query, config)
 | 
			
		||||
 | 
			
		||||
    g = forms.AlchemyGrid(
 | 
			
		||||
        edbob.Role, roles, config,
 | 
			
		||||
        gridurl=request.route_url('roles.list'),
 | 
			
		||||
        objurl='role.edit')
 | 
			
		||||
 | 
			
		||||
    g.configure(
 | 
			
		||||
        include=[
 | 
			
		||||
            g.name,
 | 
			
		||||
            ],
 | 
			
		||||
        readonly=True)
 | 
			
		||||
 | 
			
		||||
    grid = g.render(class_='clickable roles')
 | 
			
		||||
    return grids.render_grid(request, grid, search)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PermissionsField(Field):
 | 
			
		||||
 | 
			
		||||
    def sync(self):
 | 
			
		||||
        if not self.is_readonly():
 | 
			
		||||
            role = self.model
 | 
			
		||||
            role.permissions = self.renderer.deserialize()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PermissionsFieldRenderer(FieldRenderer):
 | 
			
		||||
 | 
			
		||||
    available_permissions = [
 | 
			
		||||
 | 
			
		||||
        ("Batches", [
 | 
			
		||||
                ('batches.list',        "List Batches"),
 | 
			
		||||
                ('batches.edit',        "Edit Batch"),
 | 
			
		||||
                ('batches.create',      "Create Batch"),
 | 
			
		||||
                ]),
 | 
			
		||||
 | 
			
		||||
        ("Roles", [
 | 
			
		||||
                ('roles.list',          "List Roles"),
 | 
			
		||||
                ('roles.edit',          "Edit Role"),
 | 
			
		||||
                ('roles.create',        "Create Role"),
 | 
			
		||||
                ]),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def deserialize(self):
 | 
			
		||||
        perms = []
 | 
			
		||||
        i = len(self.name) + 1
 | 
			
		||||
        for key in self.params:
 | 
			
		||||
            if key.startswith(self.name):
 | 
			
		||||
                perms.append(key[i:])
 | 
			
		||||
        return perms
 | 
			
		||||
 | 
			
		||||
    def _render(self, readonly=False, **kwargs):
 | 
			
		||||
        # result = literal('')
 | 
			
		||||
        # for group_name, group_label, perm_list in self.field.model_value:
 | 
			
		||||
        #     rendered_group_name = literal('<p class="permission-group">' + group_label + '</p>\n')
 | 
			
		||||
        #     if readonly:
 | 
			
		||||
        #         result += literal('<tr><td colspan="2">') + rendered_group_name + literal('</td></tr>')
 | 
			
		||||
        #     else:
 | 
			
		||||
        #         result += rendered_group_name
 | 
			
		||||
        #         result += literal('<div>')
 | 
			
		||||
        #     for perm_name, perm_label, checked in perm_list:
 | 
			
		||||
        #         if readonly:
 | 
			
		||||
        #             result += literal('<tr>'
 | 
			
		||||
        #                               + '<td class="permission">' + ('[X]' if checked else '[  ]') + '</td>'
 | 
			
		||||
        #                               + '<td class="permission-label">' + perm_label + '</td>'
 | 
			
		||||
        #                               + '</tr>\n')
 | 
			
		||||
        #         else:
 | 
			
		||||
        #             name = '.'.join((self.name, group_name, perm_name))
 | 
			
		||||
        #             result += check_box(name, label=perm_label, checked=checked)
 | 
			
		||||
        #     if not readonly:
 | 
			
		||||
        #         result += literal('</div>')
 | 
			
		||||
        # if readonly:
 | 
			
		||||
        #     return literal('<table class="permissions">') + result + literal('</table>')
 | 
			
		||||
        # return literal('<div class="permissions">') + result + literal('</div>')
 | 
			
		||||
 | 
			
		||||
        role = self.field.model
 | 
			
		||||
        if role is administrator_role(Session()):
 | 
			
		||||
            res = literal('<p>This is the administrative role; '
 | 
			
		||||
                          'it has full access to the entire system.</p>')
 | 
			
		||||
            if not readonly:
 | 
			
		||||
                res += hidden(self.name, value='') # ugly hack..or good idea?
 | 
			
		||||
        else:
 | 
			
		||||
            res = ''
 | 
			
		||||
            for group, perms in self.available_permissions:
 | 
			
		||||
                res += literal('<p class="group">%s</p>' % group)
 | 
			
		||||
                for perm, title in perms:
 | 
			
		||||
                    if readonly:
 | 
			
		||||
                        res += literal('<p>%s</p>' % title)
 | 
			
		||||
                    else:
 | 
			
		||||
                        checked = has_permission(role, perm)
 | 
			
		||||
                        res += checkbox(self.name + '-' + perm,
 | 
			
		||||
                                        checked=checked, label=title)
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    def render(self, **kwargs):
 | 
			
		||||
        return self._render(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def render_readonly(self, **kwargs):
 | 
			
		||||
        return self._render(readonly=True, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def role_fieldset(role, request):
 | 
			
		||||
    fs = forms.make_fieldset(role, url=request.route_url,
 | 
			
		||||
                             url_action=request.current_route_url(),
 | 
			
		||||
                             route_name='roles.list')
 | 
			
		||||
    
 | 
			
		||||
    fs.append(PermissionsField('permissions',
 | 
			
		||||
                               renderer=PermissionsFieldRenderer))
 | 
			
		||||
 | 
			
		||||
    fs.configure(
 | 
			
		||||
        include=[
 | 
			
		||||
            fs.name,
 | 
			
		||||
            fs.permissions,
 | 
			
		||||
            ])
 | 
			
		||||
 | 
			
		||||
    if not fs.edit:
 | 
			
		||||
        del fs.permissions
 | 
			
		||||
 | 
			
		||||
    return fs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def new_role(request):
 | 
			
		||||
 | 
			
		||||
    fs = role_fieldset(edbob.Role, request)
 | 
			
		||||
    if request.POST:
 | 
			
		||||
        fs.rebind(data=request.params)
 | 
			
		||||
        if fs.validate():
 | 
			
		||||
 | 
			
		||||
            with transaction.manager:
 | 
			
		||||
                fs.sync()
 | 
			
		||||
                fs.model = Session.merge(fs.model)
 | 
			
		||||
                request.session.flash("%s \"%s\" has been %s." % (
 | 
			
		||||
                        fs.crud_title, fs.get_display_text(),
 | 
			
		||||
                        'updated' if fs.edit else 'created'))
 | 
			
		||||
                home = request.route_url('roles.list')
 | 
			
		||||
 | 
			
		||||
            return HTTPFound(location=home)
 | 
			
		||||
 | 
			
		||||
    return {'fieldset': fs, 'crud': True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_role(request):
 | 
			
		||||
    uuid = request.matchdict['uuid']
 | 
			
		||||
    role = Session.query(edbob.Role).get(uuid) if uuid else None
 | 
			
		||||
    assert role
 | 
			
		||||
 | 
			
		||||
    fs = role_fieldset(role, request)
 | 
			
		||||
    if request.POST:
 | 
			
		||||
        fs.rebind(data=request.params)
 | 
			
		||||
        if fs.validate():
 | 
			
		||||
 | 
			
		||||
            with transaction.manager:
 | 
			
		||||
                Session.add(fs.model)
 | 
			
		||||
                fs.sync()
 | 
			
		||||
                request.session.flash("%s \"%s\" has been %s." % (
 | 
			
		||||
                        fs.crud_title, fs.get_display_text(),
 | 
			
		||||
                        'updated' if fs.edit else 'created'))
 | 
			
		||||
                home = request.route_url('roles.list')
 | 
			
		||||
 | 
			
		||||
            return HTTPFound(location=home)
 | 
			
		||||
 | 
			
		||||
    return {'fieldset': fs, 'crud': True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
 | 
			
		||||
    config.add_route('roles.list', '/roles')
 | 
			
		||||
    config.add_view(roles, route_name='roles.list', renderer='/roles/index.mako',
 | 
			
		||||
                    permission='roles.list', http_cache=0)
 | 
			
		||||
 | 
			
		||||
    config.add_route('role.new', '/roles/new')
 | 
			
		||||
    config.add_view(new_role, route_name='role.new', renderer='/roles/role.mako',
 | 
			
		||||
                    permission='roles.create', http_cache=0)
 | 
			
		||||
 | 
			
		||||
    config.add_route('role.edit', '/roles/{uuid}/edit')
 | 
			
		||||
    config.add_view(edit_role, route_name='role.edit', renderer='/roles/role.mako',
 | 
			
		||||
                    permission='roles.edit', http_cache=0)
 | 
			
		||||
							
								
								
									
										289
									
								
								edbob/pyramid/views/users.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								edbob/pyramid/views/users.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,289 @@
 | 
			
		|||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8  -*-
 | 
			
		||||
################################################################################
 | 
			
		||||
#
 | 
			
		||||
#  edbob -- Pythonic Software Framework
 | 
			
		||||
#  Copyright © 2010-2012 Lance Edgar
 | 
			
		||||
#
 | 
			
		||||
#  This file is part of edbob.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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.
 | 
			
		||||
#
 | 
			
		||||
#  edbob 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 edbob.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
``edbob.pyramid.views.users`` -- User Views
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import transaction
 | 
			
		||||
from pyramid.httpexceptions import HTTPFound
 | 
			
		||||
 | 
			
		||||
import formalchemy
 | 
			
		||||
from formalchemy.fields import SelectFieldRenderer
 | 
			
		||||
from webhelpers.html import literal
 | 
			
		||||
from webhelpers.html.tags import hidden, link_to, password
 | 
			
		||||
 | 
			
		||||
import edbob
 | 
			
		||||
from edbob.db.auth import set_user_password
 | 
			
		||||
from edbob.pyramid import filters
 | 
			
		||||
from edbob.pyramid import forms
 | 
			
		||||
from edbob.pyramid import grids
 | 
			
		||||
from edbob.pyramid import Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def filter_map():
 | 
			
		||||
    return filters.get_filter_map(
 | 
			
		||||
        edbob.User,
 | 
			
		||||
        ilike=['username'],
 | 
			
		||||
        person=filters.filter_ilike(edbob.Person.display_name))
 | 
			
		||||
 | 
			
		||||
def search_config(request, fmap):
 | 
			
		||||
    return filters.get_search_config(
 | 
			
		||||
        'users.list', request, fmap,
 | 
			
		||||
        include_filter_username=True,
 | 
			
		||||
        filter_type_username='lk')
 | 
			
		||||
 | 
			
		||||
def search_form(config):
 | 
			
		||||
    return filters.get_search_form(config)
 | 
			
		||||
 | 
			
		||||
def grid_config(request, search, fmap):
 | 
			
		||||
    return grids.get_grid_config(
 | 
			
		||||
        'users.list', request, search,
 | 
			
		||||
        filter_map=fmap, sort='username')
 | 
			
		||||
 | 
			
		||||
def sort_map():
 | 
			
		||||
    return grids.get_sort_map(
 | 
			
		||||
        edbob.User, ['username'],
 | 
			
		||||
        person=grids.sorter(edbob.Person.display_name))
 | 
			
		||||
 | 
			
		||||
def query(config):
 | 
			
		||||
    jmap = {'person': lambda q: q.outerjoin(edbob.Person)}
 | 
			
		||||
    smap = sort_map()
 | 
			
		||||
    q = Session.query(edbob.User)
 | 
			
		||||
    q = filters.filter_query(q, config, jmap)
 | 
			
		||||
    q = grids.sort_query(q, config, smap, jmap)
 | 
			
		||||
    return q
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def users(context, request):
 | 
			
		||||
 | 
			
		||||
    fmap = filter_map()
 | 
			
		||||
    config = search_config(request, fmap)
 | 
			
		||||
    search = search_form(config)
 | 
			
		||||
    config = grid_config(request, search, fmap)
 | 
			
		||||
    users = grids.get_pager(query, config)
 | 
			
		||||
 | 
			
		||||
    g = forms.AlchemyGrid(
 | 
			
		||||
        edbob.User, users, config,
 | 
			
		||||
        gridurl=request.route_url('users.list'),
 | 
			
		||||
        objurl='user.edit')
 | 
			
		||||
 | 
			
		||||
    g.configure(
 | 
			
		||||
        include=[
 | 
			
		||||
            g.username,
 | 
			
		||||
            g.person,
 | 
			
		||||
            ],
 | 
			
		||||
        readonly=True)
 | 
			
		||||
 | 
			
		||||
    grid = g.render(class_='clickable users')
 | 
			
		||||
    return grids.render_grid(request, grid, search)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _RolesFieldRenderer(SelectFieldRenderer):
 | 
			
		||||
 | 
			
		||||
    def render_readonly(self, **kwargs):
 | 
			
		||||
        roles = Session.query(edbob.Role)
 | 
			
		||||
        res = literal('<ul>')
 | 
			
		||||
        for uuid in self.value:
 | 
			
		||||
            role = roles.get(uuid)
 | 
			
		||||
            res += literal('<li>%s</li>' % (
 | 
			
		||||
                    link_to(role.name,
 | 
			
		||||
                            self.request.route_url('role.edit', uuid=role.uuid))))
 | 
			
		||||
        res += literal('</ul>')
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def RolesFieldRenderer(request):
 | 
			
		||||
    return type('RolesFieldRenderer', (_RolesFieldRenderer,), {'request': request})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RolesField(formalchemy.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):
 | 
			
		||||
        q = Session.query(edbob.Role.name, edbob.Role.uuid)
 | 
			
		||||
        q = q.order_by(edbob.Role.name)
 | 
			
		||||
        return q.all()
 | 
			
		||||
 | 
			
		||||
    def sync(self):
 | 
			
		||||
        if not self.is_readonly():
 | 
			
		||||
            user = self.model
 | 
			
		||||
            roles = Session.query(edbob.Role)
 | 
			
		||||
            data = self.renderer.deserialize()
 | 
			
		||||
            user.roles = [roles.get(x) for x in data]
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
class _ProtectedPersonRenderer(formalchemy.FieldRenderer):
 | 
			
		||||
 | 
			
		||||
    def render_readonly(self, **kwargs):
 | 
			
		||||
        res = str(self.person)
 | 
			
		||||
        res += hidden('User--person_uuid',
 | 
			
		||||
                      value=self.field.parent.person_uuid.value)
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ProtectedPersonRenderer(uuid):
 | 
			
		||||
    person = Session.query(edbob.Person).get(uuid)
 | 
			
		||||
    assert person
 | 
			
		||||
    return type('ProtectedPersonRenderer', (_ProtectedPersonRenderer,),
 | 
			
		||||
                {'person': person})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _LinkedPersonRenderer(formalchemy.FieldRenderer):
 | 
			
		||||
 | 
			
		||||
    def render_readonly(self, **kwargs):
 | 
			
		||||
        return link_to(str(self.raw_value),
 | 
			
		||||
                       self.request.route_url('person.edit', uuid=self.value))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def LinkedPersonRenderer(request):
 | 
			
		||||
    return type('LinkedPersonRenderer', (_LinkedPersonRenderer,), {'request': request})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer):
 | 
			
		||||
 | 
			
		||||
    def render(self, **kwargs):
 | 
			
		||||
        return password(self.name, value='', maxlength=self.length, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def passwords_match(value, field):
 | 
			
		||||
    if field.parent.confirm_password.value != value:
 | 
			
		||||
        raise formalchemy.ValidationError("Passwords do not match")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PasswordField(formalchemy.Field):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        kwargs.setdefault('value', lambda x: x.password)
 | 
			
		||||
        kwargs.setdefault('renderer', PasswordFieldRenderer)
 | 
			
		||||
        kwargs.setdefault('validate', passwords_match)
 | 
			
		||||
        super(PasswordField, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def sync(self):
 | 
			
		||||
        if not self.is_readonly():
 | 
			
		||||
            password = self.renderer.deserialize()
 | 
			
		||||
            if password:
 | 
			
		||||
                set_user_password(self.model, password)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def user_fieldset(user, request):
 | 
			
		||||
    fs = forms.make_fieldset(user, url=request.route_url,
 | 
			
		||||
                             url_action=request.current_route_url(),
 | 
			
		||||
                             route_name='users.list')
 | 
			
		||||
 | 
			
		||||
    fs.append(PasswordField('password'))
 | 
			
		||||
    fs.append(formalchemy.Field('confirm_password',
 | 
			
		||||
                                renderer=PasswordFieldRenderer))
 | 
			
		||||
 | 
			
		||||
    fs.append(RolesField(
 | 
			
		||||
            'roles', renderer=RolesFieldRenderer(request)))
 | 
			
		||||
 | 
			
		||||
    fs.configure(
 | 
			
		||||
        include=[
 | 
			
		||||
            fs.person,
 | 
			
		||||
            fs.username,
 | 
			
		||||
            fs.password.label("Set Password"),
 | 
			
		||||
            fs.confirm_password,
 | 
			
		||||
            fs.roles,
 | 
			
		||||
            ])
 | 
			
		||||
 | 
			
		||||
    if isinstance(user, edbob.User) and user.person:
 | 
			
		||||
        fs.person.set(readonly=True,
 | 
			
		||||
                      renderer=LinkedPersonRenderer(request))
 | 
			
		||||
 | 
			
		||||
    return fs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def new_user(request):
 | 
			
		||||
    """
 | 
			
		||||
    View for creating a new :class:`edbob.User` instance.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    fs = user_fieldset(edbob.User, request)
 | 
			
		||||
    if request.POST:
 | 
			
		||||
        fs.rebind(data=request.params)
 | 
			
		||||
        if fs.validate():
 | 
			
		||||
 | 
			
		||||
            with transaction.manager:
 | 
			
		||||
                Session.add(fs.model)
 | 
			
		||||
                fs.sync()
 | 
			
		||||
                request.session.flash("%s \"%s\" has been %s." % (
 | 
			
		||||
                        fs.crud_title, fs.get_display_text(),
 | 
			
		||||
                        'updated' if fs.edit else 'created'))
 | 
			
		||||
                home = request.route_url('users.list')
 | 
			
		||||
 | 
			
		||||
            return HTTPFound(location=home)
 | 
			
		||||
 | 
			
		||||
        if fs.person_uuid.value:
 | 
			
		||||
            fs.person.set(readonly=True,
 | 
			
		||||
                          renderer=ProtectedPersonRenderer(fs.person_uuid.value))
 | 
			
		||||
 | 
			
		||||
    return {'fieldset': fs, 'crud': True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_user(request):
 | 
			
		||||
    uuid = request.matchdict['uuid']
 | 
			
		||||
    user = Session.query(edbob.User).get(uuid) if uuid else None
 | 
			
		||||
    assert user
 | 
			
		||||
 | 
			
		||||
    fs = user_fieldset(user, request)
 | 
			
		||||
    if request.POST:
 | 
			
		||||
        fs.rebind(data=request.params)
 | 
			
		||||
        if fs.validate():
 | 
			
		||||
 | 
			
		||||
            with transaction.manager:
 | 
			
		||||
                Session.add(fs.model)
 | 
			
		||||
                fs.sync()
 | 
			
		||||
                request.session.flash("%s \"%s\" has been %s." % (
 | 
			
		||||
                        fs.crud_title, fs.get_display_text(),
 | 
			
		||||
                        'updated' if fs.edit else 'created'))
 | 
			
		||||
                home = request.route_url('users.list')
 | 
			
		||||
 | 
			
		||||
            return HTTPFound(location=home)
 | 
			
		||||
 | 
			
		||||
    return {'fieldset': fs, 'crud': True}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def includeme(config):
 | 
			
		||||
 | 
			
		||||
    config.add_route('users.list', '/users')
 | 
			
		||||
    config.add_view(users, route_name='users.list', renderer='/users/index.mako',
 | 
			
		||||
                    permission='users.list', http_cache=0)
 | 
			
		||||
 | 
			
		||||
    config.add_route('user.new', '/users/new')
 | 
			
		||||
    config.add_view(new_user, route_name='user.new', renderer='/users/user.mako',
 | 
			
		||||
                    permission='users.create', http_cache=0)
 | 
			
		||||
 | 
			
		||||
    config.add_route('user.edit', '/users/{uuid}/edit')
 | 
			
		||||
    config.add_view(edit_user, route_name='user.edit', renderer='/users/user.mako',
 | 
			
		||||
                    permission='users.edit', http_cache=0)
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue