Implement caching

This commit is contained in:
Gabriel Augendre 2022-09-25 21:08:44 +02:00
parent 3bf5ce3033
commit 3899e2a3db
10 changed files with 111 additions and 4 deletions

14
poetry.lock generated
View file

@ -353,6 +353,17 @@ python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
Django = ">=3.2" Django = ">=3.2"
[[package]]
name = "django-solo"
version = "2.0.0"
description = "Django Solo helps working with singletons"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
django = ">=2.2"
[[package]] [[package]]
name = "factory-boy" name = "factory-boy"
version = "3.2.1" version = "3.2.1"
@ -1164,7 +1175,7 @@ h11 = ">=0.9.0,<1"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "e0332f1446a90fc3a5dc2fa2ca5d1745eb0d90d8f033094d79b2df9376b79639" content-hash = "517db1c26bc459a29fdeae5f5ea18620c54121f95110d641b740ffdc322fb584"
[metadata.files] [metadata.files]
ansicon = [] ansicon = []
@ -1288,6 +1299,7 @@ django-environ = [
] ]
django-extensions = [] django-extensions = []
django-htmx = [] django-htmx = []
django-solo = []
factory-boy = [] factory-boy = []
faker = [] faker = []
filelock = [] filelock = []

View file

@ -22,6 +22,7 @@ crispy-bootstrap5 = "^0.6"
matplotlib = "^3.5.1" matplotlib = "^3.5.1"
freezegun = "^1.2.1" freezegun = "^1.2.1"
django-htmx = "^1.12.2" django-htmx = "^1.12.2"
django-solo = "^2.0.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pre-commit = "^2.7" pre-commit = "^2.7"

View file

@ -1,8 +1,9 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import register from django.contrib.admin import register
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from solo.admin import SingletonModelAdmin
from purchase.models import Basket, BasketItem, PaymentMethod, Product from purchase.models import Basket, BasketItem, CacheEtag, PaymentMethod, Product
from purchase.templatetags.purchase import currency from purchase.templatetags.purchase import currency
@ -70,3 +71,6 @@ class BasketAdmin(admin.ModelAdmin):
@admin.display(description=_("price")) @admin.display(description=_("price"))
def price(self, instance) -> str: def price(self, instance) -> str:
return currency(instance.price) return currency(instance.price)
admin.site.register(CacheEtag, SingletonModelAdmin)

View file

@ -1,6 +1,17 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_save
class PurchaseConfig(AppConfig): class PurchaseConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field = "django.db.models.BigAutoField"
name = "purchase" name = "purchase"
def ready(self):
from purchase.models import Basket, BasketItem, PaymentMethod, Product
from .signals import basket_item_on_save
post_save.connect(basket_item_on_save, sender=BasketItem)
post_save.connect(basket_item_on_save, sender=Basket)
post_save.connect(basket_item_on_save, sender=Product)
post_save.connect(basket_item_on_save, sender=PaymentMethod)

View file

@ -0,0 +1,33 @@
# Generated by Django 4.1.1 on 2022-09-25 19:00
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("purchase", "0008_basketitem_unique_product_per_basket"),
]
operations = [
migrations.CreateModel(
name="BasketItemEtag",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.UUIDField(default=uuid.uuid4)),
],
options={
"abstract": False,
},
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 4.1.1 on 2022-09-25 19:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("purchase", "0009_basketitemetag"),
]
operations = [
migrations.RenameModel(
old_name="BasketItemEtag",
new_name="CacheEtag",
),
]

View file

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
import uuid
from django.db import models from django.db import models
from django.db.models import Avg, Count, F, Sum, UniqueConstraint from django.db.models import Avg, Count, F, Sum, UniqueConstraint
@ -9,6 +10,7 @@ from django.urls import reverse
from django.utils.translation import gettext from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from PIL import Image, ImageOps from PIL import Image, ImageOps
from solo.models import SingletonModel
class Model(models.Model): class Model(models.Model):
@ -220,3 +222,14 @@ class BasketItem(Model):
constraints = [ constraints = [
UniqueConstraint("product", "basket", name="unique_product_per_basket") UniqueConstraint("product", "basket", name="unique_product_per_basket")
] ]
class CacheEtag(SingletonModel):
value = models.UUIDField(default=uuid.uuid4)
def __str__(self) -> str:
return str(self.value)
def refresh(self):
self.value = uuid.uuid4()
self.save()

