Refactor user and role views to use master3
This commit is contained in:
		
							parent
							
								
									789bdef190
								
							
						
					
					
						commit
						86cfc59d33
					
				
					 8 changed files with 304 additions and 209 deletions
				
			
		|  | @ -81,12 +81,6 @@ div.error-messages div.ui-state-error { | ||||||
|     margin: 0.5em 0 0 0; |     margin: 0.5em 0 0 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.error { |  | ||||||
|     color: #dd6666; |  | ||||||
|     font-weight: bold; |  | ||||||
|     margin-bottom: 10px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ul.error { | ul.error { | ||||||
|     color: #dd6666; |     color: #dd6666; | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|  |  | ||||||
|  | @ -45,47 +45,54 @@ div.fieldset { | ||||||
|  * Fieldsets |  * Fieldsets | ||||||
|  ******************************/ |  ******************************/ | ||||||
| 
 | 
 | ||||||
| div.field-wrapper { | .field-wrapper { | ||||||
|     clear: both; |     clear: both; | ||||||
|     min-height: 30px; |     min-height: 30px; | ||||||
|     overflow: auto; |     overflow: auto; | ||||||
|     padding: 5px; |     margin: 15px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.error { | .field-wrapper.error { | ||||||
|     background-color: #ddcccc; |     background-color: #ddcccc; | ||||||
|     border: 2px solid #dd6666; |     border: 2px solid #dd6666; | ||||||
|  |     padding-bottom: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper label { | .field-wrapper .field-row { | ||||||
|     display: block; |     display: table-row; | ||||||
|     float: left; | } | ||||||
|  | 
 | ||||||
|  | .field-wrapper label { | ||||||
|  |     display: table-cell; | ||||||
|  |     vertical-align: top; | ||||||
|     width: 15em; |     width: 15em; | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     margin-top: 2px; |     padding-top: 2px; | ||||||
|     white-space: nowrap; |     white-space: nowrap; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .field-wrapper.error label { | .field-wrapper.error label { | ||||||
|     color: Black; |     padding-left: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper div.field-error { | .field-wrapper .field-error { | ||||||
|  |     padding: 1em 0 0.5em 1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .field-wrapper .field-error .error-msg { | ||||||
|     color: #dd6666; |     color: #dd6666; | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper div.field { | .field-wrapper .field { | ||||||
|     display: block; |     display: table-cell; | ||||||
|     float: left; |  | ||||||
|     margin-bottom: 5px; |  | ||||||
|     line-height: 25px; |     line-height: 25px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper div.field input[type=text], | .field-wrapper .field input[type=text], | ||||||
| div.field-wrapper div.field input[type=password], | .field-wrapper .field input[type=password], | ||||||
| div.field-wrapper div.field select, | .field-wrapper .field select, | ||||||
| div.field-wrapper div.field textarea { | .field-wrapper .field textarea { | ||||||
|     width: 320px; |     width: 320px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -94,7 +101,7 @@ label input[type="radio"] { | ||||||
|     margin-right: 0.5em; |     margin-right: 0.5em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field ul { | .field ul { | ||||||
|     margin: 0px; |     margin: 0px; | ||||||
|     padding-left: 15px; |     padding-left: 15px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,34 +3,30 @@ | ||||||
|  * Permission Lists |  * Permission Lists | ||||||
|  ******************************/ |  ******************************/ | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.permissions { | .field-wrapper.permissions .field .group { | ||||||
|     overflow: visible; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div.field-wrapper.permissions div.field div.group { |  | ||||||
|     margin-bottom: 10px; |     margin-bottom: 10px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.permissions div.field div.group p { | .field-wrapper.permissions .field .group p { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.permissions div.field label { | .field-wrapper.permissions .field label { | ||||||
|     float: none; |     display: inline; | ||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.permissions div.field label input { | .field-wrapper.permissions .field label input { | ||||||
|     margin-left: 15px; |     margin-left: 15px; | ||||||
|     margin-right: 10px; |     margin-right: 10px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.permissions div.field div.group p.perm { | .field-wrapper.permissions .field .group p.perm { | ||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     margin-left: 15px; |     margin-left: 15px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| div.field-wrapper.permissions div.field div.group p.perm span { | .field-wrapper.permissions .field .group p.perm span { | ||||||
|     font-family: monospace; |     font-family: monospace; | ||||||
|     margin-right: 10px; |     margin-right: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										28
									
								
								tailbone/templates/deform/permissions.pt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								tailbone/templates/deform/permissions.pt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | <div tal:define="oid oid|field.oid; | ||||||
|  |                  true_val true_val|field.widget.true_val;" | ||||||
|  |      tal:omit-tag=""> | ||||||
|  |   ${field.start_mapping()} | ||||||
|  | 
 | ||||||
|  |   <tal:loop tal:repeat="groupkey sorted(permissions, key=lambda k: permissions[k]['label'].lower())"> | ||||||
|  |     <div tal:define="perms permissions[groupkey]['perms'];" | ||||||
|  |          class="group"> | ||||||
|  |       <p class="group">${permissions[groupkey]['label']}</p> | ||||||
|  | 
 | ||||||
|  |       <tal:loop tal:repeat="key sorted(perms, key=lambda p: perms[p]['label'].lower())"> | ||||||
|  |         <div class="perm"> | ||||||
|  |           <label> | ||||||
|  |             <input type="checkbox" | ||||||
|  |                    name="${key}" | ||||||
|  |                    id="${oid}-${key}" | ||||||
|  |                    value="${true_val}" | ||||||
|  |                    tal:attributes="checked python:field.widget.get_checked_value(cstruct, key);" /> | ||||||
|  |             ${perms[key]['label']} | ||||||
|  |           </label> | ||||||
|  |         </div> | ||||||
|  |       </tal:loop> | ||||||
|  | 
 | ||||||
|  |     </div> | ||||||
|  |   </tal:loop> | ||||||
|  | 
 | ||||||
|  |   ${field.end_mapping()} | ||||||
|  | </div> | ||||||
|  | @ -26,11 +26,13 @@ ${h.csrf_token(request)} | ||||||
|     ##                 <div class="field-error">${error}</div> |     ##                 <div class="field-error">${error}</div> | ||||||
|     ##             % endfor |     ##             % endfor | ||||||
|               % if field.error: |               % if field.error: | ||||||
|                   <div class="field-error">${field.error.msg}</div> |                   <div class="field-error"><span class="error-msg">${field.error.msg}</span></div> | ||||||
|               % endif |               % endif | ||||||
|               <label for="${field.oid}">${field.title}</label> |               <div class="field-row"> | ||||||
|               <div class="field"> |                 <label for="${field.oid}">${field.title}</label> | ||||||
|                 ${field.serialize()|n} |                 <div class="field"> | ||||||
|  |                   ${field.serialize()|n} | ||||||
|  |                 </div> | ||||||
|               </div> |               </div> | ||||||
|               % if form.has_helptext(field.name): |               % if form.has_helptext(field.name): | ||||||
|                   <span class="instructions">${form.render_helptext(field.name)}</span> |                   <span class="instructions">${form.render_helptext(field.name)}</span> | ||||||
|  |  | ||||||
|  | @ -28,9 +28,14 @@ from __future__ import unicode_literals, absolute_import | ||||||
| 
 | 
 | ||||||
| import copy | import copy | ||||||
| 
 | 
 | ||||||
| import wtforms | from rattail.db.auth import has_permission | ||||||
|  | from rattail.core import Object | ||||||
| 
 | 
 | ||||||
| from tailbone.views import MasterView2 as MasterView | import wtforms | ||||||
|  | from webhelpers2.html import HTML | ||||||
|  | 
 | ||||||
|  | from tailbone.db import Session | ||||||
|  | from tailbone.views import MasterView3 as MasterView | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PrincipalMasterView(MasterView): | class PrincipalMasterView(MasterView): | ||||||
|  | @ -95,3 +100,33 @@ class PrincipalMasterView(MasterView): | ||||||
|                         permission='{}.find_by_perm'.format(permission_prefix)) |                         permission='{}.find_by_perm'.format(permission_prefix)) | ||||||
|         config.add_tailbone_permission(permission_prefix, '{}.find_by_perm'.format(permission_prefix), |         config.add_tailbone_permission(permission_prefix, '{}.find_by_perm'.format(permission_prefix), | ||||||
|                                        "Find all {} with permission X".format(model_title_plural)) |                                        "Find all {} with permission X".format(model_title_plural)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PermissionsRenderer(Object): | ||||||
|  |     permissions = None | ||||||
|  |     include_guest = False | ||||||
|  |     include_authenticated = False | ||||||
|  | 
 | ||||||
|  |     def __call__(self, principal, field): | ||||||
|  |         self.principal = principal | ||||||
|  |         return self.render() | ||||||
|  | 
 | ||||||
|  |     def render(self): | ||||||
|  |         principal = self.principal | ||||||
|  |         html = '' | ||||||
|  |         for groupkey in sorted(self.permissions, key=lambda k: self.permissions[k]['label'].lower()): | ||||||
|  |             inner = HTML.tag('p', c=self.permissions[groupkey]['label']) | ||||||
|  |             perms = self.permissions[groupkey]['perms'] | ||||||
|  |             rendered = False | ||||||
|  |             for key in sorted(perms, key=lambda p: perms[p]['label'].lower()): | ||||||
|  |                 checked = has_permission(Session(), principal, key, | ||||||
|  |                                          include_guest=self.include_guest, | ||||||
|  |                                          include_authenticated=self.include_authenticated) | ||||||
|  |                 if checked: | ||||||
|  |                     label = perms[key]['label'] | ||||||
|  |                     span = HTML.tag('span', c="[X]" if checked else "[ ]") | ||||||
|  |                     inner += HTML.tag('p', class_='perm', c=span + ' ' + label) | ||||||
|  |                     rendered = True | ||||||
|  |             if rendered: | ||||||
|  |                 html += HTML.tag('div', class_='group', c=inner) | ||||||
|  |         return html or "(none granted)" | ||||||
|  |  | ||||||
|  | @ -26,17 +26,18 @@ Role Views | ||||||
| 
 | 
 | ||||||
| from __future__ import unicode_literals, absolute_import | from __future__ import unicode_literals, absolute_import | ||||||
| 
 | 
 | ||||||
|  | import six | ||||||
| from sqlalchemy import orm | from sqlalchemy import orm | ||||||
| 
 | 
 | ||||||
| from rattail.db import model | from rattail.db import model | ||||||
| from rattail.db.auth import has_permission, administrator_role, guest_role, authenticated_role | from rattail.db.auth import has_permission, administrator_role, guest_role, authenticated_role | ||||||
| 
 | 
 | ||||||
| import formalchemy as fa | import colander | ||||||
| from formalchemy.fields import IntegerFieldRenderer | from deform import widget as dfwidget | ||||||
| 
 | 
 | ||||||
| from tailbone import forms, grids | from tailbone import grids | ||||||
| from tailbone.db import Session | from tailbone.db import Session | ||||||
| from tailbone.views.principal import PrincipalMasterView | from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RolesView(PrincipalMasterView): | class RolesView(PrincipalMasterView): | ||||||
|  | @ -51,6 +52,12 @@ class RolesView(PrincipalMasterView): | ||||||
|         'session_timeout', |         'session_timeout', | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|  |     form_fields = [ | ||||||
|  |         'name', | ||||||
|  |         'session_timeout', | ||||||
|  |         'permissions', | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|     def configure_grid(self, g): |     def configure_grid(self, g): | ||||||
|         super(RolesView, self).configure_grid(g) |         super(RolesView, self).configure_grid(g) | ||||||
|         g.filters['name'].default_active = True |         g.filters['name'].default_active = True | ||||||
|  | @ -58,21 +65,39 @@ class RolesView(PrincipalMasterView): | ||||||
|         g.set_sort_defaults('name') |         g.set_sort_defaults('name') | ||||||
|         g.set_link('name') |         g.set_link('name') | ||||||
| 
 | 
 | ||||||
|     def _preconfigure_fieldset(self, fs): |     def configure_form(self, f): | ||||||
|         fs.append(PermissionsField('permissions')) |         super(RolesView, self).configure_form(f) | ||||||
|         permissions = self.request.registry.settings.get('tailbone_permissions', {}) |         role = f.model_instance | ||||||
|         fs.permissions.set(renderer=forms.renderers.PermissionsFieldRenderer(permissions)) |  | ||||||
|         fs.session_timeout.set(renderer=SessionTimeoutRenderer) |  | ||||||
|         if (self.viewing or self.editing) and fs.model is guest_role(self.Session()): |  | ||||||
|             fs.session_timeout.set(readonly=True, attrs={'applicable': False}) |  | ||||||
| 
 | 
 | ||||||
|     def configure_fieldset(self, fs): |         # permissions | ||||||
|         fs.configure( |         self.tailbone_permissions = self.request.registry.settings.get('tailbone_permissions', {}) | ||||||
|             include=[ |         f.set_renderer('permissions', PermissionsRenderer(permissions=self.tailbone_permissions)) | ||||||
|                 fs.name, |         f.set_node('permissions', colander.Set()) | ||||||
|                 fs.session_timeout, |         f.set_widget('permissions', PermissionsWidget(permissions=self.tailbone_permissions)) | ||||||
|                 fs.permissions, |         if self.editing: | ||||||
|             ]) |             granted = [] | ||||||
|  |             for groupkey in self.tailbone_permissions: | ||||||
|  |                 for key in self.tailbone_permissions[groupkey]['perms']: | ||||||
|  |                     if has_permission(self.Session(), role, key, include_guest=False, include_authenticated=False): | ||||||
|  |                         granted.append(key) | ||||||
|  |             f.set_default('permissions', granted) | ||||||
|  | 
 | ||||||
|  |         # session_timeout | ||||||
|  |         f.set_renderer('session_timeout', self.render_session_timeout) | ||||||
|  |         if self.editing and role is guest_role(self.Session()): | ||||||
|  |             f.set_readonly('session_timeout') | ||||||
|  | 
 | ||||||
|  |     def render_session_timeout(self, role, field): | ||||||
|  |         if role is guest_role(self.Session()): | ||||||
|  |             return "(not applicable)" | ||||||
|  |         if role.session_timeout is None: | ||||||
|  |             return "" | ||||||
|  |         return six.text_type(role.session_timeout) | ||||||
|  | 
 | ||||||
|  |     def objectify(self, form, data): | ||||||
|  |         role = super(RolesView, self).objectify(form, data) | ||||||
|  |         role.permissions = data['permissions'] | ||||||
|  |         return role | ||||||
| 
 | 
 | ||||||
|     def template_kwargs_view(self, **kwargs): |     def template_kwargs_view(self, **kwargs): | ||||||
|         role = kwargs['instance'] |         role = kwargs['instance'] | ||||||
|  | @ -86,14 +111,14 @@ class RolesView(PrincipalMasterView): | ||||||
|                                          main_actions=actions) |                                          main_actions=actions) | ||||||
|         else: |         else: | ||||||
|             kwargs['users'] = None |             kwargs['users'] = None | ||||||
|         kwargs['guest_role'] = guest_role(Session()) |         kwargs['guest_role'] = guest_role(self.Session()) | ||||||
|         kwargs['authenticated_role'] = authenticated_role(Session()) |         kwargs['authenticated_role'] = authenticated_role(self.Session()) | ||||||
|         return kwargs |         return kwargs | ||||||
| 
 | 
 | ||||||
|     def before_delete(self, role): |     def before_delete(self, role): | ||||||
|         admin = administrator_role(Session()) |         admin = administrator_role(self.Session()) | ||||||
|         guest = guest_role(Session()) |         guest = guest_role(self.Session()) | ||||||
|         authenticated = authenticated_role(Session()) |         authenticated = authenticated_role(self.Session()) | ||||||
|         if role in (admin, guest, authenticated): |         if role in (admin, guest, authenticated): | ||||||
|             self.request.session.flash("You may not delete the {} role.".format(role.name), 'error') |             self.request.session.flash("You may not delete the {} role.".format(role.name), 'error') | ||||||
|             return self.redirect(self.request.get_referrer(default=self.request.route_url('roles'))) |             return self.redirect(self.request.get_referrer(default=self.request.route_url('roles'))) | ||||||
|  | @ -110,23 +135,25 @@ class RolesView(PrincipalMasterView): | ||||||
|         return roles |         return roles | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SessionTimeoutRenderer(IntegerFieldRenderer): | class PermissionsWidget(dfwidget.Widget): | ||||||
|  |     template = 'permissions' | ||||||
|  |     permissions = None | ||||||
|  |     true_val = 'true' | ||||||
| 
 | 
 | ||||||
|     def render_readonly(self, **kwargs): |     def deserialize(self, field, pstruct): | ||||||
|         if not kwargs.pop('applicable', True): |         return [key for key, val in pstruct.items() | ||||||
|             return "(not applicable)" |                 if val == self.true_val] | ||||||
|         return super(SessionTimeoutRenderer, self).render_readonly(**kwargs) |  | ||||||
| 
 | 
 | ||||||
|  |     def get_checked_value(self, cstruct, value): | ||||||
|  |         if cstruct is colander.null: | ||||||
|  |             return False | ||||||
|  |         return value in cstruct | ||||||
| 
 | 
 | ||||||
| class PermissionsField(fa.Field): |     def serialize(self, field, cstruct, **kw): | ||||||
|     """ |         kw.setdefault('permissions', self.permissions) | ||||||
|     Custom field for role permissions. |         template = self.template | ||||||
|     """ |         values = self.get_template_values(field, cstruct, kw) | ||||||
| 
 |         return field.renderer(template, **values) | ||||||
|     def sync(self): |  | ||||||
|         if not self.is_readonly(): |  | ||||||
|             role = self.model |  | ||||||
|             role.permissions = self.renderer.deserialize() |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def includeme(config): | def includeme(config): | ||||||
|  |  | ||||||
|  | @ -28,100 +28,20 @@ from __future__ import unicode_literals, absolute_import | ||||||
| 
 | 
 | ||||||
| import copy | import copy | ||||||
| 
 | 
 | ||||||
|  | import six | ||||||
| from sqlalchemy import orm | from sqlalchemy import orm | ||||||
| 
 | 
 | ||||||
| from rattail.db import model | from rattail.db import model | ||||||
| from rattail.db.auth import guest_role, authenticated_role, set_user_password, has_permission | from rattail.db.auth import guest_role, authenticated_role, set_user_password, has_permission | ||||||
| 
 | 
 | ||||||
| import wtforms | import colander | ||||||
| import formalchemy | from deform import widget as dfwidget | ||||||
| from formalchemy.fields import SelectFieldRenderer |  | ||||||
| from webhelpers2.html import HTML, tags | from webhelpers2.html import HTML, tags | ||||||
| 
 | 
 | ||||||
| from tailbone import forms | from tailbone import forms2 as forms | ||||||
| from tailbone.db import Session | from tailbone.db import Session | ||||||
| from tailbone.views import MasterView2 as MasterView | from tailbone.views import MasterView3 as MasterView | ||||||
| from tailbone.views.principal import PrincipalMasterView | from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def unique_username(value, field): |  | ||||||
|     user = field.parent.model |  | ||||||
|     query = Session.query(model.User).filter(model.User.username == value) |  | ||||||
|     if user.uuid: |  | ||||||
|         query = query.filter(model.User.uuid != user.uuid) |  | ||||||
|     if query.count(): |  | ||||||
|         raise formalchemy.ValidationError("Username must be unique.") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def passwords_match(value, field): |  | ||||||
|     if field.parent.confirm_password.value != value: |  | ||||||
|         raise formalchemy.ValidationError("Passwords do not match") |  | ||||||
|     return value |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer): |  | ||||||
| 
 |  | ||||||
|     def render(self, **kwargs): |  | ||||||
|         return tags.password(self.name, value='', maxlength=self.length, **kwargs) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 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 RolesFieldRenderer(request): |  | ||||||
| 
 |  | ||||||
|     class RolesFieldRenderer(SelectFieldRenderer): |  | ||||||
| 
 |  | ||||||
|         def render_readonly(self, **kwargs): |  | ||||||
|             roles = Session.query(model.Role) |  | ||||||
|             html = '' |  | ||||||
|             for uuid in self.value: |  | ||||||
|                 role = roles.get(uuid) |  | ||||||
|                 link = tags.link_to( |  | ||||||
|                     role.name, request.route_url('roles.view', uuid=role.uuid)) |  | ||||||
|                 html += HTML.tag('li', c=link) |  | ||||||
|             html = HTML.tag('ul', c=html) |  | ||||||
|             return html |  | ||||||
| 
 |  | ||||||
|     return RolesFieldRenderer |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 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): |  | ||||||
|         return Session.query(model.Role.name, model.Role.uuid)\ |  | ||||||
|             .filter(model.Role.uuid != guest_role(Session()).uuid)\ |  | ||||||
|             .filter(model.Role.uuid != authenticated_role(Session()).uuid)\ |  | ||||||
|             .order_by(model.Role.name)\ |  | ||||||
|             .all() |  | ||||||
| 
 |  | ||||||
|     def sync(self): |  | ||||||
|         if not self.is_readonly(): |  | ||||||
|             user = self.model |  | ||||||
|             roles = Session.query(model.Role) |  | ||||||
|             data = self.renderer.deserialize() |  | ||||||
|             user.roles = [roles.get(x) for x in data] |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class UsersView(PrincipalMasterView): | class UsersView(PrincipalMasterView): | ||||||
|  | @ -133,6 +53,29 @@ class UsersView(PrincipalMasterView): | ||||||
|     model_row_class = model.UserEvent |     model_row_class = model.UserEvent | ||||||
|     has_versions = True |     has_versions = True | ||||||
| 
 | 
 | ||||||
|  |     grid_columns = [ | ||||||
|  |         'username', | ||||||
|  |         'person', | ||||||
|  |         'active', | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     form_fields = [ | ||||||
|  |         'username', | ||||||
|  |         'person', | ||||||
|  |         'first_name', | ||||||
|  |         'last_name', | ||||||
|  |         'display_name', | ||||||
|  |         'active', | ||||||
|  |         'active_sticky', | ||||||
|  |         'password', | ||||||
|  |         'roles', | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     row_grid_columns = [ | ||||||
|  |         'type_code', | ||||||
|  |         'occurred', | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|     mergeable = True |     mergeable = True | ||||||
|     merge_additive_fields = [ |     merge_additive_fields = [ | ||||||
|         'sent_message_count', |         'sent_message_count', | ||||||
|  | @ -147,17 +90,6 @@ class UsersView(PrincipalMasterView): | ||||||
|         'active', |         'active', | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     grid_columns = [ |  | ||||||
|         'username', |  | ||||||
|         'person', |  | ||||||
|         'active', |  | ||||||
|     ] |  | ||||||
| 
 |  | ||||||
|     row_grid_columns = [ |  | ||||||
|         'type_code', |  | ||||||
|         'occurred', |  | ||||||
|     ] |  | ||||||
| 
 |  | ||||||
|     def query(self, session): |     def query(self, session): | ||||||
|         return session.query(model.User)\ |         return session.query(model.User)\ | ||||||
|                       .outerjoin(model.Person)\ |                       .outerjoin(model.Person)\ | ||||||
|  | @ -191,45 +123,119 @@ class UsersView(PrincipalMasterView): | ||||||
|         g.set_link('last_name') |         g.set_link('last_name') | ||||||
|         g.set_link('display_name') |         g.set_link('display_name') | ||||||
| 
 | 
 | ||||||
|     def _preconfigure_fieldset(self, fs): |     def unique_username(self, node, value): | ||||||
|         fs.username.set(renderer=forms.renderers.StrippedTextFieldRenderer, validate=unique_username) |         query = self.Session.query(model.User)\ | ||||||
|         fs.person.set(renderer=forms.renderers.PersonFieldRenderer, options=[]) |                             .filter(model.User.username == value) | ||||||
|         fs.append(PasswordField('password', label="Set Password")) |         if self.editing: | ||||||
|         fs.append(formalchemy.Field('confirm_password', renderer=PasswordFieldRenderer)) |             user = self.get_instance() | ||||||
|         fs.append(RolesField('roles', renderer=RolesFieldRenderer(self.request), size=10)) |             query = query.filter(model.User.uuid != user.uuid) | ||||||
|         fs.append(forms.AssociationProxyField('first_name')) |         if query.count(): | ||||||
|         fs.append(forms.AssociationProxyField('last_name')) |             raise colander.Invalid(node, "Username must be unique") | ||||||
|         fs.append(forms.AssociationProxyField('display_name', label="Full Name")) |  | ||||||
| 
 | 
 | ||||||
|         # hm this should work according to MDN but doesn't seem to... |     def configure_form(self, f): | ||||||
|         # https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion |         super(UsersView, self).configure_form(f) | ||||||
|         fs.username.attrs(autocomplete='new-password') |         user = f.model_instance | ||||||
|         fs.password.attrs(autocomplete='new-password') | 
 | ||||||
|         fs.confirm_password.attrs(autocomplete='new-password') |         # username | ||||||
|  |         f.set_validator('username', self.unique_username) | ||||||
|  | 
 | ||||||
|  |         # person | ||||||
|  |         f.set_renderer('person', self.render_person) | ||||||
|  |         if self.creating or self.editing: | ||||||
|  |             f.replace('person', 'person_uuid') | ||||||
|  |             f.set_node('person_uuid', colander.String(), missing=colander.null) | ||||||
|  |             person_display = "" | ||||||
|  |             if self.request.method == 'POST': | ||||||
|  |                 if self.request.POST.get('person_uuid'): | ||||||
|  |                     person = self.Session.query(model.Person).get(self.request.POST['person_uuid']) | ||||||
|  |                     if person: | ||||||
|  |                         person_display = six.text_type(person) | ||||||
|  |             elif self.editing: | ||||||
|  |                 person_display = six.text_type(user.person or '') | ||||||
|  |             people_url = self.request.route_url('people.autocomplete') | ||||||
|  |             f.set_widget('person_uuid', forms.widgets.JQueryAutocompleteWidget( | ||||||
|  |                 field_display=person_display, service_url=people_url)) | ||||||
|  |             f.set_label('person_uuid', "Person") | ||||||
|  | 
 | ||||||
|  |         # password | ||||||
|  |         f.set_widget('password', dfwidget.CheckedPasswordWidget()) | ||||||
|  |         f.set_label('password', "Set Password") | ||||||
|  |         # if self.creating: | ||||||
|  |         #     f.set_required('password') | ||||||
|  | 
 | ||||||
|  |         # roles | ||||||
|  |         f.set_renderer('roles', self.render_roles) | ||||||
|  |         if self.creating or self.editing: | ||||||
|  |             roles = self.get_possible_roles().all() | ||||||
|  |             role_values = [(s.uuid, six.text_type(s)) for s in roles] | ||||||
|  |             f.set_node('roles', colander.Set()) | ||||||
|  |             f.set_widget('roles', dfwidget.SelectWidget(multiple=True, | ||||||
|  |                                                         size=len(roles), | ||||||
|  |                                                         values=role_values)) | ||||||
|  |             if self.editing: | ||||||
|  |                 f.set_default('roles', [r.uuid for r in user.roles]) | ||||||
|  | 
 | ||||||
|  |         f.set_label('display_name', "Full Name") | ||||||
|  | 
 | ||||||
|  |         # # hm this should work according to MDN but doesn't seem to... | ||||||
|  |         # # https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion | ||||||
|  |         # fs.username.attrs(autocomplete='new-password') | ||||||
|  |         # fs.password.attrs(autocomplete='new-password') | ||||||
|  |         # fs.confirm_password.attrs(autocomplete='new-password') | ||||||
| 
 | 
 | ||||||
|     def configure_fieldset(self, fs): |  | ||||||
|         fs.configure( |  | ||||||
|             include=[ |  | ||||||
|                 fs.username, |  | ||||||
|                 fs.person, |  | ||||||
|                 fs.first_name, |  | ||||||
|                 fs.last_name, |  | ||||||
|                 fs.display_name, |  | ||||||
|                 fs.active, |  | ||||||
|                 fs.active_sticky, |  | ||||||
|                 fs.password, |  | ||||||
|                 fs.confirm_password, |  | ||||||
|                 fs.roles, |  | ||||||
|             ]) |  | ||||||
|         if self.viewing: |         if self.viewing: | ||||||
|             permissions = self.request.registry.settings.get('tailbone_permissions', {}) |             permissions = self.request.registry.settings.get('tailbone_permissions', {}) | ||||||
|             renderer = forms.renderers.PermissionsFieldRenderer(permissions, |             f.append('permissions') | ||||||
|                                                                 include_guest=True, |             f.set_renderer('permissions', PermissionsRenderer(permissions=permissions, | ||||||
|                                                                 include_authenticated=True) |                                                               include_guest=True, | ||||||
|             fs.append(formalchemy.Field('permissions', renderer=renderer)) |                                                               include_authenticated=True)) | ||||||
|  | 
 | ||||||
|         if self.viewing or self.deleting: |         if self.viewing or self.deleting: | ||||||
|             del fs.password |             f.remove('password') | ||||||
|             del fs.confirm_password | 
 | ||||||
|  |     def get_possible_roles(self): | ||||||
|  |         excluded = [ | ||||||
|  |             guest_role(self.Session()).uuid, | ||||||
|  |             authenticated_role(self.Session()).uuid, | ||||||
|  |         ] | ||||||
|  |         return self.Session.query(model.Role)\ | ||||||
|  |                            .filter(~model.Role.uuid.in_(excluded))\ | ||||||
|  |                            .order_by(model.Role.name) | ||||||
|  | 
 | ||||||
|  |     def objectify(self, form, data): | ||||||
|  |         user = super(UsersView, self).objectify(form, data) | ||||||
|  |         if data['password']: | ||||||
|  |             set_user_password(user, data['password']) | ||||||
|  |         self.update_roles(user, data) | ||||||
|  |         return user | ||||||
|  | 
 | ||||||
|  |     def update_roles(self, user, data): | ||||||
|  |         old_roles = set([r.uuid for r in user.roles]) | ||||||
|  |         new_roles = data['roles'] | ||||||
|  |         for uuid in new_roles: | ||||||
|  |             if uuid not in old_roles: | ||||||
|  |                 user._roles.append(model.UserRole(role_uuid=uuid)) | ||||||
|  |         for uuid in old_roles: | ||||||
|  |             if uuid not in new_roles: | ||||||
|  |                 role = self.Session.query(model.Role).get(uuid) | ||||||
|  |                 user.roles.remove(role) | ||||||
|  | 
 | ||||||
|  |     def render_person(self, user, field): | ||||||
|  |         person = user.person | ||||||
|  |         if not person: | ||||||
|  |             return "" | ||||||
|  |         text = six.text_type(person) | ||||||
|  |         url = self.request.route_url('people.view', uuid=person.uuid) | ||||||
|  |         return tags.link_to(person, url) | ||||||
|  | 
 | ||||||
|  |     def render_roles(self, user, field): | ||||||
|  |         roles = user.roles | ||||||
|  |         items = [] | ||||||
|  |         for role in roles: | ||||||
|  |             text = role.name | ||||||
|  |             url = self.request.route_url('roles.view', uuid=role.uuid) | ||||||
|  |             items.append(HTML.tag('li', c=tags.link_to(text, url))) | ||||||
|  |         return HTML.tag('ul', c=items) | ||||||
| 
 | 
 | ||||||
|     def editable_instance(self, user): |     def editable_instance(self, user): | ||||||
|         if self.rattail_config.demo(): |         if self.rattail_config.demo(): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar