kopia lustrzana https://github.com/wagtail/wagtail
Add Recipe doc for implementing AMP on a site (#5626)
* Add Recipe doc for implementing AMP on a site * Fix typos * Add section on using a different page template for AMP * Move AMP recipe out of images Not very image specific anymore * Wording tweaks and grammar fixespull/5680/head
rodzic
1c1341e477
commit
5845cfbfe9
|
@ -0,0 +1,340 @@
|
|||
Building a site with AMP support
|
||||
================================
|
||||
|
||||
This recipe document describes a method for creating an
|
||||
`AMP <https://amp.dev/>`_ version of a Wagtail site and hosting it separately
|
||||
to the rest of the site on a URL prefix. It also describes how to make Wagtail
|
||||
render images with the ``<amp-img>`` tag when a user is visiting a page on the
|
||||
AMP version of the site.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
In the next section, we will add a new URL entry that points at Wagtail's
|
||||
internal ``serve()`` view which will have the effect of rendering the whole
|
||||
site again under the ``/amp`` prefix.
|
||||
|
||||
Then, we will add some utilities that will allow us to track whether the
|
||||
current request is in the ``/amp`` prefixed version of the site without needing
|
||||
a request object.
|
||||
|
||||
After that, we will add a template context processor to allow us to check from
|
||||
within templates which version of the site is being rendered.
|
||||
|
||||
Then, finally, we will modify the behaviour of the ``{% image %}`` tag to make it
|
||||
render ``<amp-img>`` tags when rendering the AMP version of the site.
|
||||
|
||||
Creating the second page tree
|
||||
-----------------------------
|
||||
|
||||
We can render the whole site at a different prefix by duplicating the Wagtail
|
||||
URL in the project ``urls.py`` file and giving it a prefix. This must be before
|
||||
the default URL from Wagtail, or it will try to find ``/amp`` as a page:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <project>/urls.py
|
||||
|
||||
urlpatterns += [
|
||||
# Add this line just before the default ``include(wagtail_urls)`` line
|
||||
url(r'amp/', include(wagtail_urls)),
|
||||
|
||||
url(r'', include(wagtail_urls)),
|
||||
]
|
||||
|
||||
If you now open ``http://localhost:8000/amp/`` in your browser, you should see
|
||||
the homepage.
|
||||
|
||||
Making pages aware of "AMP mode"
|
||||
--------------------------------
|
||||
|
||||
All the pages will now render under the ``/amp`` prefix, but right now there
|
||||
isn't any difference between the AMP version and the normal version.
|
||||
|
||||
To make changes, we need to add a way to detect which URL was used to render
|
||||
the page. To do this, we will have to wrap Wagtail's ``serve()`` view and
|
||||
set a thread-local to indicate to all downstream code that AMP mode is active.
|
||||
|
||||
.. note:: Why a thread-local?
|
||||
|
||||
(feel free to skip this part if you're not interested)
|
||||
|
||||
Modifying the ``request`` object would be the most common way to do this.
|
||||
However, the image tag rendering is performed in a part of Wagtail that
|
||||
does not have access to the request.
|
||||
|
||||
Thread-locals are global variables that can have a different value for each
|
||||
running thread. As each thread only handles one request at a time, we can
|
||||
use it as a way to pass around data that is specific to that request
|
||||
without having to pass the request object everywhere.
|
||||
|
||||
Django uses thread-locals internally to track the currently active language
|
||||
for the request.
|
||||
|
||||
Please be aware though: In Django 3.x and above, you will need to use an
|
||||
``asgiref.Local`` instead.
|
||||
This is because Django 3.x handles multiple requests in a single thread
|
||||
so thread-locals will no longer be unique to a single request.
|
||||
|
||||
Now let's create that thread-local and some utility functions to interact with it,
|
||||
save this module as ``amp_utils.py`` in an app in your project:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/amp_utils.py
|
||||
|
||||
from contextlib import contextmanager
|
||||
from threading import local
|
||||
|
||||
# FIXME: For Django 3.0 support, replace this with asgiref.Local
|
||||
_amp_mode_active = local()
|
||||
|
||||
@contextmanager
|
||||
def activate_amp_mode():
|
||||
"""
|
||||
A context manager used to activate AMP mode
|
||||
"""
|
||||
_amp_mode_active.value = True
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del _amp_mode_active.value
|
||||
|
||||
def amp_mode_active():
|
||||
"""
|
||||
Returns True if AMP mode is currently active
|
||||
"""
|
||||
return hasattr(_amp_mode_active, 'value')
|
||||
|
||||
This module defines two functions:
|
||||
|
||||
- ``activate_amp_mode`` is a context manager which can be invoked using Python's
|
||||
``with`` syntax. In the body of the ``with`` statement, AMP mode would be active.
|
||||
|
||||
- ``amp_mode_active`` is a function that returns ``True`` when AMP mode is active.
|
||||
|
||||
Next, we need to define a view that wraps Wagtail's builtin ``serve`` view and
|
||||
invokes the ``activate_amp_mode`` context manager:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/amp_views.py
|
||||
|
||||
from django.template.response import SimpleTemplateResponse
|
||||
from wagtail.core.views import serve as wagtail_serve
|
||||
|
||||
from .amp_utils import activate_amp_mode
|
||||
|
||||
def serve(request, path):
|
||||
with activate_amp_mode():
|
||||
response = wagtail_serve(request, path)
|
||||
|
||||
# Render template responses now while AMP mode is still active
|
||||
if isinstance(response, SimpleTemplateResponse):
|
||||
response.render()
|
||||
|
||||
return response
|
||||
|
||||
Then we need to create a ``amp_urls.py`` file in the same app:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/amp_urls.py
|
||||
|
||||
from django.conf.urls import url
|
||||
from wagtail.core.urls import serve_pattern
|
||||
|
||||
from . import amp_views
|
||||
|
||||
urlpatterns = [
|
||||
url(serve_pattern, amp_views.serve, name='wagtail_amp_serve')
|
||||
]
|
||||
|
||||
Finally, we need to update the project's main ``urls.py`` to use this new URLs
|
||||
file for the ``/amp`` prefix:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <project>/urls.py
|
||||
|
||||
from myapp import amp_urls as wagtail_amp_urls
|
||||
|
||||
urlpatterns += [
|
||||
# Change this line to point at your amp_urls instead of Wagtail's urls
|
||||
url(r'amp/', include(wagtail_amp_urls)),
|
||||
|
||||
url(r'', include(wagtail_urls)),
|
||||
]
|
||||
|
||||
After this, there shouldn't be any noticeable difference to the AMP version of
|
||||
the site.
|
||||
|
||||
Write a template context processor so that AMP state can be checked in templates
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
This is optional, but worth doing so we can confirm that everything is working
|
||||
so far.
|
||||
|
||||
Add a ``amp_context_processors.py`` file into your app that contains the
|
||||
following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/amp_context_processors.py
|
||||
|
||||
from .amp_utils import amp_mode_active
|
||||
|
||||
def amp(request):
|
||||
return {
|
||||
'amp_mode_active': amp_mode_active(),
|
||||
}
|
||||
|
||||
Now add the path to this context processor to the
|
||||
``['OPTIONS']['context_processors']`` key of the ``TEMPLATES`` setting:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Either <project>/settings.py or <project>/settings/base.py
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
...
|
||||
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
...
|
||||
# Add this after other context processors
|
||||
'myapp.amp_context_processors.amp',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
You should now be able to use the ``amp_mode_active`` variable in templates.
|
||||
For example:
|
||||
|
||||
.. code-block:: html+Django
|
||||
|
||||
{% if amp_mode_active %}
|
||||
AMP MODE IS ACTIVE!
|
||||
{% endif %}
|
||||
|
||||
Using a different page template when AMP mode is active
|
||||
-------------------------------------------------------
|
||||
|
||||
You're probably not going to want to use the same templates on the AMP site as
|
||||
you do on the regular web site. Let's add some logic in to make Wagtail use a
|
||||
separate template whenever a page is served with AMP enabled.
|
||||
|
||||
We can use a mixin, which allows us to re-use the logic on different page types.
|
||||
Add the following to the bottom of the amp_utils.py file that you created earlier:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/amp_utils.py
|
||||
|
||||
import os.path
|
||||
|
||||
...
|
||||
|
||||
class PageAMPTemplateMixin:
|
||||
|
||||
@property
|
||||
def amp_template(self):
|
||||
# Get the default template name and insert `_amp` before the extension
|
||||
name, ext = os.path.splitext(self.template)
|
||||
return name + '_amp' + ext
|
||||
|
||||
def get_template(self, request):
|
||||
if amp_mode_active():
|
||||
return self.amp_template
|
||||
|
||||
return super().get_template(request)
|
||||
|
||||
Now add this mixin to any page model, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/models.py
|
||||
|
||||
from .amp_utils import PageAMPTemplateMixin
|
||||
|
||||
class MyPageModel(PageAMPTemplateMixin, Page):
|
||||
...
|
||||
|
||||
When AMP mode is active, the template at ``app_label/mypagemodel_amp.html``
|
||||
will be used instead of the default one.
|
||||
|
||||
If you have a different naming convention, you can override the
|
||||
``amp_template`` attribute on the model. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# <app>/models.py
|
||||
|
||||
from .amp_utils import PageAMPTemplateMixin
|
||||
|
||||
class MyPageModel(PageAMPTemplateMixin, Page):
|
||||
amp_template = 'my_custom_amp_template.html'
|
||||
|
||||
Overriding the ``{% image %}`` tag to output ``<amp-img>`` tags
|
||||
---------------------------------------------------------------
|
||||
|
||||
Finally, let's change Wagtail's ``{% image %}`` tag, so it renders an ``<amp-img>``
|
||||
tags when rendering pages with AMP enabled. We'll make the change on the
|
||||
`Rendition` model itself so it applies to both images rendered with the
|
||||
``{% image %}`` tag and images rendered in rich text fields as well.
|
||||
|
||||
Doing this with a :ref:`Custom image model <custom_image_model>` is easier, as
|
||||
you can override the ``img_tag`` method on your custom ``Rendition`` model to
|
||||
return a different tag.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.forms.utils import flatatt
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from wagtail.images.models import AbstractRendition
|
||||
|
||||
...
|
||||
|
||||
class CustomRendition(AbstractRendition):
|
||||
def img_tag(self, extra_attributes):
|
||||
attrs = self.attrs_dict.copy()
|
||||
attrs.update(extra_attributes)
|
||||
|
||||
if amp_mode_active():
|
||||
return mark_safe('<amp-img{}>'.format(flatatt(attrs)))
|
||||
else:
|
||||
return mark_safe('<img{}>'.format(flatatt(attrs)))
|
||||
|
||||
...
|
||||
|
||||
Without a custom image model, you will have to monkey-patch the builtin
|
||||
``Rendition`` model.
|
||||
Add this anywhere in your project where it would be imported on start:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.forms.utils import flatatt
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from wagtail.images.models import Rendition
|
||||
|
||||
def img_tag(rendition, extra_attributes={}):
|
||||
"""
|
||||
Replacement implementation for Rendition.img_tag
|
||||
|
||||
When AMP mode is on, this returns an <amp-img> tag instead of an <img> tag
|
||||
"""
|
||||
attrs = rendition.attrs_dict.copy()
|
||||
attrs.update(extra_attributes)
|
||||
|
||||
if amp_mode_active():
|
||||
return mark_safe('<amp-img{}>'.format(flatatt(attrs)))
|
||||
else:
|
||||
return mark_safe('<img{}>'.format(flatatt(attrs)))
|
||||
|
||||
Rendition.img_tag = img_tag
|
|
@ -18,3 +18,4 @@ Advanced topics
|
|||
jinja2
|
||||
testing
|
||||
api/index
|
||||
amp
|
||||
|
|
Ładowanie…
Reference in New Issue