4
src/purchase/signals.py Normal file
View file

@ -0,0 +1,4 @@
def basket_item_on_save(sender, **kwargs):
from purchase.models import CacheEtag
CacheEtag.get_solo().refresh()

View file

@ -5,14 +5,16 @@ from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import condition, require_http_methods
from purchase.forms import BasketForm from purchase.forms import BasketForm
from purchase.models import Basket from purchase.models import Basket
from purchase.views.reports import reports_etag
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
@permission_required("purchase.add_basket") @permission_required("purchase.add_basket")
@condition(etag_func=reports_etag)
def new_basket(request: HttpRequest) -> HttpResponse: def new_basket(request: HttpRequest) -> HttpResponse:
if request.method == "POST": if request.method == "POST":
form = BasketForm(request.POST) form = BasketForm(request.POST)
@ -32,6 +34,7 @@ def new_basket(request: HttpRequest) -> HttpResponse:
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
@permission_required("purchase.change_basket") @permission_required("purchase.change_basket")
@condition(etag_func=reports_etag)
def update_basket(request: HttpRequest, pk: int) -> HttpResponse: def update_basket(request: HttpRequest, pk: int) -> HttpResponse:
basket = get_object_or_404(Basket.objects.priced(), pk=pk) basket = get_object_or_404(Basket.objects.priced(), pk=pk)
if request.method == "POST": if request.method == "POST":
@ -49,6 +52,7 @@ def update_basket(request: HttpRequest, pk: int) -> HttpResponse:
@permission_required("purchase.view_basket") @permission_required("purchase.view_basket")
@condition(etag_func=reports_etag)
def list_baskets(request: HttpRequest) -> HttpResponse: def list_baskets(request: HttpRequest) -> HttpResponse:
context = {"baskets": Basket.objects.priced().order_by("-id")} context = {"baskets": Basket.objects.priced().order_by("-id")}
return TemplateResponse(request, "purchase/basket_list.html", context) return TemplateResponse(request, "purchase/basket_list.html", context)

View file

@ -10,6 +10,7 @@ from django.contrib.auth.decorators import permission_required
from django.shortcuts import render from django.shortcuts import render
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.decorators.http import condition
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from matplotlib import ticker from matplotlib import ticker
from matplotlib.axes import Axes from matplotlib.axes import Axes
@ -17,12 +18,17 @@ from matplotlib.container import BarContainer
from matplotlib.dates import AutoDateLocator, ConciseDateFormatter, HourLocator from matplotlib.dates import AutoDateLocator, ConciseDateFormatter, HourLocator
from matplotlib.figure import Figure from matplotlib.figure import Figure
from purchase.models import Basket, PaymentMethod, Product, ProductQuerySet from purchase.models import Basket, CacheEtag, PaymentMethod, Product, ProductQuerySet
matplotlib.use("SVG") matplotlib.use("SVG")
def reports_etag(request):
return str(CacheEtag.get_solo().value)
@permission_required("purchase.view_basket") @permission_required("purchase.view_basket")
@condition(etag_func=reports_etag)
def products_plots_view(request): def products_plots_view(request):
products = Product.objects.with_turnover().with_sold() products = Product.objects.with_turnover().with_sold()
( (
@ -37,6 +43,7 @@ def products_plots_view(request):
@permission_required("purchase.view_basket") @permission_required("purchase.view_basket")
@condition(etag_func=reports_etag)
def by_hour_plot_view(request): def by_hour_plot_view(request):
baskets = list(Basket.objects.priced().order_by("created_at")) baskets = list(Basket.objects.priced().order_by("created_at"))
context = { context = {
@ -46,6 +53,7 @@ def by_hour_plot_view(request):
@permission_required("purchase.view_basket") @permission_required("purchase.view_basket")
@condition(etag_func=reports_etag)
def reports(request): def reports(request):
template_name = "purchase/reports.html" template_name = "purchase/reports.html"
baskets = list(Basket.objects.priced().order_by("created_at")) baskets = list(Basket.objects.priced().order_by("created_at"))