kopia lustrzana https://github.com/wagtail/wagtail
Add get_page_url() method to BaseSetting
* adds a convenience `page_url` shortcut to improve how page URLs can be accessed from site settings in Django templatespull/4922/head
rodzic
a7f58821a7
commit
4846a3e801
|
@ -15,6 +15,7 @@ Changelog
|
||||||
* Add ability to sort search promotions on listing page (Chris Ranjana, LB (Ben Johnston))
|
* Add ability to sort search promotions on listing page (Chris Ranjana, LB (Ben Johnston))
|
||||||
* Upgrade internal JS tooling to Gulp v4 & Node v10 (Jim Jazwiecki, Kim LaRocca)
|
* Upgrade internal JS tooling to Gulp v4 & Node v10 (Jim Jazwiecki, Kim LaRocca)
|
||||||
* Add `after_publish_page`, `before_publish_page`, `after_unpublish_page` & `before_unpublish_page` hooks (Jonatas Baldin, Coen van der Kamp)
|
* Add `after_publish_page`, `before_publish_page`, `after_unpublish_page` & `before_unpublish_page` hooks (Jonatas Baldin, Coen van der Kamp)
|
||||||
|
* Add convenience `page_url` shortcut to improve how page URLs can be accessed from site settings in Django templates (Andy Babic)
|
||||||
* Fix: Support IPv6 domain (Alex Gleason, Coen van der Kamp)
|
* Fix: Support IPv6 domain (Alex Gleason, Coen van der Kamp)
|
||||||
* Fix: Ensure link to add a new user works when no users are visible in the users list (LB (Ben Johnston))
|
* Fix: Ensure link to add a new user works when no users are visible in the users list (LB (Ben Johnston))
|
||||||
* Fix: `AbstractEmailForm` saved submission fields are now aligned with the email content fields, `form.cleaned_data` will be used instead of `form.fields` (Haydn Greatnews)
|
* Fix: `AbstractEmailForm` saved submission fields are now aligned with the email content fields, `form.cleaned_data` will be used instead of `form.fields` (Haydn Greatnews)
|
||||||
|
|
|
@ -227,6 +227,8 @@ Or, alternately, using the ``set`` tag:
|
||||||
Utilising ``select_related`` to improve efficiency
|
Utilising ``select_related`` to improve efficiency
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 2.9
|
||||||
|
|
||||||
For models with foreign key relationships to other objects (e.g. pages),
|
For models with foreign key relationships to other objects (e.g. pages),
|
||||||
which are very often needed to output values in templates, you can set
|
which are very often needed to output values in templates, you can set
|
||||||
the ``select_related`` attribute on your model to have Wagtail utilise
|
the ``select_related`` attribute on your model to have Wagtail utilise
|
||||||
|
@ -264,5 +266,55 @@ and two more to fetch each page):
|
||||||
|
|
||||||
.. code-block:: html
|
.. code-block:: html
|
||||||
|
|
||||||
{{ settings.app_label.ImportantPages.donate_page.url }}
|
{% load wagtailcore_tags %}
|
||||||
{{ settings.app_label.ImportantPages.sign_up_page.url }}
|
{% pageurl settings.app_label.ImportantPages.donate_page %}
|
||||||
|
{% pageurl settings.app_label.ImportantPages.sign_up_page %}
|
||||||
|
|
||||||
|
|
||||||
|
Utilising the ``page_url`` setting shortcut
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 2.10
|
||||||
|
|
||||||
|
If, like in the previous section, your settings model references pages,
|
||||||
|
and you regularly need to output the URLs of those pages in your project,
|
||||||
|
you can likely use the setting model's ``page_url`` shortcut to do that more
|
||||||
|
cleanly. For example, instead of doing the following:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
{% load wagtailcore_tags %}
|
||||||
|
{% pageurl settings.app_label.ImportantPages.donate_page %}
|
||||||
|
{% pageurl settings.app_label.ImportantPages.sign_up_page %}
|
||||||
|
|
||||||
|
You could write:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
{{ settings.app_label.ImportantPages.page_url.donate_page }}
|
||||||
|
{{ settings.app_label.ImportantPages.page_url.sign_up_page }}
|
||||||
|
|
||||||
|
Using the ``page_url`` shortcut has a few of advantages over using the tag:
|
||||||
|
|
||||||
|
1. The 'specific' page is automatically fetched to generate the URL,
|
||||||
|
so you don't have to worry about doing this (or forgetting to do this)
|
||||||
|
yourself.
|
||||||
|
2. The results are cached, so if you need to access the same page URL
|
||||||
|
in more than one place (e.g. in a form and in footer navigation), using
|
||||||
|
the ``page_url`` shortcut will be more efficient.
|
||||||
|
3. It's more concise, and the syntax is the same whether using it in templates
|
||||||
|
or views (or other Python code), allowing you to write more more consistent
|
||||||
|
code.
|
||||||
|
|
||||||
|
When using the ``page_url`` shortcut, there are a couple of points worth noting:
|
||||||
|
|
||||||
|
1. The same limitations that apply to the `{% pageurl %}` tag apply to the
|
||||||
|
shortcut: If the settings are accessed from a template context where the
|
||||||
|
current request is not available, all URLs returned will include the
|
||||||
|
site's scheme/domain, and URL generation will not be quite as efficient.
|
||||||
|
2. If using the shortcut in views or other Python code, the method will
|
||||||
|
raise an ``AttributeError`` if the attribute you request from ``page_url``
|
||||||
|
is not an attribute on the settings object.
|
||||||
|
3. If the settings object DOES have the attribute, but the attribute returns
|
||||||
|
a value of ``None`` (or something that is not a ``Page``), the shortcut
|
||||||
|
will return an empty string.
|
||||||
|
|
|
@ -24,6 +24,7 @@ Other features
|
||||||
* Add ability to sort search promotions on listing page (Chris Ranjana, LB (Ben Johnston))
|
* Add ability to sort search promotions on listing page (Chris Ranjana, LB (Ben Johnston))
|
||||||
* Upgrade internal JS tooling to Gulp v4 & Node v10 (Jim Jazwiecki, Kim LaRocca)
|
* Upgrade internal JS tooling to Gulp v4 & Node v10 (Jim Jazwiecki, Kim LaRocca)
|
||||||
* Add ``after_publish_page``, ``before_publish_page``, ``after_unpublish_page`` & ``before_unpublish_page`` hooks (Jonatas Baldin, Coen van der Kamp)
|
* Add ``after_publish_page``, ``before_publish_page``, ``after_unpublish_page`` & ``before_unpublish_page`` hooks (Jonatas Baldin, Coen van der Kamp)
|
||||||
|
* Add convenience ``page_url`` shortcut to improve how page URLs can be accessed from site settings in Django templates (Andy Babic)
|
||||||
|
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from wagtail.core.models import Site
|
from wagtail.core.models import Site
|
||||||
|
from wagtail.core.utils import InvokeViaAttributeShortcut
|
||||||
|
|
||||||
from .registry import register_setting
|
from .registry import register_setting
|
||||||
|
|
||||||
|
@ -62,6 +63,8 @@ class BaseSetting(models.Model):
|
||||||
return getattr(request, attr_name)
|
return getattr(request, attr_name)
|
||||||
site = Site.find_for_request(request)
|
site = Site.find_for_request(request)
|
||||||
site_settings = cls.for_site(site)
|
site_settings = cls.for_site(site)
|
||||||
|
# to allow more efficient page url generation
|
||||||
|
site_settings._request = request
|
||||||
setattr(request, attr_name, site_settings)
|
setattr(request, attr_name, site_settings)
|
||||||
return site_settings
|
return site_settings
|
||||||
|
|
||||||
|
@ -74,3 +77,42 @@ class BaseSetting(models.Model):
|
||||||
return "_{}.{}".format(
|
return "_{}.{}".format(
|
||||||
cls._meta.app_label, cls._meta.model_name
|
cls._meta.app_label, cls._meta.model_name
|
||||||
).lower()
|
).lower()
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# Allows get_page_url() to be invoked using
|
||||||
|
# `obj.page_url.foreign_key_name` syntax
|
||||||
|
self.page_url = InvokeViaAttributeShortcut(self, 'get_page_url')
|
||||||
|
# Per-instance page URL cache
|
||||||
|
self._page_url_cache = {}
|
||||||
|
|
||||||
|
def get_page_url(self, attribute_name, request=None):
|
||||||
|
"""
|
||||||
|
Returns the URL of a page referenced by a foreign key
|
||||||
|
(or other attribute) matching the name ``attribute_name``.
|
||||||
|
If the field value is null, or links to something other
|
||||||
|
than a ``Page`` object, an empty string is returned.
|
||||||
|
The result is also cached per-object to facilitate
|
||||||
|
fast repeat access.
|
||||||
|
|
||||||
|
Raises an ``AttributeError`` if the object has no such
|
||||||
|
field or attribute.
|
||||||
|
"""
|
||||||
|
if attribute_name in self._page_url_cache:
|
||||||
|
return self._page_url_cache[attribute_name]
|
||||||
|
|
||||||
|
if not hasattr(self, attribute_name):
|
||||||
|
raise AttributeError(
|
||||||
|
"'{}' object has no attribute '{}'"
|
||||||
|
.format(self.__class__.__name__, attribute_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
page = getattr(self, attribute_name)
|
||||||
|
|
||||||
|
if hasattr(page, 'specific'):
|
||||||
|
url = page.specific.get_url(getattr(self, '_request', None))
|
||||||
|
else:
|
||||||
|
url = ""
|
||||||
|
|
||||||
|
self._page_url_cache[attribute_name] = url
|
||||||
|
return url
|
||||||
|
|
|
@ -51,7 +51,7 @@ class SettingModelTestCase(SettingsTestMixin, TestCase):
|
||||||
|
|
||||||
def _create_importantpages_object(self):
|
def _create_importantpages_object(self):
|
||||||
site = self.default_site
|
site = self.default_site
|
||||||
ImportantPages.objects.create(
|
return ImportantPages.objects.create(
|
||||||
site=site,
|
site=site,
|
||||||
sign_up_page=site.root_page,
|
sign_up_page=site.root_page,
|
||||||
general_terms_page=site.root_page,
|
general_terms_page=site.root_page,
|
||||||
|
@ -83,3 +83,105 @@ class SettingModelTestCase(SettingsTestMixin, TestCase):
|
||||||
finally:
|
finally:
|
||||||
# undo temporary change
|
# undo temporary change
|
||||||
ImportantPages.select_related = None
|
ImportantPages.select_related = None
|
||||||
|
|
||||||
|
def test_get_page_url_when_settings_fetched_via_for_request(self):
|
||||||
|
""" Using ImportantPages.for_request() makes the setting
|
||||||
|
object request-aware, improving efficiency and allowing
|
||||||
|
site-relative URLs to be returned """
|
||||||
|
|
||||||
|
self._create_importantpages_object()
|
||||||
|
|
||||||
|
request = self.get_request()
|
||||||
|
settings = ImportantPages.for_request(request)
|
||||||
|
|
||||||
|
# Force site root paths query beforehand
|
||||||
|
self.default_site.root_page._get_site_root_paths(request)
|
||||||
|
|
||||||
|
for page_fk_field, expected_result in (
|
||||||
|
('sign_up_page', '/'),
|
||||||
|
('general_terms_page', '/'),
|
||||||
|
('privacy_policy_page', 'http://other/'),
|
||||||
|
):
|
||||||
|
with self.subTest(page_fk_field=page_fk_field):
|
||||||
|
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
# because results are cached, only the first
|
||||||
|
# request for a URL will trigger a query to
|
||||||
|
# fetch the page
|
||||||
|
self.assertEqual(
|
||||||
|
settings.get_page_url(page_fk_field),
|
||||||
|
expected_result)
|
||||||
|
|
||||||
|
# when called directly
|
||||||
|
self.assertEqual(
|
||||||
|
settings.get_page_url(page_fk_field),
|
||||||
|
expected_result
|
||||||
|
)
|
||||||
|
|
||||||
|
# when called indirectly via shortcut
|
||||||
|
self.assertEqual(
|
||||||
|
getattr(settings.page_url, page_fk_field),
|
||||||
|
expected_result
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_page_url_when_for_settings_fetched_via_for_site(self):
|
||||||
|
""" ImportantPages.for_site() cannot make the settings object
|
||||||
|
request-aware, so things are a little less efficient, and the
|
||||||
|
URLs returned will not be site-relative """
|
||||||
|
self._create_importantpages_object()
|
||||||
|
|
||||||
|
settings = ImportantPages.for_site(self.default_site)
|
||||||
|
|
||||||
|
# Force site root paths query beforehand
|
||||||
|
self.default_site.root_page._get_site_root_paths()
|
||||||
|
|
||||||
|
for page_fk_field, expected_result in (
|
||||||
|
('sign_up_page', 'http://localhost/'),
|
||||||
|
('general_terms_page', 'http://localhost/'),
|
||||||
|
('privacy_policy_page', 'http://other/'),
|
||||||
|
):
|
||||||
|
with self.subTest(page_fk_field=page_fk_field):
|
||||||
|
|
||||||
|
# only the first request for each URL will trigger queries.
|
||||||
|
# 2 are triggered instead of 1 here, because tests use the
|
||||||
|
# database cache backed, and the cache is queried each time
|
||||||
|
# to fetch site root paths (because there's no 'request' to
|
||||||
|
# store them on)
|
||||||
|
|
||||||
|
with self.assertNumQueries(2):
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
settings.get_page_url(page_fk_field),
|
||||||
|
expected_result
|
||||||
|
)
|
||||||
|
|
||||||
|
# when called directly
|
||||||
|
self.assertEqual(
|
||||||
|
settings.get_page_url(page_fk_field),
|
||||||
|
expected_result
|
||||||
|
)
|
||||||
|
|
||||||
|
# when called indirectly via shortcut
|
||||||
|
self.assertEqual(
|
||||||
|
getattr(settings.page_url, page_fk_field),
|
||||||
|
expected_result
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_page_url_raises_attributeerror_if_attribute_name_invalid(self):
|
||||||
|
settings = self._create_importantpages_object()
|
||||||
|
# when called directly
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
settings.get_page_url('not_an_attribute')
|
||||||
|
# when called indirectly via shortcut
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
settings.page_url.not_an_attribute
|
||||||
|
|
||||||
|
def test_get_page_url_returns_empty_string_if_attribute_value_not_a_page(self):
|
||||||
|
settings = self._create_importantpages_object()
|
||||||
|
for value in (None, self.default_site):
|
||||||
|
with self.subTest(attribute_value=value):
|
||||||
|
settings.test_attribute = value
|
||||||
|
# when called directly
|
||||||
|
self.assertEqual(settings.get_page_url('test_attribute'), '')
|
||||||
|
# when called indirectly via shortcut
|
||||||
|
self.assertEqual(settings.page_url.test_attribute, '')
|
||||||
|
|
|
@ -103,3 +103,32 @@ def accepts_kwarg(func, kwarg):
|
||||||
return True
|
return True
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class InvokeViaAttributeShortcut:
|
||||||
|
"""
|
||||||
|
Used to create a shortcut that allows an object's named
|
||||||
|
single-argument method to be invoked using a simple
|
||||||
|
attribute reference syntax. For example, adding the
|
||||||
|
following to an object:
|
||||||
|
|
||||||
|
obj.page_url = InvokeViaAttributeShortcut(obj, 'get_page_url')
|
||||||
|
|
||||||
|
Would allow you to invoke get_page_url() like so:
|
||||||
|
|
||||||
|
obj.page_url.terms_and_conditions
|
||||||
|
|
||||||
|
As well as the usual:
|
||||||
|
|
||||||
|
obj.get_page_url('terms_and_conditions')
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = 'obj', 'method_name'
|
||||||
|
|
||||||
|
def __init__(self, obj, method_name):
|
||||||
|
self.obj = obj
|
||||||
|
self.method_name = method_name
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
method = getattr(self.obj, self.method_name)
|
||||||
|
return method(name)
|
||||||
|
|
Ładowanie…
Reference in New Issue