diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6de4999..d8c5635 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,25 +5,6 @@ All notable changes to wuttaweb will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-## v0.7.0 (2024-08-15)
-
-### Feat
-
-- add sane views for 403 Forbidden and 404 Not Found
-- add permission checks for menus, view routes
-- add first-time setup page to create admin user
-- expose User password for editing in master views
-- expose Role permissions for editing
-- expose User "roles" for editing
-- improve widget, rendering for Role notes
-
-### Fix
-
-- add stub for `PersonView.make_user()`
-- allow arbitrary kwargs for `Form.render_vue_field()`
-- make some tweaks for better tailbone compatibility
-- prevent delete for built-in roles
-
## v0.6.0 (2024-08-13)
### Feat
diff --git a/pyproject.toml b/pyproject.toml
index 35d94ff..c3ce9d4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project]
name = "WuttaWeb"
-version = "0.7.0"
+version = "0.6.0"
description = "Web App for Wutta Framework"
readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
@@ -39,7 +39,7 @@ dependencies = [
"pyramid_tm",
"waitress",
"WebHelpers2",
- "WuttJamaican[db]>=0.11.1",
+ "WuttJamaican[db]>=0.11.0",
"zope.sqlalchemy>=1.5",
]
diff --git a/src/wuttaweb/app.py b/src/wuttaweb/app.py
index 845b41f..bafc921 100644
--- a/src/wuttaweb/app.py
+++ b/src/wuttaweb/app.py
@@ -135,12 +135,6 @@ def make_pyramid_config(settings):
pyramid_config.include('pyramid_mako')
pyramid_config.include('pyramid_tm')
- # add some permissions magic
- pyramid_config.add_directive('add_wutta_permission_group',
- 'wuttaweb.auth.add_permission_group')
- pyramid_config.add_directive('add_wutta_permission',
- 'wuttaweb.auth.add_permission')
-
return pyramid_config
diff --git a/src/wuttaweb/auth.py b/src/wuttaweb/auth.py
index 88b1fea..de9b868 100644
--- a/src/wuttaweb/auth.py
+++ b/src/wuttaweb/auth.py
@@ -148,93 +148,3 @@ class WuttaSecurityPolicy:
auth = app.get_auth_handler()
user = self.identity(request)
return auth.has_permission(self.db_session, user, permission)
-
-
-def add_permission_group(pyramid_config, key, label=None, overwrite=True):
- """
- Pyramid directive to add a "permission group" to the app's
- awareness.
-
- The app must be made aware of all permissions, so they are exposed
- when editing a
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role`. The logic
- for discovering permissions is in
- :meth:`~wuttaweb.views.roles.RoleView.get_available_permissions()`.
-
- This is usually called from within a master view's
- :meth:`~wuttaweb.views.master.MasterView.defaults()` to establish
- the permission group which applies to the view model.
-
- A simple example of usage::
-
- pyramid_config.add_permission_group('widgets', label="Widgets")
-
- :param key: Unique key for the permission group. In the context
- of a master view, this will be the same as
- :attr:`~wuttaweb.views.master.MasterView.permission_prefix`.
-
- :param label: Optional label for the permission group. If not
- specified, it is derived from ``key``.
-
- :param overwrite: If the permission group was already established,
- this flag controls whether the group's label should be
- overwritten (with ``label``).
-
- See also :func:`add_permission()`.
- """
- config = pyramid_config.get_settings()['wutta_config']
- app = config.get_app()
- def action():
- perms = pyramid_config.get_settings().get('wutta_permissions', {})
- if overwrite or key not in perms:
- group = perms.setdefault(key, {'key': key})
- group['label'] = label or app.make_title(key)
- pyramid_config.add_settings({'wutta_permissions': perms})
- pyramid_config.action(None, action)
-
-
-def add_permission(pyramid_config, groupkey, key, label=None):
- """
- Pyramid directive to add a single "permission" to the app's
- awareness.
-
- The app must be made aware of all permissions, so they are exposed
- when editing a
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role`. The logic
- for discovering permissions is in
- :meth:`~wuttaweb.views.roles.RoleView.get_available_permissions()`.
-
- This is usually called from within a master view's
- :meth:`~wuttaweb.views.master.MasterView.defaults()` to establish
- "known" permissions based on master view feature flags
- (:attr:`~wuttaweb.views.master.MasterView.viewable`,
- :attr:`~wuttaweb.views.master.MasterView.editable`, etc.).
-
- A simple example of usage::
-
- pyramid_config.add_permission('widgets', 'widgets.polish',
- label="Polish all the widgets")
-
- :param key: Unique key for the permission group. In the context
- of a master view, this will be the same as
- :attr:`~wuttaweb.views.master.MasterView.permission_prefix`.
-
- :param key: Unique key for the permission. This should be the
- "complete" permission name which includes the permission
- prefix.
-
- :param label: Optional label for the permission. If not
- specified, it is derived from ``key``.
-
- See also :func:`add_permission_group()`.
- """
- def action():
- config = pyramid_config.get_settings()['wutta_config']
- app = config.get_app()
- perms = pyramid_config.get_settings().get('wutta_permissions', {})
- group = perms.setdefault(groupkey, {'key': groupkey})
- group.setdefault('label', app.make_title(groupkey))
- perm = group.setdefault('perms', {}).setdefault(key, {'key': key})
- perm['label'] = label or app.make_title(key)
- pyramid_config.add_settings({'wutta_permissions': perms})
- pyramid_config.action(None, action)
diff --git a/src/wuttaweb/forms/base.py b/src/wuttaweb/forms/base.py
index 8ed17f8..7ee9b01 100644
--- a/src/wuttaweb/forms/base.py
+++ b/src/wuttaweb/forms/base.py
@@ -120,13 +120,6 @@ class Form:
See also :meth:`set_validator()`.
- .. attribute:: defaults
-
- Dict of default field values, used to construct the form in
- :meth:`get_schema()`.
-
- See also :meth:`set_default()`.
-
.. attribute:: readonly
Boolean indicating the form does not allow submit. In practice
@@ -255,7 +248,6 @@ class Form:
nodes={},
widgets={},
validators={},
- defaults={},
readonly=False,
readonly_fields=[],
required_fields={},
@@ -279,7 +271,6 @@ class Form:
self.nodes = nodes or {}
self.widgets = widgets or {}
self.validators = validators or {}
- self.defaults = defaults or {}
self.readonly = readonly
self.readonly_fields = set(readonly_fields or [])
self.required_fields = required_fields or {}
@@ -384,23 +375,6 @@ class Form:
"""
self.fields = FieldList(fields)
- def append(self, *keys):
- """
- Add some fields(s) to the form.
-
- This is a convenience to allow adding multiple fields at
- once::
-
- form.append('first_field',
- 'second_field',
- 'third_field')
-
- It will add each field to :attr:`fields`.
- """
- for key in keys:
- if key not in self.fields:
- self.fields.append(key)
-
def remove(self, *keys):
"""
Remove some fields(s) from the form.
@@ -497,18 +471,6 @@ class Form:
if self.schema and key in self.schema:
self.schema[key].validator = validator
- def set_default(self, key, value):
- """
- Set/override the default value for a field.
-
- :param key: Name of field.
-
- :param validator: Default value for the field.
-
- Default value overrides are tracked via :attr:`defaults`.
- """
- self.defaults[key] = value
-
def set_readonly(self, key, readonly=True):
"""
Enable or disable the "readonly" flag for a given field.
@@ -662,22 +624,29 @@ class Form:
if self.model_class:
- # collect list of field names and/or nodes
- includes = []
- for key in fields:
+ # first define full list of 'includes' - final schema
+ # should contain all of these fields
+ includes = list(fields)
+
+ # determine which we want ColanderAlchemy to handle
+ auto_includes = []
+ for key in includes:
+
+ # skip if we already have a node defined
if key in self.nodes:
- includes.append(self.nodes[key])
- else:
- includes.append(key)
+ continue
+
+ # we want the magic for this field
+ auto_includes.append(key)
# make initial schema with ColanderAlchemy magic
schema = SQLAlchemySchemaNode(self.model_class,
- includes=includes)
+ includes=auto_includes)
- # fill in the blanks if anything got missed
- for key in fields:
- if key not in schema:
- node = colander.SchemaNode(colander.String(), name=key)
+ # now fill in the blanks for non-magic fields
+ for key in includes:
+ if key not in auto_includes:
+ node = self.nodes[key]
schema.add(node)
else:
@@ -716,11 +685,6 @@ class Form:
elif key in schema: # field-level
schema[key].validator = validator
- # apply default value overrides
- for key, value in self.defaults.items():
- if key in schema:
- schema[key].default = value
-
# apply required flags
for key, required in self.required_fields.items():
if key in schema:
@@ -811,12 +775,7 @@ class Form:
output = render(template, context)
return HTML.literal(output)
- def render_vue_field(
- self,
- fieldname,
- readonly=None,
- **kwargs,
- ):
+ def render_vue_field(self, fieldname, readonly=None):
"""
Render the given field completely, i.e. ```` wrapper
with label and containing a widget.
@@ -832,12 +791,6 @@ class Form:
message="something went wrong!">
-
- .. warning::
-
- Any ``**kwargs`` received from caller are ignored by this
- method. For now they are allowed, for sake of backwawrd
- compatibility. This may change in the future.
"""
# readonly comes from: caller, field flag, or form flag
if readonly is None:
@@ -950,20 +903,6 @@ class Form:
return model_data
- # TODO: for tailbone compat, should document?
- # (ideally should remove this and find a better way)
- def get_vue_field_value(self, key):
- """ """
- if key not in self.fields:
- return
-
- dform = self.get_deform()
- if key not in dform:
- return
-
- field = dform[key]
- return make_json_safe(field.cstruct)
-
def validate(self):
"""
Try to validate the form, using data from the :attr:`request`.
diff --git a/src/wuttaweb/forms/schema.py b/src/wuttaweb/forms/schema.py
index 98ce0f1..ccb357f 100644
--- a/src/wuttaweb/forms/schema.py
+++ b/src/wuttaweb/forms/schema.py
@@ -257,101 +257,3 @@ class PersonRef(ObjectRef):
def sort_query(self, query):
""" """
return query.order_by(self.model_class.full_name)
-
-
-class WuttaSet(colander.Set):
- """
- Custom schema type for :class:`python:set` fields.
-
- This is a subclass of :class:`colander.Set`, but adds
- Wutta-related params to the constructor.
-
- :param request: Current :term:`request` object.
-
- :param session: Optional :term:`db session` to use instead of
- :class:`wuttaweb.db.Session`.
- """
-
- def __init__(self, request, session=None):
- super().__init__()
- self.request = request
- self.config = self.request.wutta_config
- self.app = self.config.get_app()
- self.session = session or Session()
-
-
-class RoleRefs(WuttaSet):
- """
- Form schema type for the User
- :attr:`~wuttjamaican:wuttjamaican.db.model.auth.User.roles`
- association proxy field.
-
- This is a subclass of :class:`WuttaSet`. It uses a ``set`` of
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role` ``uuid``
- values for underlying data format.
- """
-
- def widget_maker(self, **kwargs):
- """
- Constructs a default widget for the field.
-
- :returns: Instance of
- :class:`~wuttaweb.forms.widgets.RoleRefsWidget`.
- """
- kwargs.setdefault('session', self.session)
-
- if 'values' not in kwargs:
- model = self.app.model
- auth = self.app.get_auth_handler()
- avoid = {
- auth.get_role_authenticated(self.session),
- auth.get_role_anonymous(self.session),
- }
- avoid = set([role.uuid for role in avoid])
- roles = self.session.query(model.Role)\
- .filter(~model.Role.uuid.in_(avoid))\
- .order_by(model.Role.name)\
- .all()
- values = [(role.uuid, role.name) for role in roles]
- kwargs['values'] = values
-
- return widgets.RoleRefsWidget(self.request, **kwargs)
-
-
-class Permissions(WuttaSet):
- """
- Form schema type for the Role
- :attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.permissions`
- association proxy field.
-
- This is a subclass of :class:`WuttaSet`. It uses a ``set`` of
- :attr:`~wuttjamaican:wuttjamaican.db.model.auth.Permission.permission`
- values for underlying data format.
-
- :param permissions: Dict with all possible permissions. Should be
- in the same format as returned by
- :meth:`~wuttaweb.views.roles.RoleView.get_available_permissions()`.
- """
-
- def __init__(self, request, permissions, *args, **kwargs):
- super().__init__(request, *args, **kwargs)
- self.permissions = permissions
-
- def widget_maker(self, **kwargs):
- """
- Constructs a default widget for the field.
-
- :returns: Instance of
- :class:`~wuttaweb.forms.widgets.PermissionsWidget`.
- """
- kwargs.setdefault('session', self.session)
- kwargs.setdefault('permissions', self.permissions)
-
- if 'values' not in kwargs:
- values = []
- for gkey, group in self.permissions.items():
- for pkey, perm in group['perms'].items():
- values.append((pkey, perm['label']))
- kwargs['values'] = values
-
- return widgets.PermissionsWidget(self.request, **kwargs)
diff --git a/src/wuttaweb/forms/widgets.py b/src/wuttaweb/forms/widgets.py
index b4d8254..6627375 100644
--- a/src/wuttaweb/forms/widgets.py
+++ b/src/wuttaweb/forms/widgets.py
@@ -30,21 +30,12 @@ in the namespace:
* :class:`deform:deform.widget.Widget` (base class)
* :class:`deform:deform.widget.TextInputWidget`
-* :class:`deform:deform.widget.TextAreaWidget`
-* :class:`deform:deform.widget.PasswordWidget`
-* :class:`deform:deform.widget.CheckedPasswordWidget`
* :class:`deform:deform.widget.SelectWidget`
-* :class:`deform:deform.widget.CheckboxChoiceWidget`
"""
-import colander
-from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
- PasswordWidget, CheckedPasswordWidget,
- SelectWidget, CheckboxChoiceWidget)
+from deform.widget import Widget, TextInputWidget, SelectWidget
from webhelpers2.html import HTML
-from wuttaweb.db import Session
-
class ObjectRefWidget(SelectWidget):
"""
@@ -57,18 +48,6 @@ class ObjectRefWidget(SelectWidget):
the form schema; via
:meth:`~wuttaweb.forms.schema.ObjectRef.widget_maker()`.
- In readonly mode, this renders a ```` tag around the
- :attr:`model_instance` (converted to string).
-
- Otherwise it renders a select (dropdown) element allowing user to
- choose from available records.
-
- This is a subclass of :class:`deform:deform.widget.SelectWidget`
- and uses these Deform templates:
-
- * ``select``
- * ``readonly/objectref``
-
.. attribute:: model_instance
Reference to the model record instance, i.e. the "far side" of
@@ -81,112 +60,23 @@ class ObjectRefWidget(SelectWidget):
when the :class:`~wuttaweb.forms.schema.ObjectRef` type
instance (associated with the node) is serialized.
"""
- readonly_template = 'readonly/objectref'
def __init__(self, request, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
-
-class NotesWidget(TextAreaWidget):
- """
- Widget for use with "notes" fields.
-
- In readonly mode, this shows the notes with a background to make
- them stand out a bit more.
-
- Otherwise it effectively shows a ``