diff --git a/CHANGELOG.md b/CHANGELOG.md index 34f59cb..c7b0850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,32 +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.13.0 (2024-08-26) - -### Feat - -- use native wuttjamaican app to send feedback email -- add basic user feedback email mechanism -- add "progress" page for executing upgrades -- add basic support for execute upgrades, download stdout/stderr -- add basic progress page/indicator support -- add basic "delete results" grid tool -- add initial views for upgrades -- allow app db to be rattail-native instead of wutta-native -- add per-row css class support for grids -- improve grid filter API a bit, support string/bool filters - -### Fix - -- tweak max image size for full logo on home, login pages -- improve handling of boolean form fields -- misc. improvements for display of grids, form errors -- use autocomplete for grid filter verb choices -- small cleanup for grid filters template -- add once-button action for grid Reset View -- set sort defaults for users, roles -- add override hook for base form template - ## v0.12.1 (2024-08-22) ### Fix diff --git a/docs/api/wuttaweb/index.rst b/docs/api/wuttaweb/index.rst index 7299034..9749cae 100644 --- a/docs/api/wuttaweb/index.rst +++ b/docs/api/wuttaweb/index.rst @@ -20,7 +20,6 @@ handler helpers menus - progress static subscribers util @@ -31,8 +30,6 @@ views.essential views.master views.people - views.progress views.roles views.settings - views.upgrades views.users diff --git a/docs/api/wuttaweb/progress.rst b/docs/api/wuttaweb/progress.rst deleted file mode 100644 index 498d641..0000000 --- a/docs/api/wuttaweb/progress.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttaweb.progress`` -===================== - -.. automodule:: wuttaweb.progress - :members: diff --git a/docs/api/wuttaweb/views.people.rst b/docs/api/wuttaweb/views.people.rst index 2dc919b..89c6883 100644 --- a/docs/api/wuttaweb/views.people.rst +++ b/docs/api/wuttaweb/views.people.rst @@ -1,6 +1,6 @@ ``wuttaweb.views.people`` -========================= +=========================== .. automodule:: wuttaweb.views.people :members: diff --git a/docs/api/wuttaweb/views.progress.rst b/docs/api/wuttaweb/views.progress.rst deleted file mode 100644 index 34e2661..0000000 --- a/docs/api/wuttaweb/views.progress.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttaweb.views.progress`` -=========================== - -.. automodule:: wuttaweb.views.progress - :members: diff --git a/docs/api/wuttaweb/views.upgrades.rst b/docs/api/wuttaweb/views.upgrades.rst deleted file mode 100644 index 2909003..0000000 --- a/docs/api/wuttaweb/views.upgrades.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttaweb.views.upgrades`` -=========================== - -.. automodule:: wuttaweb.views.upgrades - :members: diff --git a/docs/conf.py b/docs/conf.py index 0f73d82..3d568ef 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,6 @@ intersphinx_mapping = { 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None), 'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None), 'python': ('https://docs.python.org/3/', None), - 'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None), 'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None), 'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None), } diff --git a/pyproject.toml b/pyproject.toml index fac1e9b..41fda6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "WuttaWeb" -version = "0.13.0" +version = "0.12.1" description = "Web App for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] @@ -31,7 +31,6 @@ classifiers = [ requires-python = ">= 3.8" dependencies = [ "ColanderAlchemy", - "humanize", "paginate", "paginate_sqlalchemy", "pyramid>=2", @@ -42,7 +41,7 @@ dependencies = [ "pyramid_tm", "waitress", "WebHelpers2", - "WuttJamaican[db,email]>=0.13.0", + "WuttJamaican[db]>=0.12.1", "zope.sqlalchemy>=1.5", ] diff --git a/src/wuttaweb/app.py b/src/wuttaweb/app.py index c263b60..88318b4 100644 --- a/src/wuttaweb/app.py +++ b/src/wuttaweb/app.py @@ -37,10 +37,9 @@ from wuttaweb.auth import WuttaSecurityPolicy class WebAppProvider(AppProvider): """ - The :term:`app provider` for WuttaWeb. This adds some methods to - the :term:`app handler`, which are specific to web apps. + The :term:`app provider` for WuttaWeb. This adds some methods + specific to web apps. """ - email_templates = 'wuttaweb:email/templates' def get_web_handler(self, **kwargs): """ diff --git a/src/wuttaweb/email/templates/feedback.html.mako b/src/wuttaweb/email/templates/feedback.html.mako deleted file mode 100644 index c483f1a..0000000 --- a/src/wuttaweb/email/templates/feedback.html.mako +++ /dev/null @@ -1,40 +0,0 @@ -## -*- coding: utf-8 -*- - -
- - - -- % if user: - ${user} - % else: - ${user_name} - % endif -
- - - - - -${client_ip}
- - -${message}
- - - diff --git a/src/wuttaweb/email/templates/feedback.txt.mako b/src/wuttaweb/email/templates/feedback.txt.mako deleted file mode 100644 index a73a55e..0000000 --- a/src/wuttaweb/email/templates/feedback.txt.mako +++ /dev/null @@ -1,23 +0,0 @@ -## -*- coding: utf-8; -*- - -# User feedback from website - -**User Name** - -% if user: -${user} -% else: -${user_name} -% endif - -**Referring URL** - -${referrer} - -**Client IP** - -${client_ip} - -**Message** - -${message} diff --git a/src/wuttaweb/forms/schema.py b/src/wuttaweb/forms/schema.py index 74839a7..4402fde 100644 --- a/src/wuttaweb/forms/schema.py +++ b/src/wuttaweb/forms/schema.py @@ -92,53 +92,6 @@ class ObjectNode(colander.SchemaNode): raise NotImplementedError(f"you must define {class_name}.objectify()") -class WuttaEnum(colander.Enum): - """ - Custom schema type for enum fields. - - This is a subclass of :class:`colander.Enum`, but adds a - default widget (``SelectWidget``) with enum choices. - - :param request: Current :term:`request` object. - """ - - def __init__(self, request, *args, **kwargs): - super().__init__(*args, **kwargs) - self.request = request - self.config = self.request.wutta_config - self.app = self.config.get_app() - - def widget_maker(self, **kwargs): - """ """ - - if 'values' not in kwargs: - kwargs['values'] = [(getattr(e, self.attr), getattr(e, self.attr)) - for e in self.enum_cls] - - return widgets.SelectWidget(**kwargs) - - -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 ObjectRef(colander.SchemaType): """ Custom schema type for a model class reference field. @@ -246,7 +199,7 @@ class ObjectRef(colander.SchemaType): # fetch object from DB model = self.app.model - obj = self.session.get(self.model_class, value) + obj = self.session.query(self.model_class).get(value) # raise error if not found if not obj: @@ -294,28 +247,14 @@ class ObjectRef(colander.SchemaType): kwargs['values'] = values if 'url' not in kwargs: - kwargs['url'] = self.get_object_url + kwargs['url'] = lambda person: self.request.route_url('people.view', uuid=person.uuid) return widgets.ObjectRefWidget(self.request, **kwargs) - def get_object_url(self, obj): - """ - Returns the "view" URL for the given object, if applicable. - - This is used when rendering the field readonly. If this - method returns a URL then the field text will be wrapped with - a hyperlink, otherwise it will be shown as-is. - - Default logic always returns ``None``; subclass should - override as needed. - """ - class PersonRef(ObjectRef): """ - Custom schema type for a - :class:`~wuttjamaican:wuttjamaican.db.model.base.Person` reference - field. + Custom schema type for a ``Person`` reference field. This is a subclass of :class:`ObjectRef`. """ @@ -330,33 +269,26 @@ class PersonRef(ObjectRef): """ """ return query.order_by(self.model_class.full_name) - def get_object_url(self, person): - """ """ - return self.request.route_url('people.view', uuid=person.uuid) - -class UserRef(ObjectRef): +class WuttaSet(colander.Set): """ - Custom schema type for a - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` reference - field. + Custom schema type for :class:`python:set` fields. - This is a subclass of :class:`ObjectRef`. + 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`. """ - @property - def model_class(self): - """ """ - model = self.app.model - return model.User - - def sort_query(self, query): - """ """ - return query.order_by(self.model_class.username) - - def get_object_url(self, user): - """ """ - return self.request.route_url('users.view', uuid=user.uuid) + 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): @@ -456,35 +388,3 @@ class Permissions(WuttaSet): kwargs['values'] = values return widgets.PermissionsWidget(self.request, **kwargs) - - -class FileDownload(colander.String): - """ - Custom schema type for a file download field. - - This field is only meant for readonly use, it does not handle file - uploads. - - It expects the incoming ``appstruct`` to be the path to a file on - disk (or null). - - Uses the :class:`~wuttaweb.forms.widgets.FileDownloadWidget` by - default. - - :param request: Current :term:`request` object. - - :param url: Optional URL for hyperlink. If not specified, file - name/size is shown with no hyperlink. - """ - - def __init__(self, request, *args, **kwargs): - self.url = kwargs.pop('url', None) - super().__init__(*args, **kwargs) - self.request = request - self.config = self.request.wutta_config - self.app = self.config.get_app() - - def widget_maker(self, **kwargs): - """ """ - kwargs.setdefault('url', self.url) - return widgets.FileDownloadWidget(self.request, **kwargs) diff --git a/src/wuttaweb/forms/widgets.py b/src/wuttaweb/forms/widgets.py index 4db861a..837b6f1 100644 --- a/src/wuttaweb/forms/widgets.py +++ b/src/wuttaweb/forms/widgets.py @@ -39,10 +39,7 @@ in the namespace: * :class:`deform:deform.widget.MoneyInputWidget` """ -import os - import colander -import humanize from deform.widget import (Widget, TextInputWidget, TextAreaWidget, PasswordWidget, CheckedPasswordWidget, CheckboxWidget, SelectWidget, CheckboxChoiceWidget, @@ -150,63 +147,6 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget): self.session = session or Session() -class FileDownloadWidget(Widget): - """ - Widget for use with :class:`~wuttaweb.forms.schema.FileDownload` - fields. - - This only supports readonly, and shows a hyperlink to download the - file. Link text is the filename plus file size. - - This is a subclass of :class:`deform:deform.widget.Widget` and - uses these Deform templates: - - * ``readonly/filedownload`` - - :param request: Current :term:`request` object. - - :param url: Optional URL for hyperlink. If not specified, file - name/size is shown with no hyperlink. - """ - readonly_template = 'readonly/filedownload' - - def __init__(self, request, *args, **kwargs): - self.url = kwargs.pop('url', None) - super().__init__(*args, **kwargs) - self.request = request - self.config = self.request.wutta_config - self.app = self.config.get_app() - - def serialize(self, field, cstruct, **kw): - """ """ - # nb. readonly is the only way this rolls - kw['readonly'] = True - template = self.readonly_template - - path = cstruct or None - if path: - kw.setdefault('filename', os.path.basename(path)) - kw.setdefault('filesize', self.readable_size(path)) - if self.url: - kw.setdefault('url', self.url) - - else: - kw.setdefault('filename', None) - kw.setdefault('filesize', None) - - kw.setdefault('url', None) - values = self.get_template_values(field, cstruct, kw) - return field.renderer(template, **values) - - def readable_size(self, path): - """ """ - try: - size = os.path.getsize(path) - except os.error: - size = 0 - return humanize.naturalsize(size) - - class RoleRefsWidget(WuttaCheckboxChoiceWidget): """ Widget for use with User @@ -244,7 +184,7 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget): roles = [] if cstruct: for uuid in cstruct: - role = self.session.get(model.Role, uuid) + role = self.session.query(model.Role).get(uuid) if role: roles.append(role) kw['roles'] = roles @@ -288,10 +228,6 @@ class UserRefsWidget(WuttaCheckboxChoiceWidget): users.append(dict([(key, getattr(user, key)) for key in columns + ['uuid']])) - # do not render if no data - if not users: - return HTML.tag('span') - # grid grid = Grid(self.request, key='roles.view.users', columns=columns, data=users) diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py index 4ff990e..0f2c812 100644 --- a/src/wuttaweb/grids/base.py +++ b/src/wuttaweb/grids/base.py @@ -28,7 +28,7 @@ import functools import json import logging import warnings -from collections import namedtuple, OrderedDict +from collections import namedtuple import sqlalchemy as sa from sqlalchemy import orm @@ -339,16 +339,6 @@ class Grid: sorting. See :meth:`set_joiner()` for more info. - - .. attribute:: tools - - Dict of "tool" elements for the grid. Tools are usually buttons - (e.g. "Delete Results"), shown on top right of the grid. - - The keys for this dict are somewhat arbitrary, defined by the - caller. Values should be HTML literal elements. - - See also :meth:`add_tool()` and :meth:`set_tools()`. """ def __init__( @@ -379,7 +369,6 @@ class Grid: filters=None, filter_defaults=None, joiners=None, - tools=None, ): self.request = request self.vue_tagname = vue_tagname @@ -397,7 +386,6 @@ class Grid: self.app = self.config.get_app() self.set_columns(columns or self.get_columns()) - self.set_tools(tools) # sorting self.sortable = sortable @@ -670,33 +658,6 @@ class Grid: """ self.actions.append(GridAction(self.request, key, **kwargs)) - def set_tools(self, tools): - """ - Set the :attr:`tools` attribute using the given tools collection. - - This will normalize the list/dict to desired internal format. - """ - if tools and isinstance(tools, list): - if not any([isinstance(t, (tuple, list)) for t in tools]): - tools = [(self.app.make_uuid(), t) for t in tools] - self.tools = OrderedDict(tools or []) - - def add_tool(self, html, key=None): - """ - Add a new HTML snippet to the :attr:`tools` dict. - - :param html: HTML literal for the tool element. - - :param key: Optional key to use when adding to the - :attr:`tools` dict. If not specified, a random string is - generated. - - See also :meth:`set_tools()`. - """ - if not key: - key = self.app.make_uuid() - self.tools[key] = html - ############################## # joining methods ############################## @@ -1117,7 +1078,6 @@ class Grid: :returns: A :class:`~wuttaweb.grids.filters.GridFilter` instance. """ - key = kwargs.pop('key', None) # model_property is required model_property = None @@ -1142,7 +1102,7 @@ class Grid: # make filter kwargs['model_property'] = model_property - return factory(self.request, key or model_property.key, **kwargs) + return factory(self.request, model_property.key, **kwargs) def set_filter(self, key, filterinfo=None, **kwargs): """ @@ -1172,7 +1132,6 @@ class Grid: # filtr = filterinfo raise NotImplementedError else: - kwargs['key'] = key kwargs.setdefault('label', self.get_label(key)) filtr = self.make_filter(filterinfo or key, **kwargs) diff --git a/src/wuttaweb/menus.py b/src/wuttaweb/menus.py index c1c47dc..84d5534 100644 --- a/src/wuttaweb/menus.py +++ b/src/wuttaweb/menus.py @@ -168,11 +168,6 @@ class MenuHandler(GenericHandler): 'route': 'settings', 'perm': 'settings.list', }, - { - 'title': "Upgrades", - 'route': 'upgrades', - 'perm': 'upgrades.list', - }, ], } diff --git a/src/wuttaweb/progress.py b/src/wuttaweb/progress.py deleted file mode 100644 index 759c2da..0000000 --- a/src/wuttaweb/progress.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# wuttaweb -- Web App for Wutta Framework -# Copyright © 2024 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework 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 General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see- {{ progressMessage }} ... {{ totalDisplay }} -
- -