Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions readthedocs/api/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ class CDNCacheTagsMixin:
"""
Add cache tags for project and version to the response of this view.

The view inheriting this mixin should implement the
`self._get_project` and `self._get_version` methods.

If `self._get_version` returns `None`,
only the project level tags are added.
The view inheriting this mixin can either call :py:method:`set_cache_tags` or
implement the ``self._get_project`` and ``self._get_version`` methods.

You can add an extra per-project tag by overriding the `project_cache_tag` attribute.
"""
Expand All @@ -30,29 +27,40 @@ class CDNCacheTagsMixin:

def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
cache_tags = self._get_cache_tags()
cache_tags = getattr(self, "_cache_tags", self._get_cache_tags())
if cache_tags:
add_cache_tags(response, cache_tags)
return response

def _get_cache_tags(self):
def _get_cache_tags(self, project=None, version=None):
"""
Get cache tags for this view.

This returns an array of tag identifiers used to tag the response at CDN.

If project and version are not passed in, these values will come from the
methods ``_get_project()`` and ``_get_version()``.
If ``_get_version()`` returns ``None``, only the project level tags are added.

It's easier to use :py:method:`set_cache_tags` if project/version aren't
set at the instance level, or if they are passed in through a method
like ``get()``.

.. warning::

This method is run at the end of the request,
so any exceptions like 404 should be caught.
"""
try:
project = self._get_project()
version = self._get_version()
except Exception:
log.warning(
"Error while retrieving project or version for this view.",
exc_info=True,
)
return []
if project is None and version is None:
try:
project = self._get_project()
version = self._get_version()
except Exception:
log.warning(
"Error while retrieving project or version for this view.",
exc_info=True,
)
return []

tags = []
if project:
Expand All @@ -63,6 +71,19 @@ def _get_cache_tags(self):
tags.append(get_cache_tag(project.slug, self.project_cache_tag))
return tags

def set_cache_tags(self, project=None, version=None):
"""
Store cache tags to be added to response.

This method can be used if project/version do not exist on the view
instance or if they are passed into the view through a method like
``get()``.

The attribute methods ``_get_project()``/``_get_version()`` aren`t used
in this pattern.
"""
self._cache_tags = self._get_cache_tags(project, version)


class EmbedAPIMixin:
"""
Expand Down
5 changes: 4 additions & 1 deletion readthedocs/projects/views/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from django.views.generic import ListView
from taggit.models import Tag

from readthedocs.api.mixins import CDNCacheTagsMixin
from readthedocs.builds.constants import BUILD_STATE_FINISHED
from readthedocs.builds.constants import EXTERNAL
from readthedocs.builds.constants import INTERNAL
Expand Down Expand Up @@ -299,7 +300,7 @@ def verify_project_token(cls, token, project_slug):
project_badge = never_cache(ProjectBadgeView.as_view())


class ProjectDownloadMediaBase(CDNCacheControlMixin, ServeDocsMixin, View):
class ProjectDownloadMediaBase(CDNCacheControlMixin, CDNCacheTagsMixin, ServeDocsMixin, View):
# Use new-style URLs (same domain as docs) or old-style URLs (dashboard URL)
same_domain_url = False

Expand Down Expand Up @@ -382,6 +383,8 @@ def get(
slug=version_slug,
)

self.set_cache_tags(project=version.project, version=version)

return self._serve_dowload(
request=request,
project=version.project,
Expand Down
3 changes: 3 additions & 0 deletions readthedocs/proxito/tests/test_full.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ def test_download_files_public_version(self):
headers={"host": "project.dev.readthedocs.io"},
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp["Cache-Tag"], "project,project:latest")
extension = "zip" if type_ == MEDIA_TYPE_HTMLZIP else type_
self.assertEqual(
resp["X-Accel-Redirect"],
Expand All @@ -560,6 +561,7 @@ def test_download_files_public_version(self):
headers={"host": "project.dev.readthedocs.io"},
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp["Cache-Tag"], "translation,translation:latest")
extension = "zip" if type_ == MEDIA_TYPE_HTMLZIP else type_
self.assertEqual(
resp["X-Accel-Redirect"],
Expand Down Expand Up @@ -604,6 +606,7 @@ def test_download_files_private_version(self):
headers={"host": "project.dev.readthedocs.io"},
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp["Cache-Tag"], "project,project:latest")
extension = "zip" if type_ == MEDIA_TYPE_HTMLZIP else type_
self.assertEqual(
resp["X-Accel-Redirect"],
Expand Down