django-simple-page 1.0.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.
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2020, Thomas Leichtfuß.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the author nor the names of contributors
15
+ may be used to endorse or promote products derived from this software
16
+ without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-simple-page
3
+ Version: 1.0.0
4
+ Author-email: Thomas Leichtfuß <thomas.leichtfuss@posteo.de>
5
+ License: BSD-3-Clause
6
+ Project-URL: Homepage, https://github.com/thomst/django-simple-page
7
+ Project-URL: Repository, https://github.com/thomst/django-simple-page
8
+ Project-URL: Documentation, https://github.com/thomst/django-simple-page#readme
9
+ Keywords: django,django-admin,cms,website
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Django
12
+ Classifier: Framework :: Django :: 3.2
13
+ Classifier: Framework :: Django :: 4.0
14
+ Classifier: Framework :: Django :: 4.1
15
+ Classifier: Framework :: Django :: 4.2
16
+ Classifier: Framework :: Django :: 5.0
17
+ Classifier: Framework :: Django :: 5.1
18
+ Classifier: Framework :: Django :: 5.2
19
+ Classifier: Framework :: Django :: 6.0
20
+ Classifier: Environment :: Web Environment
21
+ Classifier: Intended Audience :: Developers
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Programming Language :: Python
24
+ Classifier: Programming Language :: Python :: 3
25
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
26
+ Classifier: Topic :: Software Development
27
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
+ Requires-Python: >=3.8
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENCE
31
+ Requires-Dist: Django>=3.2
32
+ Requires-Dist: django-mptt
33
+ Requires-Dist: django-model-utils
34
+ Requires-Dist: django-reorder_items_widget
35
+ Provides-Extra: test
36
+ Requires-Dist: Pillow; extra == "test"
37
+ Requires-Dist: coverage; extra == "test"
38
+ Dynamic: license-file
39
+
40
+ # Welcome to django-simple-page
41
+
42
+ [![Tests](https://github.com/thomst/django-simple-page/actions/workflows/tests.yml/badge.svg)](https://github.com/thomst/django-simple-page/actions/workflows/tests.yml)
43
+ [![Coverage Status](https://coveralls.io/repos/github/thomst/django-simple-page/badge.svg?branch=main)](https://coveralls.io/github/thomst/django-simple-page?branch=main)
44
+ [<img src="https://img.shields.io/badge/django-3.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%206.0-orange">](https://img.shields.io/badge/django-3.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%206.0-orange)
45
+
46
+ Django-simple-page is a cms buildkit for your website. The strength of this
47
+ project is its simplicity - using comprehensible yet powerful concepts. You get
48
+ the basic stuff, but retain all your freedom.
49
+
50
+ ## Links
51
+
52
+ - [github](https://github.com/django-simple-page/)
53
+ - [docs](https://thomst.github.io/django-simple-page/)
54
+ - [pypi](https://pypi.org/project/django-simple-page/)
55
+
56
+
57
+ ## Features
58
+
59
+ - **Tree structured Pages**: By django-mptt.
60
+ - **Pages, regions and sections**: Assigning sections to regions on pages.
61
+ - **Custom rendering logic**: Each page or section can have its own renderer.
62
+ - **Simple yet powerful concept**: Everything can be customized by subclassing.
63
+ - **Admin backend integration**: Easy to use. Order elements via drag and drop.
64
+
65
+
66
+ ## Description
67
+
68
+ ### Pages, regions and sections
69
+
70
+ You got a reliable database layout of pages and sections. Sections are
71
+ associated with regions on pages. Everything else is up to you. Sections could
72
+ be anything you want, from a simple content type like an article with title and
73
+ text body to a full featured gallery. You build what you need just by
74
+ subclassing the page and section model.
75
+
76
+ ### Renderer
77
+
78
+ While there are default renderers for pages and sections which are probably
79
+ suitable for most use cases, you are free to completely adapt or overwrite them.
80
+ Each page or section can have its own renderer providing a specific rendering
81
+ logic. And each renderer can have its own Media class defining javascript or css
82
+ files. Those media assets are merged by the page renderer and be available as
83
+ `media` template variable.
84
+
85
+ ### Admin integration
86
+
87
+ At least we provide a handy admin backend integration. Rearrange your pages by
88
+ drag and drop. Add sections to your page regions with inline formsets and
89
+ reorder them by just dragging them to their new position. It's simple and
90
+ sufficient.
91
+
92
+ ### Summing-up
93
+
94
+ As you can see, everything is done by subclassing. While django-simple-page
95
+ giving you the basics to build your website, it is not taking any freedom from
96
+ you. You define your pages with regions, your sections as content, your
97
+ rendering logic with their media classes and put everything together like
98
+ building blocks.
@@ -0,0 +1,59 @@
1
+ # Welcome to django-simple-page
2
+
3
+ [![Tests](https://github.com/thomst/django-simple-page/actions/workflows/tests.yml/badge.svg)](https://github.com/thomst/django-simple-page/actions/workflows/tests.yml)
4
+ [![Coverage Status](https://coveralls.io/repos/github/thomst/django-simple-page/badge.svg?branch=main)](https://coveralls.io/github/thomst/django-simple-page?branch=main)
5
+ [<img src="https://img.shields.io/badge/django-3.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%206.0-orange">](https://img.shields.io/badge/django-3.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%206.0-orange)
6
+
7
+ Django-simple-page is a cms buildkit for your website. The strength of this
8
+ project is its simplicity - using comprehensible yet powerful concepts. You get
9
+ the basic stuff, but retain all your freedom.
10
+
11
+ ## Links
12
+
13
+ - [github](https://github.com/django-simple-page/)
14
+ - [docs](https://thomst.github.io/django-simple-page/)
15
+ - [pypi](https://pypi.org/project/django-simple-page/)
16
+
17
+
18
+ ## Features
19
+
20
+ - **Tree structured Pages**: By django-mptt.
21
+ - **Pages, regions and sections**: Assigning sections to regions on pages.
22
+ - **Custom rendering logic**: Each page or section can have its own renderer.
23
+ - **Simple yet powerful concept**: Everything can be customized by subclassing.
24
+ - **Admin backend integration**: Easy to use. Order elements via drag and drop.
25
+
26
+
27
+ ## Description
28
+
29
+ ### Pages, regions and sections
30
+
31
+ You got a reliable database layout of pages and sections. Sections are
32
+ associated with regions on pages. Everything else is up to you. Sections could
33
+ be anything you want, from a simple content type like an article with title and
34
+ text body to a full featured gallery. You build what you need just by
35
+ subclassing the page and section model.
36
+
37
+ ### Renderer
38
+
39
+ While there are default renderers for pages and sections which are probably
40
+ suitable for most use cases, you are free to completely adapt or overwrite them.
41
+ Each page or section can have its own renderer providing a specific rendering
42
+ logic. And each renderer can have its own Media class defining javascript or css
43
+ files. Those media assets are merged by the page renderer and be available as
44
+ `media` template variable.
45
+
46
+ ### Admin integration
47
+
48
+ At least we provide a handy admin backend integration. Rearrange your pages by
49
+ drag and drop. Add sections to your page regions with inline formsets and
50
+ reorder them by just dragging them to their new position. It's simple and
51
+ sufficient.
52
+
53
+ ### Summing-up
54
+
55
+ As you can see, everything is done by subclassing. While django-simple-page
56
+ giving you the basics to build your website, it is not taking any freedom from
57
+ you. You define your pages with regions, your sections as content, your
58
+ rendering logic with their media classes and put everything together like
59
+ building blocks.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-simple-page
3
+ Version: 1.0.0
4
+ Author-email: Thomas Leichtfuß <thomas.leichtfuss@posteo.de>
5
+ License: BSD-3-Clause
6
+ Project-URL: Homepage, https://github.com/thomst/django-simple-page
7
+ Project-URL: Repository, https://github.com/thomst/django-simple-page
8
+ Project-URL: Documentation, https://github.com/thomst/django-simple-page#readme
9
+ Keywords: django,django-admin,cms,website
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Django
12
+ Classifier: Framework :: Django :: 3.2
13
+ Classifier: Framework :: Django :: 4.0
14
+ Classifier: Framework :: Django :: 4.1
15
+ Classifier: Framework :: Django :: 4.2
16
+ Classifier: Framework :: Django :: 5.0
17
+ Classifier: Framework :: Django :: 5.1
18
+ Classifier: Framework :: Django :: 5.2
19
+ Classifier: Framework :: Django :: 6.0
20
+ Classifier: Environment :: Web Environment
21
+ Classifier: Intended Audience :: Developers
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Programming Language :: Python
24
+ Classifier: Programming Language :: Python :: 3
25
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
26
+ Classifier: Topic :: Software Development
27
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
+ Requires-Python: >=3.8
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENCE
31
+ Requires-Dist: Django>=3.2
32
+ Requires-Dist: django-mptt
33
+ Requires-Dist: django-model-utils
34
+ Requires-Dist: django-reorder_items_widget
35
+ Provides-Extra: test
36
+ Requires-Dist: Pillow; extra == "test"
37
+ Requires-Dist: coverage; extra == "test"
38
+ Dynamic: license-file
39
+
40
+ # Welcome to django-simple-page
41
+
42
+ [![Tests](https://github.com/thomst/django-simple-page/actions/workflows/tests.yml/badge.svg)](https://github.com/thomst/django-simple-page/actions/workflows/tests.yml)
43
+ [![Coverage Status](https://coveralls.io/repos/github/thomst/django-simple-page/badge.svg?branch=main)](https://coveralls.io/github/thomst/django-simple-page?branch=main)
44
+ [<img src="https://img.shields.io/badge/django-3.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%206.0-orange">](https://img.shields.io/badge/django-3.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2%20%7C%206.0-orange)
45
+
46
+ Django-simple-page is a cms buildkit for your website. The strength of this
47
+ project is its simplicity - using comprehensible yet powerful concepts. You get
48
+ the basic stuff, but retain all your freedom.
49
+
50
+ ## Links
51
+
52
+ - [github](https://github.com/django-simple-page/)
53
+ - [docs](https://thomst.github.io/django-simple-page/)
54
+ - [pypi](https://pypi.org/project/django-simple-page/)
55
+
56
+
57
+ ## Features
58
+
59
+ - **Tree structured Pages**: By django-mptt.
60
+ - **Pages, regions and sections**: Assigning sections to regions on pages.
61
+ - **Custom rendering logic**: Each page or section can have its own renderer.
62
+ - **Simple yet powerful concept**: Everything can be customized by subclassing.
63
+ - **Admin backend integration**: Easy to use. Order elements via drag and drop.
64
+
65
+
66
+ ## Description
67
+
68
+ ### Pages, regions and sections
69
+
70
+ You got a reliable database layout of pages and sections. Sections are
71
+ associated with regions on pages. Everything else is up to you. Sections could
72
+ be anything you want, from a simple content type like an article with title and
73
+ text body to a full featured gallery. You build what you need just by
74
+ subclassing the page and section model.
75
+
76
+ ### Renderer
77
+
78
+ While there are default renderers for pages and sections which are probably
79
+ suitable for most use cases, you are free to completely adapt or overwrite them.
80
+ Each page or section can have its own renderer providing a specific rendering
81
+ logic. And each renderer can have its own Media class defining javascript or css
82
+ files. Those media assets are merged by the page renderer and be available as
83
+ `media` template variable.
84
+
85
+ ### Admin integration
86
+
87
+ At least we provide a handy admin backend integration. Rearrange your pages by
88
+ drag and drop. Add sections to your page regions with inline formsets and
89
+ reorder them by just dragging them to their new position. It's simple and
90
+ sufficient.
91
+
92
+ ### Summing-up
93
+
94
+ As you can see, everything is done by subclassing. While django-simple-page
95
+ giving you the basics to build your website, it is not taking any freedom from
96
+ you. You define your pages with regions, your sections as content, your
97
+ rendering logic with their media classes and put everything together like
98
+ building blocks.
@@ -0,0 +1,19 @@
1
+ LICENCE
2
+ README.md
3
+ pyproject.toml
4
+ django_simple_page.egg-info/PKG-INFO
5
+ django_simple_page.egg-info/SOURCES.txt
6
+ django_simple_page.egg-info/dependency_links.txt
7
+ django_simple_page.egg-info/requires.txt
8
+ django_simple_page.egg-info/top_level.txt
9
+ simple_page/__init__.py
10
+ simple_page/__version__.py
11
+ simple_page/admin.py
12
+ simple_page/apps.py
13
+ simple_page/forms.py
14
+ simple_page/models.py
15
+ simple_page/renderer.py
16
+ simple_page/signals.py
17
+ simple_page/views.py
18
+ simple_page/templates/admin/simple_page/page/change_form.html
19
+ simple_page/templates/simple_page/menu.html
@@ -0,0 +1,8 @@
1
+ Django>=3.2
2
+ django-mptt
3
+ django-model-utils
4
+ django-reorder_items_widget
5
+
6
+ [test]
7
+ Pillow
8
+ coverage
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "django-simple-page"
7
+ authors = [{name = "Thomas Leichtfuß", email = "thomas.leichtfuss@posteo.de"}]
8
+ description = ""
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ keywords = ["django", "django-admin", "cms", "website"]
12
+ license = { text = "BSD-3-Clause"}
13
+ classifiers = [
14
+ "Development Status :: 5 - Production/Stable",
15
+ "Framework :: Django",
16
+ "Framework :: Django :: 3.2",
17
+ "Framework :: Django :: 4.0",
18
+ "Framework :: Django :: 4.1",
19
+ "Framework :: Django :: 4.2",
20
+ "Framework :: Django :: 5.0",
21
+ "Framework :: Django :: 5.1",
22
+ "Framework :: Django :: 5.2",
23
+ "Framework :: Django :: 6.0",
24
+ "Environment :: Web Environment",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python",
28
+ "Programming Language :: Python :: 3",
29
+ "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
30
+ "Topic :: Software Development",
31
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
32
+ ]
33
+ dependencies = [
34
+ "Django>=3.2",
35
+ "django-mptt",
36
+ "django-model-utils",
37
+ "django-reorder_items_widget",
38
+ ]
39
+ dynamic = ["version"]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/thomst/django-simple-page"
43
+ Repository = "https://github.com/thomst/django-simple-page"
44
+ Documentation = "https://github.com/thomst/django-simple-page#readme"
45
+
46
+ [tool.setuptools]
47
+ packages = ["simple_page"]
48
+ include-package-data = false
49
+
50
+ [tool.setuptools.package-data]
51
+ simple_page = ["templates/**"]
52
+
53
+ [tool.setuptools.dynamic]
54
+ version = {attr = "simple_page.__version__.__version__"}
55
+
56
+ [project.optional-dependencies]
57
+ test = [
58
+ "Pillow",
59
+ "coverage",
60
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,75 @@
1
+ """
2
+ This is django-simple-page
3
+ ==========================
4
+
5
+ Django-simple-page is a cms buildkit for your website. The strength of this
6
+ project is its simplicity - using comprehensible yet powerful concepts. You get
7
+ the basic stuff, but retain all your freedom.
8
+
9
+ Features
10
+ ========
11
+
12
+ - **Tree structured Pages**: By `django-mptt`_.
13
+ - **Pages and sections**: Assigning sections to regions on pages.
14
+ - **Renderer**: Each page or section can have its own renderer.
15
+ - **Simple yet powerful concept**: Everything can be customized by subclassing.
16
+ - **Admin backend integration**: Easy to use. Order elements via drag and drop.
17
+
18
+ .. _django-mptt: https://django-mptt.readthedocs.io/en/latest/
19
+
20
+ Basic Concept
21
+ =============
22
+
23
+ Pages and sections
24
+ ------------------
25
+
26
+ You got a reliable database layout of :class:`pages <.models.Page>` and
27
+ :class:`sections <.models.Section>` objects. Sections are associated with
28
+ regions on pages. Everything else is up to you. Sections could be anything you
29
+ want, from a simple content type like an article with title and text body to a
30
+ full featured gallery. You build what you need just by subclassing the page and
31
+ section model.
32
+
33
+ Renderer
34
+ --------
35
+
36
+ While there are default :mod:`renderers <.renderer>` for pages and sections
37
+ which are probably suitable for most use cases, you are free to completely adapt
38
+ or overwrite them. Each page or section can have its own renderer providing a
39
+ specific rendering logic. And each renderer can have its own Media class
40
+ defining javascript or css files. Those media assets are merged by the page
41
+ renderer and be available as `media` template variable.
42
+
43
+ Summing-up
44
+ ----------
45
+
46
+ As you can see, everything is done by subclassing. While django-simple-page
47
+ giving you the basics to build your website, it is not taking any freedom from
48
+ you. You define your pages with regions, your sections as content, your
49
+ rendering logic with their media classes and put everything together like
50
+ building blocks.
51
+
52
+
53
+ Admin integration
54
+ =================
55
+
56
+ We provide a handy admin backend integration. Rearrange your pages by drag and
57
+ drop. Add sections to your page regions with inline formsets and reorder them by
58
+ just dragging them to their new position. It's simple and sufficient.
59
+
60
+
61
+ Utils
62
+ =====
63
+
64
+ Menu template tag
65
+ -----------------
66
+ We provide an inclusion template tag to generate a tree based menu using nested
67
+ lists. Still you are free to build your own menu logic or customize the default
68
+ menu template to your needs. See :func:`~.templatetags.simple_page.menu` for
69
+ more details.
70
+
71
+ Page view
72
+ ---------
73
+ A simple view function to render a page by its slug. Use it in your url
74
+ configuration. See :func:`~.views.page_view` for more details.
75
+ """
@@ -0,0 +1,16 @@
1
+ """
2
+ This project uses the Semantic Versioning scheme in conjunction with PEP 0440:
3
+ <http://semver.org/>
4
+ <https://www.python.org/dev/peps/pep-0440>
5
+
6
+ Major versions introduce significant changes to the API, and backwards
7
+ compatibility is not guaranteed. Minor versions are for new features and other
8
+ backwards-compatible changes to the API. Patch versions are for bug fixes and
9
+ internal code changes that do not affect the API. Development versions are
10
+ incomplete states of a release .
11
+
12
+ Version 0.x should be considered a development version with an unstable API,
13
+ and backwards compatibility is not guaranteed for minor versions.
14
+ """
15
+
16
+ __version__ = "1.0.0"
@@ -0,0 +1,199 @@
1
+ """
2
+ The admin integration for django-simple-page is realized by a customized
3
+ :class:`page modeladmin <.PageAdmin>`. This modeladmin is registered for the
4
+ page model and will be used for all proxy page models. It provides the following
5
+ features:
6
+
7
+ - Let pages be orderable by drag and drop in the admin changelist view.
8
+ - Let the user choose the type of the page before rendering the page's add form.
9
+ - Set the initial value of the hidden page_type field in the page add form.
10
+ - Render an inline formset for each region of the page which allows to assign
11
+ sections to the region and rearrange them via drag and drop.
12
+
13
+ For your own concrete page models you should use :class:`~.admin.BasePageAdmin`
14
+ as a base class for your modeladmin. It will take care of rendering the inline
15
+ formsets for regions and setting the appropriate value for the hidden page_type
16
+ field.
17
+ """
18
+
19
+ from django.contrib import admin
20
+ from django.forms import HiddenInput
21
+ from django.utils.functional import cached_property
22
+ from django.utils.html import mark_safe
23
+ from django.urls import reverse
24
+ from django.contrib.contenttypes.models import ContentType
25
+ from django.utils.translation import gettext as _
26
+ from mptt.admin import DraggableMPTTAdmin
27
+ from .models import Page, PageSection
28
+ from .forms import ReorderRelationForm
29
+
30
+
31
+ class BaseRegionInline(admin.TabularInline):
32
+ """
33
+ Inline for the :class:`~.models.PageSection` model. Each region of a page
34
+ gets its own inline table with sections belonging to that region. We use
35
+ the django-reorder-items-widget_ to make the sections orderable via drag and
36
+ drop within their region.
37
+
38
+ .. _django-reorder-items-widget: https://github.com/thomst/django-reorder-items-widget
39
+ """
40
+ region_name = None
41
+ form = ReorderRelationForm
42
+ model = PageSection
43
+ extra = 1
44
+ fields = ("section", "index", "region")
45
+
46
+ def get_queryset(self, request):
47
+ qs = super().get_queryset(request)
48
+ return qs.filter(region=self.region_name)
49
+
50
+ class Media:
51
+ js = ["simple_page/formset_handlers.js"]
52
+
53
+
54
+ class GetPageModelMixin:
55
+ """
56
+ A mixin to get the page model based on a page_type url query parameter or
57
+ the page_type field of the current page object.
58
+ """
59
+
60
+ @cached_property
61
+ def page_types(self):
62
+ """
63
+ Return content types of all page models.
64
+ """
65
+ exclude_apps = ["admin", "auth", "contenttypes", "sessions", "simple_page"]
66
+ cts = ContentType.objects.exclude(app_label__in=exclude_apps)
67
+ return [ct for ct in cts if ct.model_class() and issubclass(ct.model_class(), Page)]
68
+
69
+ def get_page_model(self, request, obj=None):
70
+ """
71
+ Return the page model based on the request and object.
72
+ """
73
+ if obj:
74
+ return obj.page_type.model_class()
75
+ elif 'page_type' in request.GET:
76
+ page_type_id = request.GET['page_type']
77
+ try:
78
+ page_type = [ct for ct in self.page_types if ct.id == int(page_type_id)][0]
79
+ except (IndexError, ValueError):
80
+ raise ValueError(f"Invalid page type id: {page_type_id}")
81
+ else:
82
+ return page_type.model_class()
83
+ else:
84
+ return self.model
85
+
86
+
87
+ class RenderPageRegionsMixin(GetPageModelMixin):
88
+ """
89
+ Render a :class:`~.models.PageSection` inline formset for each region of the
90
+ page. Also make sure extra forms have the region's name as initial value for
91
+ the region form field.
92
+ """
93
+
94
+ def get_page_regions(self, request, obj):
95
+ return self.get_page_model(request, obj).get_regions()
96
+
97
+ def get_formset_kwargs(self, request, obj, inline, prefix):
98
+ kwargs = super().get_formset_kwargs(request, obj, inline, prefix)
99
+ if isinstance(inline, BaseRegionInline):
100
+ kwargs["initial"] = [
101
+ {"region": inline.region_name}
102
+ for i in range(inline.extra)
103
+ ]
104
+ return kwargs
105
+
106
+ def get_inlines(self, request, obj):
107
+ inlines = list(super().get_inlines(request, obj))
108
+ regions = self.get_page_regions(request, obj)
109
+ for region, title in regions:
110
+ class_name = f"{region.capitalize()}Inline"
111
+ attrs = dict(
112
+ region_name=region,
113
+ verbose_name=title,
114
+ verbose_name_plural=title,
115
+ )
116
+ inlines.append(type(class_name, (BaseRegionInline,), attrs))
117
+ return inlines
118
+
119
+
120
+ class ChoosePageTypeMixin(GetPageModelMixin):
121
+ """
122
+ Let the user choose the type of the page she wants to add. Therefore render
123
+ a simple list of add-links which either set the page_type url-query
124
+ parameter for proxy page models or link to the modeladmin changeform
125
+ for concrete page models.
126
+ """
127
+
128
+ def render_change_form(self, request, context, add=False, change=False, form_url="", obj=None):
129
+ # Set the title of the change form based on the page type.
130
+ if add or change:
131
+ title = _("Add %s") if add else _("Change %s")
132
+ else:
133
+ title = _("View %s")
134
+ page_model = self.get_page_model(request, obj)
135
+ context["title"] = title % page_model._meta.verbose_name
136
+ return super().render_change_form(request, context, add, change, form_url, obj)
137
+
138
+ def add_view(self, request, form_url="", extra_context=None):
139
+ # Add page types to the context to render a list of add links for each
140
+ # page type. Using their content type id in the query string.
141
+ if "page_type" not in request.GET:
142
+ extra_context = extra_context or {}
143
+ extra_context["page_types"] = []
144
+ for ct in self.page_types:
145
+ name = ct.model_class()._meta.verbose_name
146
+ if ct.model_class()._meta.proxy:
147
+ url = f"?page_type={ct.id}"
148
+ else:
149
+ url = reverse(f"admin:{ct.app_label}_{ct.model}_add")
150
+ extra_context["page_types"].append((url, name))
151
+ return super().add_view(request, form_url, extra_context)
152
+
153
+
154
+ class SetPageTypeMixin(GetPageModelMixin):
155
+ """
156
+ Set the initial value of the hidden page_type field in changeforms when
157
+ adding a new page.
158
+ """
159
+
160
+ def get_form(self, request, obj=None, **kwargs):
161
+ form = super().get_form(request, obj, **kwargs)
162
+ page_model = self.get_page_model(request, obj)
163
+ form.base_fields["page_type"].initial = ContentType.objects.get_for_model(page_model)
164
+ form.base_fields["page_type"].widget = HiddenInput()
165
+ return form
166
+
167
+
168
+ @admin.register(Page)
169
+ class PageAdmin(SetPageTypeMixin, ChoosePageTypeMixin, RenderPageRegionsMixin, DraggableMPTTAdmin):
170
+ """
171
+ The modeladmin for all proxy page models. This modeladmin is already
172
+ registered for the page model. It provides the following features:
173
+
174
+ - Let pages be orderable by drag and drop in the admin changelist view.
175
+ - Let the user choose the type of the page before rendering the page's add
176
+ form.
177
+ - Set the initial value of the hidden page_type field in the page add form.
178
+ - Render an inline formset for each region of the page.
179
+ """
180
+ list_display = ("tree_actions", "indented_title", "slug", "page_type", "view_page_link")
181
+ list_display_links=('indented_title',)
182
+ search_fields = ("title", "slug")
183
+ prepopulated_fields = {"slug": ("title",)}
184
+ list_filter = ("parent",)
185
+
186
+ def view_page_link(self, obj):
187
+ url = obj.get_absolute_url()
188
+ return mark_safe(f'<a href="{url}" target="_blank">View page</a>')
189
+ view_page_link.short_description = "View page"
190
+
191
+
192
+ class BasePageAdmin(SetPageTypeMixin, RenderPageRegionsMixin, admin.ModelAdmin):
193
+ """
194
+ Base class for modeladmins for concrete page models. It provides the
195
+ following features:
196
+
197
+ - Set the initial value of the hidden page_type field in the page add form.
198
+ - Render an inline formset for each region of the page.
199
+ """
@@ -0,0 +1,11 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class SimplePageConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'simple_page'
7
+ verbose_name = "Simple Page"
8
+
9
+
10
+ def ready(self):
11
+ import simple_page.signals
@@ -0,0 +1,13 @@
1
+ from django.forms import ModelForm, HiddenInput
2
+ from reorder_items_widget import ReorderItemsWidget
3
+
4
+
5
+ class ReorderRelationForm(ModelForm):
6
+ """
7
+ A ModelForm using the ReorderItemsWidget with an index field.
8
+ """
9
+ class Meta:
10
+ widgets = {
11
+ 'index': ReorderItemsWidget(attrs={'class': 'hidden'}),
12
+ 'region': HiddenInput(),
13
+ }
@@ -0,0 +1,192 @@
1
+ """
2
+ Pages and sections are the basic building blocks of your website. Pages define
3
+ regions in which sections can be placed. And sections can be any kind of content
4
+ you want to see on your website.
5
+
6
+ Pages and sections are defined by subclassing the :class:`~.models.Page` and
7
+ :class:`~.models.Section` model::
8
+
9
+ from simple_page.models import Page, Section
10
+
11
+ class FancyPage(Page):
12
+ REGIONS = [
13
+ ('main', 'Main Region'),
14
+ ('sidebar', 'Sidebar'),
15
+ ('footer', 'Footer'),
16
+ ]
17
+
18
+ class Meta:
19
+ proxy = True
20
+
21
+
22
+ class FancySection(Section):
23
+ title = models.CharField(max_length=255, blank=True)
24
+ text = models.TextField(blank=True)
25
+
26
+
27
+ With those two models you are able to build a simple website.
28
+ """
29
+
30
+ from mptt.models import MPTTModel, TreeForeignKey
31
+ from model_utils.managers import InheritanceManager
32
+
33
+ from django.db import models
34
+ from django.urls import reverse
35
+ from django.contrib.contenttypes.models import ContentType
36
+
37
+
38
+ class Section(models.Model):
39
+ """
40
+ Base model for what ever content you want to see on your website. It does
41
+ not has any fields by its own but can be equipped by sublcasses.
42
+
43
+ Sections are related to pages via a many-to-many relationship that holds the
44
+ region in which a section should be rendered and an index field to make the
45
+ sections orderable whithin that region.
46
+ """
47
+
48
+ objects = InheritanceManager()
49
+ """
50
+ We use the `InheritanceManager`_ to provide a simple api to access child
51
+ class objects.
52
+
53
+ .. _InheritanceManager: https://django-model-utils.readthedocs.io/en/latest/managers.html#inheritancemanager
54
+ """
55
+
56
+ def __str__(self):
57
+ if type(self) is Section:
58
+ child_self = self._meta.model.objects.get_subclass(id=self.id)
59
+ return f"{child_self._meta.verbose_name}: {child_self}"
60
+ else:
61
+ # FIXME: This leads to a recursive call if child_self is a Section
62
+ # as well. This happens when for any reason a section object has no
63
+ # child class.
64
+ return super().__str__()
65
+
66
+
67
+ class Page(MPTTModel):
68
+ """
69
+ Base model for all pages.
70
+
71
+ The only thing a subclass has to do is to setup its :attr:`regions
72
+ <.Page.REGIONS>`. Since the database layout is fully functional, you may
73
+ define your own page model as a proxy if you do not want to provide
74
+ additional fields.
75
+
76
+ Sections associated with a page are accessible by their region. Use the
77
+ region's name to get a queryset of sections belonging to that region.
78
+
79
+ The page model is tree structured by `django-mptt`_.
80
+
81
+ .. _django-mptt: https://django-mptt.readthedocs.io/en/latest/
82
+ """
83
+
84
+ # FIXME: We should use REGIONS = None and raise a NotImplementedError. But
85
+ # tests are failing, since for any reason the get_regions method is called
86
+ # on a Page objects occacionally.
87
+ REGIONS = []
88
+ """
89
+ REGIONS must be set by subclasses as a list of tuples holding the region's
90
+ name and its title. Something like::
91
+
92
+ REGIONS = [
93
+ ('main', 'Main Region'),
94
+ ('sidebar', 'Sidebar'),
95
+ ('footer', 'Footer'),
96
+ ]
97
+ """
98
+
99
+ @classmethod
100
+ def get_regions(cls):
101
+ """
102
+ Return the regions for this page. This method can be customized by child
103
+ classes to return different regions.
104
+ """
105
+ return cls.REGIONS
106
+
107
+ def resolve_obj(self):
108
+ """
109
+ Return the instance of the child class.
110
+ """
111
+ model = self.page_type.model_class()
112
+ return model.objects.get(id=self.id)
113
+
114
+ title = models.CharField(max_length=255)
115
+ slug = models.SlugField(unique=True)
116
+ page_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
117
+ sections = models.ManyToManyField(
118
+ Section,
119
+ through="PageSection",
120
+ related_name="pages",
121
+ blank=True,
122
+ )
123
+ parent = TreeForeignKey(
124
+ "self",
125
+ null=True,
126
+ blank=True,
127
+ related_name="children",
128
+ on_delete=models.SET_NULL,
129
+ )
130
+
131
+ def get_absolute_url(self):
132
+ return reverse("page", kwargs={"slug": self.slug})
133
+
134
+ def __str__(self):
135
+ return self.title
136
+
137
+ def __getattr__(self, name):
138
+ """
139
+ If the attribute name is a region return its sections, otherwise raise
140
+ AttributeError.
141
+ """
142
+ if name in [region for region, _ in self.get_regions()]:
143
+ sections = self.sections.filter(pagesection__region=name)
144
+ return sections.select_subclasses().order_by("pagesection__index")
145
+ else:
146
+ msg = f"{self.__class__.__name__} object has no attribute '{name}'"
147
+ raise AttributeError(msg)
148
+
149
+
150
+ class UpdateIndexesManager(models.Manager):
151
+ """
152
+ Provide methods to set the index of an newly saved object and to fix
153
+ indexes of a set of items from which one was deleted.
154
+ """
155
+ def set_index(self, obj):
156
+ """
157
+ If an object is about to be added we give him the next higher
158
+ index. Call this method from a pre-save signal handler.
159
+ """
160
+ items = self.filter(page=obj.page, region=obj.region)
161
+ max_index = items.aggregate(models.Max('index'))['index__max'] or 0
162
+ obj.index = max_index + 1
163
+
164
+ def update_indexes(self, obj):
165
+ """
166
+ If an object was deleted fix the indexes of following objects. Call this
167
+ method from a post-delete signal handler.
168
+ """
169
+ items = self.filter(page=obj.page, region=obj.region)
170
+ for item in items.filter(index__gt=obj.index):
171
+ item.index -= 1
172
+ item.save()
173
+
174
+
175
+ class PageSection(models.Model):
176
+ """
177
+ PageSection is the intermediate model for the many-to-many relationship
178
+ between pages and sections. It holds the region in which a section should be
179
+ rendered and an index field to make sections orderable whithin that region.
180
+ """
181
+ objects = UpdateIndexesManager()
182
+
183
+ page = models.ForeignKey(Page, on_delete=models.CASCADE)
184
+ section = models.ForeignKey(Section, on_delete=models.CASCADE)
185
+ region = models.CharField('Region', max_length=255)
186
+ index = models.SmallIntegerField(blank=True)
187
+
188
+ class Meta:
189
+ ordering = ["page__id", "index"]
190
+
191
+ def __str__(self):
192
+ return f"{self.section}"
@@ -0,0 +1,294 @@
1
+ """
2
+ To build HTML for a page or section object a renderer class is used. While a
3
+ section renderer produces a html snippet representing the section object, a page
4
+ renderer provides a full html document for a page. Including all its sections.
5
+
6
+ Nevertheless, both renderers are based on the same concept, using the proven
7
+ triad of `get_template_name`, `get_context` and `render` methods.
8
+
9
+ There is a default renderer for pages as well as for sections. Which are
10
+ probably sufficient for most use cases. Still you are free to write your own
11
+ renderer classes and :func:`~.register` them for your page and section models.
12
+ The only thing a renderer class has to provide is a `render` method returning
13
+ valid HTML.
14
+
15
+ Renderer classes using django's :class:`~django.forms.MediaDefiningClass` as
16
+ metaclass. They can be equipped with a `Media` classes like django's forms and
17
+ widgets::
18
+
19
+ class FancySectionRenderer(SectionRenderer):
20
+ class Media:
21
+ css = dict(all=['fancy_section.css'])
22
+ js = ['fancy_section.js']
23
+
24
+ It is the responsibility of the page renderer to merge the media
25
+ definitions of all renderers involved and provide them as a `media` template
26
+ variable. For more details see :meth:`~.PageRenderer.get_media_assets`.
27
+ """
28
+
29
+ import re
30
+ from django.template.loader import get_template
31
+ from django.forms.widgets import MediaDefiningClass
32
+ from .models import Page
33
+
34
+
35
+ REGISTRY = dict()
36
+
37
+ def register(model_cls, renderer_cls=None, context=None):
38
+ """
39
+ Register a :class:`renderer class <.BaseRenderer>` for a page or section
40
+ model. This function can also be used as a decorator for your renderer
41
+ class::
42
+
43
+ @renderer.register(FancyPage)
44
+ class FancyPageRenderer(PageRenderer):
45
+ ...
46
+
47
+ :class:`Section renderer <.SectionRenderer>` can be applied context
48
+ specific. A context can be a page type, a region name or a tuple of page
49
+ type and region name::
50
+
51
+ @renderer.register(FancySection, context='main')
52
+ class MainRegionFancySectionRenderer(SectionRenderer):
53
+ ...
54
+
55
+ or::
56
+
57
+ @renderer.register(FancySection, context=(FancyPage, 'main'))
58
+ class FancyPageMainRegionFancySectionRenderer(SectionRenderer):
59
+ ...
60
+
61
+ This allows you to use different renderers depending on where a section
62
+ appears. See :func:`~.get_section_renderer` for more details about how a
63
+ renderer will be choosen.
64
+
65
+ :param model_cls: model to be rendered
66
+ :type model_cls: :class:`~.models.Page` or :class:`~.models.Section`
67
+ :param renderer_cls: renderer class
68
+ :type renderer_cls: :class:`~.PageRenderer` or :class:`~.SectionRenderer`
69
+ :param context: context where a section renderer should be applied
70
+ :type context: :class:`~.models.Page` or str or tuple of both, optional
71
+ """
72
+ def _register(renderer_cls):
73
+ if issubclass(model_cls, Page):
74
+ REGISTRY[model_cls] = renderer_cls
75
+ else:
76
+ REGISTRY[model_cls] = REGISTRY.get(model_cls) or dict()
77
+ REGISTRY[model_cls][context] = renderer_cls
78
+ return model_cls
79
+
80
+ # Usage as function.
81
+ if renderer_cls:
82
+ _register(renderer_cls)
83
+
84
+ # Usage as decorator.
85
+ else:
86
+ return _register
87
+
88
+
89
+ def get_page_renderer(page):
90
+ """
91
+ Return the registered renderer for the page or :class:`~.PageRenderer`.
92
+
93
+ :param page: page instance to be rendered
94
+ :type page: :class:`~.models.Page`
95
+ :return: renderer class
96
+ :rtype: :class:`~.PageRenderer`
97
+ """
98
+ return REGISTRY.get(type(page), PageRenderer)
99
+
100
+
101
+ def get_section_renderer(section, page=None, region=None):
102
+ """
103
+ Return a renderer instance for the section.
104
+
105
+ We look for a registered renderer in this order:
106
+
107
+ * page-type and region specific
108
+ * region specific
109
+ * page-type specific
110
+ * neither page-type nor region specific
111
+
112
+ The first one found will be returned. Otherwise the
113
+ :class:`~.SectionRenderer` is used as fallback.
114
+
115
+ :param obj: section instance
116
+ :type obj: :class:`~.models.Section`
117
+ :param page: page the section will be rendered for
118
+ :type page: :class:`~.models.Page`
119
+ :param str region: region the section will be rendered in
120
+ :return: renderer class
121
+ :rtype: :class:`~.SectionRenderer`
122
+ """
123
+ if type(section) in REGISTRY:
124
+ # One of these keys must have been used to register a renderer class.
125
+ for key in [(type(page), region), region, type(page), None]:
126
+ if key in REGISTRY[type(section)]:
127
+ return REGISTRY[type(section)][key]
128
+ else:
129
+ return SectionRenderer
130
+
131
+
132
+ class BaseRenderer(metaclass=MediaDefiningClass):
133
+ """
134
+ Base renderer class.
135
+ """
136
+
137
+ template_name = None
138
+ """
139
+ Template name. Default is None. See :meth:`~.get_template_name`.
140
+ """
141
+
142
+ def __init__(self, obj, request=None, **kwargs):
143
+ """
144
+ Initialize the renderer.
145
+
146
+ :param obj: object to be rendered
147
+ :type obj: :class:`~.models.Page` or :class:`~.models.Section`
148
+ :param request: request object (default: None)
149
+ :type request: :class:`~django.http.HttpRequest`
150
+ :param kwargs: Additional data as keyword arguments (default: dict())
151
+ """
152
+ self.obj = obj
153
+ self.request = request
154
+ self.kwargs = kwargs
155
+
156
+ def render(self):
157
+ """
158
+ Return the rendered HTML using the template and context returned by
159
+ :meth:`~.get_template_name` and :meth:`~.get_context` methods.
160
+ """
161
+ template = get_template(self.get_template_name())
162
+ context = self.get_context()
163
+ return template.render(context)
164
+
165
+
166
+ class SectionRenderer(BaseRenderer):
167
+ """
168
+ Renderer for Section instances.
169
+ """
170
+ # TODO: Use a get_template method instead.
171
+ def get_template_name(self):
172
+ """
173
+ Return template name. If :attr:`~.template_name` is set it will be
174
+ returned. Otherwise the template name will be constructed as follows:
175
+
176
+ "sections/<section_class_name_in_snake_case>.html"
177
+ """
178
+ if self.template_name:
179
+ return self.template_name
180
+ else:
181
+ cls_name = self.obj.__class__.__name__
182
+ template_name = re.sub(r'(?<!^)(?=[A-Z])', '_', cls_name).lower()
183
+ return f'sections/{template_name}.html'
184
+
185
+ def get_context(self):
186
+ """
187
+ Build and return rendering context:
188
+
189
+ - `section`: section object
190
+
191
+ :return: rendering context
192
+ :rtype: dict
193
+ """
194
+ context = self.kwargs.get('extra_context', dict())
195
+ context['section'] = self.obj
196
+ return context
197
+
198
+
199
+ class PageRenderer(BaseRenderer):
200
+ """
201
+ Renderer for Page instances.
202
+ """
203
+ def get_template_name(self):
204
+ """
205
+ Return template name. If :attr:`~.template_name` is set it will be
206
+ returned. Otherwise the template name will be constructed as follows:
207
+
208
+ "pages/<page_class_name_in_snake_case>.html"
209
+ """
210
+ if self.template_name:
211
+ return self.template_name
212
+ else:
213
+ cls_name = self.obj.__class__.__name__
214
+ template_name = re.sub(r'(?<!^)(?=[A-Z])', '_', cls_name).lower()
215
+ return f'pages/{template_name}.html'
216
+
217
+ def get_section_data(self, section, region):
218
+ """
219
+ Build and return a dictionary holding the section's data:
220
+
221
+ - `obj`: section object itself
222
+ - `html`: section's html build by the renderer returned by :func:`~.get_section_renderer`
223
+
224
+ :param section: section object
225
+ :type section: :class:`~.models.Section`
226
+ :param str region: region name
227
+ :return: section data holding the section object and the rendered html
228
+ :rtype: dict
229
+ """
230
+ renderer_cls = get_section_renderer(section, self.obj, region)
231
+ renderer = renderer_cls(section, self.request, **self.kwargs)
232
+ return dict(
233
+ obj=section,
234
+ html=renderer.render()
235
+ )
236
+
237
+ def get_region_data(self, region, title):
238
+ """
239
+ Build and return a dictionary holding the region's data:
240
+
241
+ - `name`: region name
242
+ - `title`: region title
243
+ - `sections`: list of section data build by :meth:`~.get_section_data`
244
+
245
+ :param str region: region name
246
+ :param str tilte: region title
247
+ :return: region data holding title, name and sections for this region
248
+ :rtype: dict
249
+ """
250
+ region_data = {'title': title, 'name': region, 'sections': []}
251
+ for section in getattr(self.obj, region):
252
+ section_data = self.get_section_data(section, region)
253
+ region_data['sections'].append(section_data)
254
+ return region_data
255
+
256
+ def get_media_assets(self):
257
+ """
258
+ Merge media definitions of the page's and all sections' renderers.
259
+ Return them as string.
260
+
261
+ :return str: merged media assets
262
+ """
263
+ media = get_page_renderer(self.obj)(self.obj).media
264
+ for region, _ in self.obj.get_regions():
265
+ for section in getattr(self.obj, region):
266
+ section_renderer = get_section_renderer(section, self.obj, region)
267
+ media += section_renderer(section).media
268
+ return str(media)
269
+
270
+ def get_context(self):
271
+ """
272
+ Build rendering context:
273
+
274
+ - `page`: page object
275
+ - `media`: media assets build by :meth:`~.get_media_assets`
276
+ - `regions`: mapping of region names to their data build by
277
+ :meth:`~.get_region_data`
278
+
279
+ As a shortcut each region data will also be added using the region's
280
+ name as an own context variable.
281
+
282
+ :return: rendering context
283
+ :rtype: dict
284
+ """
285
+ # Add regions, sections and media to the context.
286
+ context = self.kwargs.get('extra_context', dict())
287
+ context['page'] = self.obj
288
+ context['media'] = self.get_media_assets()
289
+ context['regions'] = dict()
290
+ for region, title in self.obj.get_regions():
291
+ context[region] = self.get_region_data(region, title)
292
+ context['regions'][region] = context[region]
293
+
294
+ return context
@@ -0,0 +1,15 @@
1
+ from django.db.models.signals import pre_save, post_delete
2
+ from django.dispatch import receiver
3
+ from .models import PageSection
4
+
5
+
6
+ @receiver(pre_save, sender=PageSection)
7
+ def pre_save_page_section(sender, instance, **kwargs):
8
+ # Ensure instance was added and not changed.
9
+ if instance.pk is None:
10
+ PageSection.objects.set_index(instance)
11
+
12
+
13
+ @receiver(post_delete, sender=PageSection)
14
+ def post_delete_page_section(sender, instance, **kwargs):
15
+ PageSection.objects.update_indexes(instance)
@@ -0,0 +1,19 @@
1
+ {% extends "admin/change_form.html" %}
2
+ {% load i18n static %}
3
+
4
+ {% block content %}
5
+ {% if page_types %}
6
+ <h2>{% translate "Choose page type" %}</h2>
7
+ <ul>
8
+ {% for url, name in page_types %}
9
+ <li>
10
+ <a href="{{ url }}" class="addlink" role="button">
11
+ {% translate "Add" %} {{ name }}
12
+ </a>
13
+ </li>
14
+ {% endfor %}
15
+ </ul>
16
+ {% else %}
17
+ {{ block.super }}
18
+ {% endif %}
19
+ {% endblock %}
@@ -0,0 +1,18 @@
1
+ {% load simple_page mptt_tags %}
2
+ <ul class="nav-level-1">
3
+ {% if root %}
4
+ <li{% if page.is_root_node %} class="active"{% endif %}>
5
+ <a href="{{ root.get_absolute_url }}">{{ root.title }}</a>
6
+ </li>
7
+ {% endif %}
8
+ {% recursetree nodes %}
9
+ <li{% if page|is_active:node %} class="active"{% endif %}>
10
+ <a href="{{ node.get_absolute_url }}">{{ node.title }}</a>
11
+ {% if not node.is_leaf_node and not node|level > max_level%}
12
+ <ul class="nav-level-{{ node|level }}">
13
+ {{ children }}
14
+ </ul>
15
+ {% endif %}
16
+ </li>
17
+ {% endrecursetree %}
18
+ </ul>
@@ -0,0 +1,22 @@
1
+ from django.shortcuts import get_object_or_404
2
+ from django.http import HttpResponse
3
+ from .models import Page
4
+ from .renderer import get_page_renderer
5
+
6
+
7
+ def page_view(request, slug, **kwargs):
8
+ """
9
+ Simple view function for pages. Get the page by its slug, find the right renderer
10
+ for it and return an HTTP response with the rendered page.
11
+
12
+ :param request: HTTP request
13
+ :type request: :class:`~django.http.HttpRequest`
14
+ :param str slug: slug of the page to be rendered
15
+ :param kwargs: Additional data as keyword arguments (default empty dict)
16
+ :return: HTTP response with the rendered page
17
+ :rtype: :class:`~django.http.HttpResponse`
18
+ :raises Http404: if no page with the given slug exists
19
+ """
20
+ page = get_object_or_404(Page, slug=slug).resolve_obj()
21
+ renderer_cls = get_page_renderer(page)
22
+ return HttpResponse(renderer_cls(page, request, **kwargs).render())