WuttaWeb 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/CHANGELOG.md +12 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/PKG-INFO +2 -2
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/index.rst +2 -0
- wuttaweb-0.4.0/docs/api/wuttaweb/views.master.rst +6 -0
- wuttaweb-0.4.0/docs/api/wuttaweb/views.settings.rst +6 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/pyproject.toml +2 -2
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/menus.py +3 -2
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/subscribers.py +1 -0
- wuttaweb-0.4.0/src/wuttaweb/templates/appinfo/index.mako +56 -0
- wuttaweb-0.4.0/src/wuttaweb/templates/master/index.mako +13 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/views/__init__.py +2 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/views/base.py +8 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/views/common.py +4 -1
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/views/essential.py +1 -0
- wuttaweb-0.4.0/src/wuttaweb/views/master.py +443 -0
- wuttaweb-0.4.0/src/wuttaweb/views/settings.py +47 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/forms/test_base.py +4 -2
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_subscribers.py +4 -2
- wuttaweb-0.4.0/tests/utils.py +11 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/views/test_base.py +5 -1
- wuttaweb-0.4.0/tests/views/test_master.py +282 -0
- wuttaweb-0.4.0/tests/views/test_settings.py +13 -0
- wuttaweb-0.4.0/tests/views/utils.py +57 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/.gitignore +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/COPYING.txt +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/README.md +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/Makefile +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/index.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/app.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/auth.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/db.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/forms.base.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/forms.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/handler.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/helpers.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/menus.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/static.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/subscribers.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/util.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/views.auth.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/views.base.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/views.common.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/views.essential.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/api/wuttaweb/views.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/conf.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/index.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/make.bat +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/docs/narr/index.rst +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/db.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tasks.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/__init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/forms/__init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_app.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_handler.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_static.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/test_util.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.3.0 → wuttaweb-0.4.0}/tox.ini +0 -0
|
@@ -5,6 +5,18 @@ All notable changes to wuttaweb will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v0.4.0 (2024-08-05)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- add basic App Info view (index only)
|
|
13
|
+
- add initial `MasterView` support
|
|
14
|
+
|
|
15
|
+
### Fix
|
|
16
|
+
|
|
17
|
+
- add `notfound()` View method; auto-append trailing slash
|
|
18
|
+
- bump min version for wuttjamaican
|
|
19
|
+
|
|
8
20
|
## v0.3.0 (2024-08-05)
|
|
9
21
|
|
|
10
22
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Web App for Wutta Framework
|
|
5
5
|
Project-URL: Homepage, https://wuttaproject.org/
|
|
6
6
|
Project-URL: Repository, https://forgejo.wuttaproject.org/wutta/wuttaweb
|
|
@@ -31,7 +31,7 @@ Requires-Dist: pyramid-tm
|
|
|
31
31
|
Requires-Dist: pyramid>=2
|
|
32
32
|
Requires-Dist: waitress
|
|
33
33
|
Requires-Dist: webhelpers2
|
|
34
|
-
Requires-Dist: wuttjamaican[db]>=0.
|
|
34
|
+
Requires-Dist: wuttjamaican[db]>=0.8.3
|
|
35
35
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
36
36
|
Provides-Extra: docs
|
|
37
37
|
Requires-Dist: furo; extra == 'docs'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.4.0"
|
|
10
10
|
description = "Web App for Wutta Framework"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
|
|
@@ -37,7 +37,7 @@ dependencies = [
|
|
|
37
37
|
"pyramid_tm",
|
|
38
38
|
"waitress",
|
|
39
39
|
"WebHelpers2",
|
|
40
|
-
"WuttJamaican[db]>=0.
|
|
40
|
+
"WuttJamaican[db]>=0.8.3",
|
|
41
41
|
"zope.sqlalchemy>=1.5",
|
|
42
42
|
]
|
|
43
43
|
|
|
@@ -249,6 +249,7 @@ def before_render(event):
|
|
|
249
249
|
context['h'] = helpers
|
|
250
250
|
context['url'] = request.route_url
|
|
251
251
|
context['json'] = json
|
|
252
|
+
context['b'] = 'o' if request.use_oruga else 'b' # for buefy
|
|
252
253
|
|
|
253
254
|
# TODO: this should be avoided somehow, for non-traditional web
|
|
254
255
|
# apps, esp. "API" web apps. (in the meantime can configure the
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
## -*- coding: utf-8; -*-
|
|
2
|
+
<%inherit file="/master/index.mako" />
|
|
3
|
+
|
|
4
|
+
<%def name="page_content()">
|
|
5
|
+
|
|
6
|
+
<nav class="panel item-panel">
|
|
7
|
+
<p class="panel-heading">Application</p>
|
|
8
|
+
<div class="panel-block">
|
|
9
|
+
<div style="width: 100%;">
|
|
10
|
+
<b-field horizontal label="Distribution">
|
|
11
|
+
<span>${app.get_distribution(obj=app.get_web_handler()) or f'?? - set config for `{app.appname}.app_dist`'}</span>
|
|
12
|
+
</b-field>
|
|
13
|
+
<b-field horizontal label="Version">
|
|
14
|
+
<span>${app.get_version(obj=app.get_web_handler()) or f'?? - set config for `{app.appname}.app_dist`'}</span>
|
|
15
|
+
</b-field>
|
|
16
|
+
<b-field horizontal label="App Title">
|
|
17
|
+
<span>${app.get_title()}</span>
|
|
18
|
+
</b-field>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</nav>
|
|
22
|
+
|
|
23
|
+
<nav class="panel item-panel">
|
|
24
|
+
<p class="panel-heading">Configuration Files</p>
|
|
25
|
+
<div class="panel-block">
|
|
26
|
+
<div style="width: 100%;">
|
|
27
|
+
<${b}-table :data="configFiles">
|
|
28
|
+
|
|
29
|
+
<${b}-table-column field="priority"
|
|
30
|
+
label="Priority"
|
|
31
|
+
v-slot="props">
|
|
32
|
+
{{ props.row.priority }}
|
|
33
|
+
</${b}-table-column>
|
|
34
|
+
|
|
35
|
+
<${b}-table-column field="path"
|
|
36
|
+
label="File Path"
|
|
37
|
+
v-slot="props">
|
|
38
|
+
{{ props.row.path }}
|
|
39
|
+
</${b}-table-column>
|
|
40
|
+
|
|
41
|
+
</${b}-table>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</nav>
|
|
45
|
+
|
|
46
|
+
</%def>
|
|
47
|
+
|
|
48
|
+
<%def name="modify_this_page_vars()">
|
|
49
|
+
${parent.modify_this_page_vars()}
|
|
50
|
+
<script>
|
|
51
|
+
ThisPageData.configFiles = ${json.dumps([dict(path=p, priority=i) for i, p in enumerate(config.get_prioritized_files(), 1)])|n}
|
|
52
|
+
</script>
|
|
53
|
+
</%def>
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
${parent.body()}
|
|
@@ -27,9 +27,11 @@ For convenience, from this ``wuttaweb.views`` namespace you can access
|
|
|
27
27
|
the following:
|
|
28
28
|
|
|
29
29
|
* :class:`~wuttaweb.views.base.View`
|
|
30
|
+
* :class:`~wuttaweb.views.master.MasterView`
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
33
|
from .base import View
|
|
34
|
+
from .master import MasterView
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
def includeme(config):
|
|
@@ -73,6 +73,14 @@ class View:
|
|
|
73
73
|
"""
|
|
74
74
|
return forms.Form(self.request, **kwargs)
|
|
75
75
|
|
|
76
|
+
def notfound(self):
|
|
77
|
+
"""
|
|
78
|
+
Convenience method, to raise a HTTP 404 Not Found exception::
|
|
79
|
+
|
|
80
|
+
raise self.notfound()
|
|
81
|
+
"""
|
|
82
|
+
return httpexceptions.HTTPNotFound()
|
|
83
|
+
|
|
76
84
|
def redirect(self, url, **kwargs):
|
|
77
85
|
"""
|
|
78
86
|
Convenience method to return a HTTP 302 response.
|
|
@@ -53,7 +53,10 @@ class CommonView(View):
|
|
|
53
53
|
@classmethod
|
|
54
54
|
def _defaults(cls, config):
|
|
55
55
|
|
|
56
|
-
#
|
|
56
|
+
# auto-correct URLs which require trailing slash
|
|
57
|
+
config.add_notfound_view(cls, attr='notfound', append_slash=True)
|
|
58
|
+
|
|
59
|
+
# home page
|
|
57
60
|
config.add_route('home', '/')
|
|
58
61
|
config.add_view(cls, attr='home',
|
|
59
62
|
route_name='home',
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
################################################################################
|
|
3
|
+
#
|
|
4
|
+
# wuttaweb -- Web App for Wutta Framework
|
|
5
|
+
# Copyright © 2024 Lance Edgar
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Wutta Framework.
|
|
8
|
+
#
|
|
9
|
+
# Wutta Framework is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU General Public License as published by the Free
|
|
11
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Wutta Framework is distributed in the hope that it will be useful, but
|
|
15
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
17
|
+
# more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License along with
|
|
20
|
+
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
#
|
|
22
|
+
################################################################################
|
|
23
|
+
"""
|
|
24
|
+
Base Logic for Master Views
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from pyramid.renderers import render_to_response
|
|
28
|
+
|
|
29
|
+
from wuttaweb.views import View
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MasterView(View):
|
|
33
|
+
"""
|
|
34
|
+
Base class for "master" views.
|
|
35
|
+
|
|
36
|
+
Master views typically map to a table in a DB, though not always.
|
|
37
|
+
They essentially are a set of CRUD views for a certain type of
|
|
38
|
+
data record.
|
|
39
|
+
|
|
40
|
+
Many attributes may be overridden in subclass. For instance to
|
|
41
|
+
define :attr:`model_class`::
|
|
42
|
+
|
|
43
|
+
from wuttaweb.views import MasterView
|
|
44
|
+
from wuttjamaican.db.model import Person
|
|
45
|
+
|
|
46
|
+
class MyPersonView(MasterView):
|
|
47
|
+
model_class = Person
|
|
48
|
+
|
|
49
|
+
def includeme(config):
|
|
50
|
+
MyPersonView.defaults(config)
|
|
51
|
+
|
|
52
|
+
.. note::
|
|
53
|
+
|
|
54
|
+
Many of these attributes will only exist if they have been
|
|
55
|
+
explicitly defined in a subclass. There are corresponding
|
|
56
|
+
``get_xxx()`` methods which should be used instead of accessing
|
|
57
|
+
these attributes directly.
|
|
58
|
+
|
|
59
|
+
.. attribute:: model_class
|
|
60
|
+
|
|
61
|
+
Optional reference to a data model class. While not strictly
|
|
62
|
+
required, most views will set this to a SQLAlchemy mapped
|
|
63
|
+
class,
|
|
64
|
+
e.g. :class:`wuttjamaican:wuttjamaican.db.model.auth.User`.
|
|
65
|
+
|
|
66
|
+
Code should not access this directly but instead call
|
|
67
|
+
:meth:`get_model_class()`.
|
|
68
|
+
|
|
69
|
+
.. attribute:: model_name
|
|
70
|
+
|
|
71
|
+
Optional override for the view's data model name,
|
|
72
|
+
e.g. ``'WuttaWidget'``.
|
|
73
|
+
|
|
74
|
+
Code should not access this directly but instead call
|
|
75
|
+
:meth:`get_model_name()`.
|
|
76
|
+
|
|
77
|
+
.. attribute:: model_name_normalized
|
|
78
|
+
|
|
79
|
+
Optional override for the view's "normalized" data model name,
|
|
80
|
+
e.g. ``'wutta_widget'``.
|
|
81
|
+
|
|
82
|
+
Code should not access this directly but instead call
|
|
83
|
+
:meth:`get_model_name_normalized()`.
|
|
84
|
+
|
|
85
|
+
.. attribute:: model_title
|
|
86
|
+
|
|
87
|
+
Optional override for the view's "humanized" (singular) model
|
|
88
|
+
title, e.g. ``"Wutta Widget"``.
|
|
89
|
+
|
|
90
|
+
Code should not access this directly but instead call
|
|
91
|
+
:meth:`get_model_title()`.
|
|
92
|
+
|
|
93
|
+
.. attribute:: model_title_plural
|
|
94
|
+
|
|
95
|
+
Optional override for the view's "humanized" (plural) model
|
|
96
|
+
title, e.g. ``"Wutta Widgets"``.
|
|
97
|
+
|
|
98
|
+
Code should not access this directly but instead call
|
|
99
|
+
:meth:`get_model_title_plural()`.
|
|
100
|
+
|
|
101
|
+
.. attribute:: route_prefix
|
|
102
|
+
|
|
103
|
+
Optional override for the view's route prefix,
|
|
104
|
+
e.g. ``'wutta_widgets'``.
|
|
105
|
+
|
|
106
|
+
Code should not access this directly but instead call
|
|
107
|
+
:meth:`get_route_prefix()`.
|
|
108
|
+
|
|
109
|
+
.. attribute:: url_prefix
|
|
110
|
+
|
|
111
|
+
Optional override for the view's URL prefix,
|
|
112
|
+
e.g. ``'/widgets'``.
|
|
113
|
+
|
|
114
|
+
Code should not access this directly but instead call
|
|
115
|
+
:meth:`get_url_prefix()`.
|
|
116
|
+
|
|
117
|
+
.. attribute:: template_prefix
|
|
118
|
+
|
|
119
|
+
Optional override for the view's template prefix,
|
|
120
|
+
e.g. ``'/widgets'``.
|
|
121
|
+
|
|
122
|
+
Code should not access this directly but instead call
|
|
123
|
+
:meth:`get_template_prefix()`.
|
|
124
|
+
|
|
125
|
+
.. attribute:: listable
|
|
126
|
+
|
|
127
|
+
Boolean indicating whether the view model supports "listing" -
|
|
128
|
+
i.e. it should have an :meth:`index()` view.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
##############################
|
|
132
|
+
# attributes
|
|
133
|
+
##############################
|
|
134
|
+
|
|
135
|
+
listable = True
|
|
136
|
+
|
|
137
|
+
##############################
|
|
138
|
+
# view methods
|
|
139
|
+
##############################
|
|
140
|
+
|
|
141
|
+
def index(self):
|
|
142
|
+
"""
|
|
143
|
+
View to "list" (filter/browse) the model data.
|
|
144
|
+
|
|
145
|
+
This is the "default" view for the model and is what user sees
|
|
146
|
+
when visiting the "root" path under the :attr:`url_prefix`,
|
|
147
|
+
e.g. ``/widgets/``.
|
|
148
|
+
"""
|
|
149
|
+
return self.render_to_response('index', {})
|
|
150
|
+
|
|
151
|
+
##############################
|
|
152
|
+
# support methods
|
|
153
|
+
##############################
|
|
154
|
+
|
|
155
|
+
def get_index_title(self):
|
|
156
|
+
"""
|
|
157
|
+
Returns the main index title for the master view.
|
|
158
|
+
|
|
159
|
+
By default this returns the value from
|
|
160
|
+
:meth:`get_model_title_plural()`. Subclass may override as
|
|
161
|
+
needed.
|
|
162
|
+
"""
|
|
163
|
+
return self.get_model_title_plural()
|
|
164
|
+
|
|
165
|
+
def render_to_response(self, template, context):
|
|
166
|
+
"""
|
|
167
|
+
Locate and render an appropriate template, with the given
|
|
168
|
+
context, and return a :term:`response`.
|
|
169
|
+
|
|
170
|
+
The specified ``template`` should be only the "base name" for
|
|
171
|
+
the template - e.g. ``'index'`` or ``'edit'``. This method
|
|
172
|
+
will then try to locate a suitable template file, based on
|
|
173
|
+
values from :meth:`get_template_prefix()` and
|
|
174
|
+
:meth:`get_fallback_templates()`.
|
|
175
|
+
|
|
176
|
+
In practice this *usually* means two different template paths
|
|
177
|
+
will be attempted, e.g. if ``template`` is ``'edit'`` and
|
|
178
|
+
:attr:`template_prefix` is ``'/widgets'``:
|
|
179
|
+
|
|
180
|
+
* ``/widgets/edit.mako``
|
|
181
|
+
* ``/master/edit.mako``
|
|
182
|
+
|
|
183
|
+
The first template found to exist will be used for rendering.
|
|
184
|
+
It then calls
|
|
185
|
+
:func:`pyramid:pyramid.renderers.render_to_response()` and
|
|
186
|
+
returns the result.
|
|
187
|
+
|
|
188
|
+
:param template: Base name for the template.
|
|
189
|
+
|
|
190
|
+
:param context: Data dict to be used as template context.
|
|
191
|
+
|
|
192
|
+
:returns: Response object containing the rendered template.
|
|
193
|
+
"""
|
|
194
|
+
defaults = {
|
|
195
|
+
'index_title': self.get_index_title(),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# merge defaults + caller-provided context
|
|
199
|
+
defaults.update(context)
|
|
200
|
+
context = defaults
|
|
201
|
+
|
|
202
|
+
# first try the template path most specific to this view
|
|
203
|
+
template_prefix = self.get_template_prefix()
|
|
204
|
+
mako_path = f'{template_prefix}/{template}.mako'
|
|
205
|
+
try:
|
|
206
|
+
return render_to_response(mako_path, context, request=self.request)
|
|
207
|
+
except IOError:
|
|
208
|
+
|
|
209
|
+
# failing that, try one or more fallback templates
|
|
210
|
+
for fallback in self.get_fallback_templates(template):
|
|
211
|
+
try:
|
|
212
|
+
return render_to_response(fallback, context, request=self.request)
|
|
213
|
+
except IOError:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
# if we made it all the way here, then we found no
|
|
217
|
+
# templates at all, in which case re-attempt the first and
|
|
218
|
+
# let that error raise on up
|
|
219
|
+
return render_to_response(mako_path, context, request=self.request)
|
|
220
|
+
|
|
221
|
+
def get_fallback_templates(self, template):
|
|
222
|
+
"""
|
|
223
|
+
Returns a list of "fallback" template paths which may be
|
|
224
|
+
attempted for rendering a view. This is used within
|
|
225
|
+
:meth:`render_to_response()` if the "first guess" template
|
|
226
|
+
file was not found.
|
|
227
|
+
|
|
228
|
+
:param template: Base name for a template (without prefix), e.g.
|
|
229
|
+
``'custom'``.
|
|
230
|
+
|
|
231
|
+
:returns: List of full template paths to be tried, based on
|
|
232
|
+
the specified template. For instance if ``template`` is
|
|
233
|
+
``'custom'`` this will (by default) return::
|
|
234
|
+
|
|
235
|
+
['/master/custom.mako']
|
|
236
|
+
"""
|
|
237
|
+
return [f'/master/{template}.mako']
|
|
238
|
+
|
|
239
|
+
##############################
|
|
240
|
+
# class methods
|
|
241
|
+
##############################
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def get_model_class(cls):
|
|
245
|
+
"""
|
|
246
|
+
Returns the model class for the view (if defined).
|
|
247
|
+
|
|
248
|
+
A model class will *usually* be a SQLAlchemy mapped class,
|
|
249
|
+
e.g. :class:`wuttjamaican:wuttjamaican.db.model.base.Person`.
|
|
250
|
+
|
|
251
|
+
There is no default value here, but a subclass may override by
|
|
252
|
+
assigning :attr:`model_class`.
|
|
253
|
+
|
|
254
|
+
Note that the model class is not *required* - however if you
|
|
255
|
+
do not set the :attr:`model_class`, then you *must* set the
|
|
256
|
+
:attr:`model_name`.
|
|
257
|
+
"""
|
|
258
|
+
if hasattr(cls, 'model_class'):
|
|
259
|
+
return cls.model_class
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def get_model_name(cls):
|
|
263
|
+
"""
|
|
264
|
+
Returns the model name for the view.
|
|
265
|
+
|
|
266
|
+
A model name should generally be in the format of a Python
|
|
267
|
+
class name, e.g. ``'WuttaWidget'``. (Note this is
|
|
268
|
+
*singular*, not plural.)
|
|
269
|
+
|
|
270
|
+
The default logic will call :meth:`get_model_class()` and
|
|
271
|
+
return that class name as-is. A subclass may override by
|
|
272
|
+
assigning :attr:`model_name`.
|
|
273
|
+
"""
|
|
274
|
+
if hasattr(cls, 'model_name'):
|
|
275
|
+
return cls.model_name
|
|
276
|
+
|
|
277
|
+
return cls.get_model_class().__name__
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def get_model_name_normalized(cls):
|
|
281
|
+
"""
|
|
282
|
+
Returns the "normalized" model name for the view.
|
|
283
|
+
|
|
284
|
+
A normalized model name should generally be in the format of a
|
|
285
|
+
Python variable name, e.g. ``'wutta_widget'``. (Note this is
|
|
286
|
+
*singular*, not plural.)
|
|
287
|
+
|
|
288
|
+
The default logic will call :meth:`get_model_name()` and
|
|
289
|
+
simply lower-case the result. A subclass may override by
|
|
290
|
+
assigning :attr:`model_name_normalized`.
|
|
291
|
+
"""
|
|
292
|
+
if hasattr(cls, 'model_name_normalized'):
|
|
293
|
+
return cls.model_name_normalized
|
|
294
|
+
|
|
295
|
+
return cls.get_model_name().lower()
|
|
296
|
+
|
|
297
|
+
@classmethod
|
|
298
|
+
def get_model_title(cls):
|
|
299
|
+
"""
|
|
300
|
+
Returns the "humanized" (singular) model title for the view.
|
|
301
|
+
|
|
302
|
+
The model title will be displayed to the user, so should have
|
|
303
|
+
proper grammar and capitalization, e.g. ``"Wutta Widget"``.
|
|
304
|
+
(Note this is *singular*, not plural.)
|
|
305
|
+
|
|
306
|
+
The default logic will call :meth:`get_model_name()` and use
|
|
307
|
+
the result as-is. A subclass may override by assigning
|
|
308
|
+
:attr:`model_title`.
|
|
309
|
+
"""
|
|
310
|
+
if hasattr(cls, 'model_title'):
|
|
311
|
+
return cls.model_title
|
|
312
|
+
|
|
313
|
+
return cls.get_model_name()
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def get_model_title_plural(cls):
|
|
317
|
+
"""
|
|
318
|
+
Returns the "humanized" (plural) model title for the view.
|
|
319
|
+
|
|
320
|
+
The model title will be displayed to the user, so should have
|
|
321
|
+
proper grammar and capitalization, e.g. ``"Wutta Widgets"``.
|
|
322
|
+
(Note this is *plural*, not singular.)
|
|
323
|
+
|
|
324
|
+
The default logic will call :meth:`get_model_title()` and
|
|
325
|
+
simply add a ``'s'`` to the end. A subclass may override by
|
|
326
|
+
assigning :attr:`model_title_plural`.
|
|
327
|
+
"""
|
|
328
|
+
if hasattr(cls, 'model_title_plural'):
|
|
329
|
+
return cls.model_title_plural
|
|
330
|
+
|
|
331
|
+
model_title = cls.get_model_title()
|
|
332
|
+
return f"{model_title}s"
|
|
333
|
+
|
|
334
|
+
@classmethod
|
|
335
|
+
def get_route_prefix(cls):
|
|
336
|
+
"""
|
|
337
|
+
Returns the "route prefix" for the master view. This prefix
|
|
338
|
+
is used for all named routes defined by the view class.
|
|
339
|
+
|
|
340
|
+
For instance if route prefix is ``'widgets'`` then a view
|
|
341
|
+
might have these routes:
|
|
342
|
+
|
|
343
|
+
* ``'widgets'``
|
|
344
|
+
* ``'widgets.create'``
|
|
345
|
+
* ``'widgets.edit'``
|
|
346
|
+
* ``'widgets.delete'``
|
|
347
|
+
|
|
348
|
+
The default logic will call
|
|
349
|
+
:meth:`get_model_name_normalized()` and simply add an ``'s'``
|
|
350
|
+
to the end, making it plural. A subclass may override by
|
|
351
|
+
assigning :attr:`route_prefix`.
|
|
352
|
+
"""
|
|
353
|
+
if hasattr(cls, 'route_prefix'):
|
|
354
|
+
return cls.route_prefix
|
|
355
|
+
|
|
356
|
+
model_name = cls.get_model_name_normalized()
|
|
357
|
+
return f'{model_name}s'
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def get_url_prefix(cls):
|
|
361
|
+
"""
|
|
362
|
+
Returns the "URL prefix" for the master view. This prefix is
|
|
363
|
+
used for all URLs defined by the view class.
|
|
364
|
+
|
|
365
|
+
Using the same example as in :meth:`get_route_prefix()`, the
|
|
366
|
+
URL prefix would be ``'/widgets'`` and the view would have
|
|
367
|
+
defined routes for these URLs:
|
|
368
|
+
|
|
369
|
+
* ``/widgets/``
|
|
370
|
+
* ``/widgets/new``
|
|
371
|
+
* ``/widgets/XXX/edit``
|
|
372
|
+
* ``/widgets/XXX/delete``
|
|
373
|
+
|
|
374
|
+
The default logic will call :meth:`get_route_prefix()` and
|
|
375
|
+
simply add a ``'/'`` to the beginning. A subclass may
|
|
376
|
+
override by assigning :attr:`url_prefix`.
|
|
377
|
+
"""
|
|
378
|
+
if hasattr(cls, 'url_prefix'):
|
|
379
|
+
return cls.url_prefix
|
|
380
|
+
|
|
381
|
+
route_prefix = cls.get_route_prefix()
|
|
382
|
+
return f'/{route_prefix}'
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
def get_template_prefix(cls):
|
|
386
|
+
"""
|
|
387
|
+
Returns the "template prefix" for the master view. This
|
|
388
|
+
prefix is used to guess which template path to render for a
|
|
389
|
+
given view.
|
|
390
|
+
|
|
391
|
+
Using the same example as in :meth:`get_url_prefix()`, the
|
|
392
|
+
template prefix would also be ``'/widgets'`` and the templates
|
|
393
|
+
assumed for those routes would be:
|
|
394
|
+
|
|
395
|
+
* ``/widgets/index.mako``
|
|
396
|
+
* ``/widgets/create.mako``
|
|
397
|
+
* ``/widgets/edit.mako``
|
|
398
|
+
* ``/widgets/delete.mako``
|
|
399
|
+
|
|
400
|
+
The default logic will call :meth:`get_url_prefix()` and
|
|
401
|
+
return that value as-is. A subclass may override by assigning
|
|
402
|
+
:attr:`template_prefix`.
|
|
403
|
+
"""
|
|
404
|
+
if hasattr(cls, 'template_prefix'):
|
|
405
|
+
return cls.template_prefix
|
|
406
|
+
|
|
407
|
+
return cls.get_url_prefix()
|
|
408
|
+
|
|
409
|
+
##############################
|
|
410
|
+
# configuration
|
|
411
|
+
##############################
|
|
412
|
+
|
|
413
|
+
@classmethod
|
|
414
|
+
def defaults(cls, config):
|
|
415
|
+
"""
|
|
416
|
+
Provide default Pyramid configuration for a master view.
|
|
417
|
+
|
|
418
|
+
This is generally called from within the module's
|
|
419
|
+
``includeme()`` function, e.g.::
|
|
420
|
+
|
|
421
|
+
from wuttaweb.views import MasterView
|
|
422
|
+
|
|
423
|
+
class WidgetView(MasterView):
|
|
424
|
+
model_name = 'Widget'
|
|
425
|
+
|
|
426
|
+
def includeme(config):
|
|
427
|
+
WidgetView.defaults(config)
|
|
428
|
+
|
|
429
|
+
:param config: Reference to the app's
|
|
430
|
+
:class:`pyramid:pyramid.config.Configurator` instance.
|
|
431
|
+
"""
|
|
432
|
+
cls._defaults(config)
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
def _defaults(cls, config):
|
|
436
|
+
route_prefix = cls.get_route_prefix()
|
|
437
|
+
url_prefix = cls.get_url_prefix()
|
|
438
|
+
|
|
439
|
+
# index view
|
|
440
|
+
if cls.listable:
|
|
441
|
+
config.add_route(route_prefix, f'{url_prefix}/')
|
|
442
|
+
config.add_view(cls, attr='index',
|
|
443
|
+
route_name=route_prefix)
|