initial users/roles/perms admin stuff

This commit is contained in:
Lance Edgar 2012-04-21 12:45:58 -05:00
parent 02d4cfc6c5
commit ba94a015a6
24 changed files with 1061 additions and 110 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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;
}

View file

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

View file

@ -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('')}

View file

@ -0,0 +1,2 @@
<%inherit file="/base.mako" />
${parent.body()}

View 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()}

View 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

View file

@ -0,0 +1,2 @@
<%inherit file="/base.mako" />
${parent.body()}

View 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()}

View 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()}

View file

@ -0,0 +1,2 @@
<%inherit file="/base.mako" />
${parent.body()}

View 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()}

View 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()}

View file

@ -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("&nbsp; (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')

View file

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

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

View 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 '[&nbsp; ]') + '</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)

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