Merge branch 'AddCache' into 'master'

Add cache & optimise SQL requests

See merge request bde/photo21!33
This commit is contained in:
loulous27 2025-11-23 15:13:56 +01:00
commit 90c7bdfd38
5 changed files with 81 additions and 17 deletions

View file

@ -0,0 +1,25 @@
"""A generator of small picture to test very larges galeries"""
from PIL import Image
from PIL import ImageDraw
import argparse
parser = argparse.ArgumentParser(
description="A generator of small picture to test very larges galeries"
)
parser.add_argument("count", help="Numbers of photo to generate", type=int)
parser.add_argument(
"-outputfolder", help="The outputfolders by default : ./photos/", default="photos/"
)
args = parser.parse_args()
for i in range(args.count):
if (100//5 * (i + 1)) % args.count == 0: # affichage tout les 5%
print(f"Image {i+1} : {(i+1)/args.count:.0%}")
img = Image.new(mode="RGB",size=(100,100),color=(0,0,0))
ImageDraw.Draw(img).text((0,0),str(i+1),(255,255,255))
img.save(args.outputfolder+f"img_{i+1}.jpg",)

View file

@ -37,6 +37,11 @@ ALLOWED_HOSTS = [
"photos-dev.crans.org",
]
INTERNAL_IPS = [
"127.0.0.1",
"localhost",
]
# Admins receive server errors, this is useful to be notified of potential bugs
ADMINS = [
("admin", "photos-admin@lists.crans.org"),
@ -68,9 +73,12 @@ INSTALLED_APPS = [
"allauth_note_kfet",
"crispy_forms",
"photologue",
"photo21"
"photo21",
]
if DEBUG:
INSTALLED_APPS += ["debug_toolbar",] # For debug and optimisations
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
@ -81,8 +89,10 @@ MIDDLEWARE = [
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.contrib.sites.middleware.CurrentSiteMiddleware",
"allauth.account.middleware.AccountMiddleware",
"allauth.account.middleware.AccountMiddleware", # For the django =< 5.0
]
if DEBUG :
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware",]
ROOT_URLCONF = "photo21.urls"
@ -122,6 +132,13 @@ DATABASES = {
}
}
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "Master",
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
@ -206,11 +223,11 @@ MESSAGE_TAGS = {
}
# Allauth configuration
# ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*']
ACCOUNT_EMAIL_REQUIRED = True
# ACCOUNT_SIGNUP_FIELDS = ['email*', 'username*', 'password1*', 'password2*']
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
# ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_LOGIN_METHODS = {'username', 'email'}
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
# ACCOUNT_LOGIN_METHODS = {'username', 'email'}
ACCOUNT_FORMS = {"signup": "photo21.forms.CustomSignupForm"}
SOCIALACCOUNT_PROVIDERS = {
"notekfet": {

View file

@ -13,6 +13,9 @@ from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path, re_path
if settings.DEBUG :
from debug_toolbar.toolbar import debug_toolbar_urls
from .views import IndexView, MediaAccess
urlpatterns = [
@ -27,6 +30,7 @@ urlpatterns = [
# In production media are served through NGINX with X-Accel-Redirect
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += debug_toolbar_urls()
else:
urlpatterns.append(
re_path("^media/(?P<path>.*)", MediaAccess.as_view(), name="media")

View file

@ -3,7 +3,6 @@
import logging
import os
import random
import unicodedata
from datetime import datetime
from functools import partial
@ -16,6 +15,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.core.validators import RegexValidator
from django.core.cache import caches
from django.db import models
from django.template.defaultfilters import slugify
from django.urls import reverse
@ -25,6 +25,8 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from PIL import Image, ImageFile, ImageFilter
logger = logging.getLogger("photologue.models")
# Default limit for gallery.latest
@ -184,13 +186,15 @@ class Gallery(models.Model):
def sample(self, public=True):
"""Return a sample of photos, ordered at random."""
count = 1
if count > self.photo_count():
count = self.photo_count()
nb = self.photo_count(public) #Optimisation don't do twice the SQL requests
if nb < count:
count = nb
if public:
photo_set = self.photos.filter(is_public=True)
else:
photo_set = self.photos
return random.sample(list(photo_set), count)
return photo_set.order_by("?")[:count] # Use native SQL random
def photo_count(self, public=True):
"""Return a count of all the photos in this gallery."""
@ -721,10 +725,16 @@ class PhotoSizeCache:
def __init__(self):
self.__dict__ = self.__state
if not len(self.sizes):
sizes = PhotoSize.objects.all()
for size in sizes:
self.sizes[size.name] = size
cached = caches.get("PhotoSizeCache",None)
if cached is None :
if not len(self.sizes):
sizes = PhotoSize.objects.all()
for size in sizes:
self.sizes[size.name] = size
caches.set("PhotoSizeCache",self)
else :
self = cached
def reset(self):
global size_method_map

View file

@ -20,10 +20,16 @@ from django.views.generic.dates import ArchiveIndexView, YearArchiveView
from django.views.generic.detail import DetailView
from django.views.generic.edit import DeleteView, FormView
from PIL import Image
from django.contrib.auth import get_user_model
from .forms import UploadForm
from .models import Gallery, Photo, Tag
# Cette ligne renvoie le modèle d'utilisateur actif (le natif ou le vôtre)
User = get_user_model()
class GalleryDateView(LoginRequiredMixin):
model = Gallery
@ -135,11 +141,11 @@ class GalleryDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Non-staff members only see public photos
# Non-staff members only see public photos + prefetch all owners informations (Optimisation)
if self.request.user.is_staff:
context["photos"] = self.object.photos.all()
context["photos"] = self.object.photos.all().select_related('owner')
else:
context["photos"] = self.object.photos.filter(is_public=True)
context["photos"] = self.object.photos.filter(is_public=True).select_related('onwer')
# List owners
context["owners"] = []
@ -147,6 +153,8 @@ class GalleryDetailView(LoginRequiredMixin, DetailView):
if photo.owner not in context["owners"]:
context["owners"].append(photo.owner)
# Filter on owner
if "owner" in self.kwargs:
context["photos"] = context["photos"].filter(owner__id=self.kwargs["owner"])