From 49ca2d7631adcef3e3a1e34a0a59f9dd04213ba8 Mon Sep 17 00:00:00 2001 From: Martin Mahner Date: Mon, 13 Dec 2021 11:00:40 +0100 Subject: [PATCH] Cache Fixes, Django 4.0 Update --- CHANGELOG.rst | 8 ++++++++ dpaste/apps.py | 11 +++++----- dpaste/urls/__init__.py | 12 +++++------ dpaste/urls/dpaste.py | 14 ++++++------- dpaste/urls/dpaste_api.py | 4 ++-- dpaste/views.py | 42 ++++++++++++++++----------------------- tox.ini | 5 ++++- 7 files changed, 49 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 975c2d6..f3c960e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +3.6 (master) +------------ + +- Added support for Python 3.9. +- Added support for Python 3.10. +- Removed cache headers for all views except 404. Due to that snippets can be + deleted, it's not trivial to have them removed from upstream caches. + 3.5 (2020-01-08) ---------------- diff --git a/dpaste/apps.py b/dpaste/apps.py index 0e02344..a56f07d 100644 --- a/dpaste/apps.py +++ b/dpaste/apps.py @@ -2,6 +2,9 @@ from django.apps import AppConfig, apps from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ +from logging import getLogger + +log = getLogger(__file__) class dpasteAppConfig(AppConfig): name = "dpaste" @@ -610,12 +613,8 @@ class dpasteAppConfig(AppConfig): # ('zephir', 'Zephir') ] - # Whether to send out cache headers (Max-Age, Never-Cache, etc.). - CACHE_HEADER = True - - # Defines how long pages are cached by upstream Proxies (Max-Age Header). - # This does not affect caching of snippets, their max-age limit is set - # to the expire date. + # Cache timeout for 404 and static pages. Snippets don't have an explicit + # Cache timeout set to avoid caching in upstream proxies. CACHE_TIMEOUT = 60 * 10 @staticmethod diff --git a/dpaste/urls/__init__.py b/dpaste/urls/__init__.py index 163eb58..ad746cf 100644 --- a/dpaste/urls/__init__.py +++ b/dpaste/urls/__init__.py @@ -1,11 +1,11 @@ -from django.conf.urls import include, url +from django.urls import include, re_path urlpatterns = [ - url(r"^", include("dpaste.urls.dpaste_api")), - url(r"^", include("dpaste.urls.dpaste")), - url(r"^i18n/", include("django.conf.urls.i18n")), + re_path(r"^", include("dpaste.urls.dpaste_api")), + re_path(r"^", include("dpaste.urls.dpaste")), + re_path(r"^i18n/", include("django.conf.urls.i18n")), ] # Custom error handlers which load `dpaste/.html` instead of `.html` -handler404 = "dpaste.views.page_not_found" -handler500 = "dpaste.views.server_error" +handler404 = "dpaste.views.handler404" +handler500 = "dpaste.views.handler500" diff --git a/dpaste/urls/dpaste.py b/dpaste/urls/dpaste.py index 601f62e..9fc55c2 100644 --- a/dpaste/urls/dpaste.py +++ b/dpaste/urls/dpaste.py @@ -1,6 +1,6 @@ from django.apps import apps from django.conf import settings -from django.conf.urls import url +from django.urls import re_path from django.views.decorators.cache import cache_control from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import TemplateView @@ -11,8 +11,8 @@ L = getattr(settings, "DPASTE_SLUG_LENGTH", 4) config = apps.get_app_config("dpaste") urlpatterns = [ - url(r"^$", views.SnippetView.as_view(), name="snippet_new"), - url( + re_path(r"^$", views.SnippetView.as_view(), name="snippet_new"), + re_path( r"^about/$", cache_control(max_age=config.CACHE_TIMEOUT)( TemplateView.as_view( @@ -22,18 +22,18 @@ urlpatterns = [ ), name="dpaste_about", ), - url(r"^history/$", views.SnippetHistory.as_view(), name="snippet_history"), - url( + re_path(r"^history/$", views.SnippetHistory.as_view(), name="snippet_history"), + re_path( r"^(?P[a-zA-Z0-9]{%d,})/?$" % L, views.SnippetDetailView.as_view(), name="snippet_details", ), - url( + re_path( r"^(?P[a-zA-Z0-9]{%d,})/raw/?$" % L, views.SnippetRawView.as_view(), name="snippet_details_raw", ), - url( + re_path( r"^(?P[a-zA-Z0-9]{%d,})/slim/?$" % L, xframe_options_exempt( views.SnippetDetailView.as_view(template_name="dpaste/details_slim.html") diff --git a/dpaste/urls/dpaste_api.py b/dpaste/urls/dpaste_api.py index 05d64ce..e8c3036 100644 --- a/dpaste/urls/dpaste_api.py +++ b/dpaste/urls/dpaste_api.py @@ -1,8 +1,8 @@ -from django.conf.urls import url +from django.urls import re_path from django.views.decorators.csrf import csrf_exempt from ..views import APIView urlpatterns = [ - url(r"^api/$", csrf_exempt(APIView.as_view()), name="dpaste_api_create_snippet",) + re_path(r"^api/$", csrf_exempt(APIView.as_view()), name="dpaste_api_create_snippet",) ] diff --git a/dpaste/views.py b/dpaste/views.py index c8a5ade..6531971 100644 --- a/dpaste/views.py +++ b/dpaste/views.py @@ -1,5 +1,6 @@ import datetime import difflib +import ipaddress import json from django.apps import apps @@ -14,19 +15,22 @@ from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils import timezone from django.utils.cache import add_never_cache_headers, patch_cache_control -from django.utils.http import http_date -from django.utils.translation import ugettext +from django.utils.translation import gettext from django.views.generic import FormView from django.views.generic.base import TemplateView, View from django.views.generic.detail import DetailView from pygments.lexers import get_lexer_for_filename from pygments.util import ClassNotFound +from ratelimit.decorators import ratelimit +from ratelimit.exceptions import Ratelimited from dpaste import highlight from dpaste.forms import SnippetForm, get_expire_values from dpaste.highlight import PygmentsHighlighter from dpaste.models import Snippet +from django.conf import settings + config = apps.get_app_config("dpaste") @@ -45,8 +49,6 @@ class SnippetView(FormView): def get(self, request, *args, **kwargs): response = super().get(request, *args, **kwargs) - if config.CACHE_HEADER: - patch_cache_control(response, max_age=config.CACHE_TIMEOUT) return response def get_form_kwargs(self): @@ -111,18 +113,7 @@ class SnippetDetailView(DetailView, FormView): snippet.view_count += 1 snippet.save(update_fields=["view_count"]) - response = super().get(request, *args, **kwargs) - - # Set the Max-Age header up until the snippet would expire - if config.CACHE_HEADER: - if snippet.expire_type == Snippet.EXPIRE_KEEP: - patch_cache_control(response, max_age=config.CACHE_TIMEOUT) - if snippet.expire_type == Snippet.EXPIRE_ONETIME: - add_never_cache_headers(response) - if snippet.expire_type == Snippet.EXPIRE_TIME and snippet.expires: - response["Expires"] = http_date(snippet.expires.timestamp()) - - return response + return super().get(request, *args, **kwargs) def get_initial(self): snippet = self.get_object() @@ -153,8 +144,8 @@ class SnippetDetailView(DetailView, FormView): d = difflib.unified_diff( snippet.parent.content.splitlines(), snippet.content.splitlines(), - ugettext("Previous Snippet"), - ugettext("Current Snippet"), + gettext("Previous Snippet"), + gettext("Current Snippet"), n=1, ) diff_code = "\n".join(d).strip() @@ -188,7 +179,7 @@ class SnippetRawView(SnippetDetailView): def dispatch(self, request, *args, **kwargs): if not config.RAW_MODE_ENABLED: return HttpResponseForbidden( - ugettext("This dpaste installation has Raw view mode disabled.") + gettext("This dpaste installation has Raw view mode disabled.") ) return super().dispatch(request, *args, **kwargs) @@ -332,7 +323,10 @@ class APIView(View): expire_type = Snippet.EXPIRE_TIME snippet = Snippet.objects.create( - content=content, lexer=lexer, expires=expires, expire_type=expire_type, + content=content, + lexer=lexer, + expires=expires, + expire_type=expire_type, ) # Custom formatter for the API response @@ -349,17 +343,15 @@ class APIView(View): # handle them here. # ----------------------------------------------------------------------------- - -def page_not_found(request, exception=None, template_name="dpaste/404.html"): +def handler404(request, exception=None, template_name="dpaste/404.html"): context = {} context.update(config.extra_template_context) response = render(request, template_name, context, status=404) - if config.CACHE_HEADER: - patch_cache_control(response, max_age=config.CACHE_TIMEOUT) + patch_cache_control(response, max_age=config.CACHE_TIMEOUT) return response -def server_error(request, template_name="dpaste/500.html"): +def handler500(request, template_name="dpaste/500.html"): context = {} context.update(config.extra_template_context) response = render(request, template_name, context, status=500) diff --git a/tox.ini b/tox.ini index ebe9efe..3fd64ff 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,8 @@ skip_missing_interpreters=True envlist= readme coverage_setup - py{36,37,38}-django-{22,30,31} + py{36,37,38,39,310}-django-{22,30,31,32} + py{38,39,310}-django-{40} coverage_report [testenv] @@ -18,6 +19,8 @@ deps= django-22: django>=2.2,<3.0 django-30: django>=3.0,<3.1 django-31: django>=3.1,<3.2 + django-32: django>=3.2,<4.0 + django-40: django>=4.0,<4.1 [testenv:coverage_setup] skip_install = True