initial users/roles/perms admin stuff
This commit is contained in:
parent
02d4cfc6c5
commit
ba94a015a6
24 changed files with 1061 additions and 110 deletions
|
@ -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