mirror of
https://github.com/Crocmagnon/checkout.git
synced 2024-11-24 17:18:03 +01:00
Implement caching
This commit is contained in:
parent
3bf5ce3033
commit
3899e2a3db
10 changed files with 111 additions and 4 deletions
14
poetry.lock
generated
14
poetry.lock
generated
|
@ -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 = []
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
33
src/purchase/migrations/0009_basketitemetag.py
Normal file
33
src/purchase/migrations/0009_basketitemetag.py
Normal 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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -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",
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
4
src/purchase/signals.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
def basket_item_on_save(sender, **kwargs):
|
||||||
|
from purchase.models import CacheEtag
|
||||||
|
|
||||||
|
CacheEtag.get_solo().refresh()
|
|
@ -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)
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
Loading…
Reference in a new issue