diff --git a/bedrock/base/templatetags/helpers.py b/bedrock/base/templatetags/helpers.py index d13e1195f3f..607e63b57c3 100644 --- a/bedrock/base/templatetags/helpers.py +++ b/bedrock/base/templatetags/helpers.py @@ -8,6 +8,7 @@ from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage +from django.urls import NoReverseMatch from django.utils.encoding import smart_str import jinja2 @@ -60,7 +61,20 @@ def thisyear(): @library.global_function def url(viewname, *args, **kwargs): """Helper for Django's ``reverse`` in templates.""" - return reverse(viewname, args=args, kwargs=kwargs) + + try: + # First, look for URLs which only exist in the CMS - these are solely defined + # in bedrock/cms/cms_only_urls.py. These URLs are not listed in + # the main URLConf because they aren't served by the Django views in + # bedrock, but they will/must have matching routes set up in the CMS. + return reverse( + viewname, + urlconf="bedrock.cms.cms_only_urls", + args=args, + kwargs=kwargs, + ) + except NoReverseMatch: + return reverse(viewname, args=args, kwargs=kwargs) @library.filter diff --git a/bedrock/cms/cms_only_urls.py b/bedrock/cms/cms_only_urls.py new file mode 100644 index 00000000000..edecdb3a5f4 --- /dev/null +++ b/bedrock/cms/cms_only_urls.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +# This module contains named URL paths which only exist in the CMS. +# +# They are named so that they can be looked up via our url() jinja +# helper, even though the actual page exists in the CMS only. +# +# These URLs will/must have matching routes set up in the CMS pages +# else they will lead to a 404 from the CMS. +# +# Note that all URL routes defined here should point to the +# dummy_view function, which never gets called because this +# urlconf is only use with reverse(), never resolve() + + +# from django.urls import path + + +def dummy_view(*args, **kwargs): + # This view will never get called + pass + + +urlpatterns = ( + # pattern is: + # path("url/path/here", dummy_view, name="route.name.here") +) diff --git a/docs/cms.rst b/docs/cms.rst index 64b7ffa388c..b6e1e00ea79 100644 --- a/docs/cms.rst +++ b/docs/cms.rst @@ -423,6 +423,33 @@ views in the URLconf. It can also be passed to our very handy For more details, please see the docstring on ``bedrock.cms.decorators.prefer_cms``. + +Generating URLs for CMS pages in non-CMS templates +================================================== + +Pages in the CMS don't appear in the hard-coded URLConfs in Bedrock. Normally, +this means there's no way to use `url()` to generate a path to it. + +However, if there's a page in the CMS you need to generate a URL for using +the ``url()`` template tag, `and you know what its path will be`, Bedrock contains +a solution. + +``bedrock.cms.cms_only_urls`` is a special URLConf that only gets loaded during +the call to the ``url()`` helper. If you expand it with a named route definition +that matches the path you know will/should exist in the CMS (and most of our +CMS-backed pages `do` have carefully curated paths), the ``url()`` helper will +give you a path that points to that page, even though it doesn't really exist +as a static Django view. + +See the example in the ``bedrock.cms.cms_only_urls.py`` file. + +.. note:: + Moving a URL route to ``cms_only_urls.py`` is a natural next step after + you've migrated a page to the CMS using the ``@prefer_cms`` decorator + and now want to remove the old view without breaking all the calls to + `url('some.view')` or `reverse('some.view')`. + + Images ====== @@ -502,6 +529,7 @@ By default, the sqlite DB you can download to run bedrock locally is based on th Bedrock Dev. To get images from the cloud bucket for dev, run: .. code-block:: shell + ./manage.py download_media_to_local This will look at your local DB, find the image files that it says should be