Calculate ETag for the index page using last modified date
This commit is contained in:
parent
43abe234fb
commit
b56e6be8c9
|
@ -12,3 +12,4 @@ stale_outputs_checked
|
||||||
__pycache__
|
__pycache__
|
||||||
*.iml
|
*.iml
|
||||||
*.pages
|
*.pages
|
||||||
|
/git_commit
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 4.0.3 on 2023-12-12 20:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('thoughts', '0007_thought_html_content_alter_thought_posted'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='thought',
|
||||||
|
name='last_updated',
|
||||||
|
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 4.0.3 on 2023-12-12 21:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('thoughts', '0008_thought_last_updated'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='thought',
|
||||||
|
index=models.Index(fields=['-posted'], name='thoughts_th_posted_6b4f21_idx'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='thought',
|
||||||
|
index=models.Index(fields=['-last_updated'], name='thoughts_th_last_up_2f98ba_idx'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -68,6 +68,12 @@ class Thought(models.Model):
|
||||||
except cls.DoesNotExist:
|
except cls.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["-posted"]),
|
||||||
|
models.Index(fields=["-last_updated"])
|
||||||
|
]
|
||||||
|
|
||||||
# Honestly I'm so sick of this problem that writing out a comment here to explain why it is necessary is beyond me.
|
# Honestly I'm so sick of this problem that writing out a comment here to explain why it is necessary is beyond me.
|
||||||
# I'm calling this CharField and not MySpecialLineNormalizingCharField
|
# I'm calling this CharField and not MySpecialLineNormalizingCharField
|
||||||
# because I think this should be the default behavior and I don't understand why it's not.
|
# because I think this should be the default behavior and I don't understand why it's not.
|
||||||
|
@ -126,7 +132,7 @@ class ThoughtForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Thought
|
model = Thought
|
||||||
exclude = ["posted"]
|
exclude = ["posted", "last_updated"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"timezone_offset": forms.NumberInput,
|
"timezone_offset": forms.NumberInput,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
||||||
import subprocess
|
import subprocess
|
||||||
import base64
|
import base64
|
||||||
import collections
|
import collections
|
||||||
|
import hashlib
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
|
@ -10,6 +11,7 @@ import magic
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.utils.crypto import constant_time_compare
|
from django.utils.crypto import constant_time_compare
|
||||||
|
from django.views.decorators.http import condition
|
||||||
|
|
||||||
from haystack.views import SearchView, search_view_factory
|
from haystack.views import SearchView, search_view_factory
|
||||||
from haystack.query import SearchQuerySet
|
from haystack.query import SearchQuerySet
|
||||||
|
@ -21,7 +23,7 @@ from .pagination import get_all_pages, get_page_slug
|
||||||
|
|
||||||
# Python's itertools standard library is bad
|
# Python's itertools standard library is bad
|
||||||
# Instead of providing functions
|
# Instead of providing functions
|
||||||
# They provide "receipes" that you can copy-paste into your own program
|
# They provide "recipes" that you can copy-paste into your own program
|
||||||
# => https://docs.python.org/3/library/itertools.html#itertools-recipes
|
# => https://docs.python.org/3/library/itertools.html#itertools-recipes
|
||||||
def sliding_window(iterable, n):
|
def sliding_window(iterable, n):
|
||||||
# sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
|
# sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
|
||||||
|
@ -43,15 +45,14 @@ def check_authenticated(request):
|
||||||
pass
|
pass
|
||||||
return authenticated
|
return authenticated
|
||||||
|
|
||||||
|
def get_highlighted_uuid(request):
|
||||||
def index(request):
|
|
||||||
authenticated = check_authenticated(request)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
highlighted_uuid = uuid.UUID(request.GET.get("show", ""))
|
return uuid.UUID(request.GET.get("show", ""))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
highlighted_uuid = ""
|
return ""
|
||||||
|
|
||||||
|
# Return the previous, current, and next page
|
||||||
|
def get_pages(request):
|
||||||
## Figure out what page we're viewing
|
## Figure out what page we're viewing
|
||||||
pages = get_all_pages()
|
pages = get_all_pages()
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ def index(request):
|
||||||
|
|
||||||
# If we've passed a valid highlighted thought uuid,
|
# If we've passed a valid highlighted thought uuid,
|
||||||
# that takes priority over a page= value, so we overwrite requested_slug
|
# that takes priority over a page= value, so we overwrite requested_slug
|
||||||
|
highlighted_uuid = get_highlighted_uuid(request)
|
||||||
if highlighted_uuid:
|
if highlighted_uuid:
|
||||||
try:
|
try:
|
||||||
requested_slug = get_page_slug(Thought.objects.get(uuid=highlighted_uuid))
|
requested_slug = get_page_slug(Thought.objects.get(uuid=highlighted_uuid))
|
||||||
|
@ -82,8 +84,42 @@ def index(request):
|
||||||
if nex.slug == requested_slug:
|
if nex.slug == requested_slug:
|
||||||
previous_page, current_page, next_page = curr, nex, None
|
previous_page, current_page, next_page = curr, nex, None
|
||||||
|
|
||||||
|
return previous_page, current_page, next_page
|
||||||
|
|
||||||
|
|
||||||
|
# Takes a Queryset of Thoughts and returns a hash of:
|
||||||
|
# the last-modified time for the most recently modified
|
||||||
|
# the number of thoughts
|
||||||
|
# and the current git commit (read from settings (which reads it from the git_commit file or randomly generates an id))
|
||||||
|
# I can't think of any edge cases. It's impossible to change page without changing the query params, the time, or the count
|
||||||
|
# I still need to double check that different query params are treated differently
|
||||||
|
def get_etag_from_thoughts(thoughts):
|
||||||
|
count = thoughts.count()
|
||||||
|
last_updated = thoughts.order_by("-last_updated").first().last_updated
|
||||||
|
# Do I need to include query params? I'd hope that it was seen as a different URL
|
||||||
|
|
||||||
|
hash_str = f"{count}:{last_updated}:{settings.COMMIT}"
|
||||||
|
return hashlib.sha256(hash_str.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def get_etag(request):
|
||||||
|
previous_page, current_page, next_page = get_pages(request)
|
||||||
|
|
||||||
thoughts = current_page.get_all_entries()
|
thoughts = current_page.get_all_entries()
|
||||||
|
|
||||||
|
return get_etag_from_thoughts(thoughts)
|
||||||
|
|
||||||
|
|
||||||
|
@condition(etag_func=get_etag)
|
||||||
|
def index(request):
|
||||||
|
authenticated = check_authenticated(request)
|
||||||
|
|
||||||
|
previous_page, current_page, next_page = get_pages(request)
|
||||||
|
|
||||||
|
thoughts = current_page.get_all_entries()
|
||||||
|
|
||||||
|
highlighted_uuid = get_highlighted_uuid(request)
|
||||||
|
|
||||||
return render(request, "thoughts/index.html", {
|
return render(request, "thoughts/index.html", {
|
||||||
"thoughts": thoughts,
|
"thoughts": thoughts,
|
||||||
"highlighted": highlighted_uuid,
|
"highlighted": highlighted_uuid,
|
||||||
|
@ -91,8 +127,8 @@ def index(request):
|
||||||
"page": current_page,
|
"page": current_page,
|
||||||
"previous_page_slug": getattr(previous_page, "slug", None),
|
"previous_page_slug": getattr(previous_page, "slug", None),
|
||||||
"next_page_slug": getattr(next_page, "slug", None),
|
"next_page_slug": getattr(next_page, "slug", None),
|
||||||
"is_first_page": current_page.slug == pages[0].slug, # if you're viewing the first page
|
"is_first_page": previous_page is None, # if you're viewing the first page
|
||||||
"is_last_page": current_page.slug == pages[-1].slug
|
"is_last_page": next_page is None
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from decouple import config
|
from decouple import config
|
||||||
|
import secrets
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / "subdir".
|
# Build paths inside the project like this: BASE_DIR / "subdir".
|
||||||
|
@ -40,6 +41,14 @@ def split_string(string):
|
||||||
# DO NOT USE THE COLORS I USE. These values right here are black and white, if you're lazy, keep them
|
# DO NOT USE THE COLORS I USE. These values right here are black and white, if you're lazy, keep them
|
||||||
COLORS = config("COLORS", default="#FEFEFE,#222222,#999999", cast=split_string)
|
COLORS = config("COLORS", default="#FEFEFE,#222222,#999999", cast=split_string)
|
||||||
|
|
||||||
|
# The current git commit if known; used for cache invalidation
|
||||||
|
COMMIT = ""
|
||||||
|
try:
|
||||||
|
with open(BASE_DIR / "git_commit", "r") as f:
|
||||||
|
COMMIT = f.read().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
COMMIT = "unknown-" + secrets.token_hex(15)
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
|
Loading…
Reference in New Issue