Compare commits

..

1 Commits

Author SHA1 Message Date
imperosol
fa6e6c320f feat: cache user pictures 2025-09-17 07:47:22 +02:00
8 changed files with 43 additions and 14 deletions

View File

@@ -6,7 +6,7 @@ addAssignees: author
# A list of team reviewers to be added to pull requests (GitHub team slug) # A list of team reviewers to be added to pull requests (GitHub team slug)
reviewers: reviewers:
- ae-utbm/developpeurs - ae-utbm/sith-3-developers
# Number of reviewers has no impact on GitHub teams # Number of reviewers has no impact on GitHub teams
# Set 0 to add all the reviewers (default: 0) # Set 0 to add all the reviewers (default: 0)

View File

@@ -16,16 +16,7 @@ multi-ecosystem-groups:
updates: updates:
- package-ecosystem: "uv" - package-ecosystem: "uv"
patterns: ["*"]
multi-ecosystem-group: "common" multi-ecosystem-group: "common"
- package-ecosystem: "npm" - package-ecosystem: "npm"
patterns: ["*"]
multi-ecosystem-group: "common" multi-ecosystem-group: "common"
groups:
# npm supports production and development groups, but not uv
# cf. https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#dependency-type-groups
main-deps:
dependency-type: "production"
dev-deps:
dependency-type: "development"

View File

@@ -34,10 +34,12 @@ def migrate_meta_groups(apps: StateApps, schema_editor):
clubs = list(Club.objects.all()) clubs = list(Club.objects.all())
for club in clubs: for club in clubs:
club.board_group = meta_groups.get_or_create( club.board_group = meta_groups.get_or_create(
name=f"{club.unix_name}-bureau", defaults={"is_meta": True} name=club.unix_name + settings.SITH_BOARD_SUFFIX,
defaults={"is_meta": True},
)[0] )[0]
club.members_group = meta_groups.get_or_create( club.members_group = meta_groups.get_or_create(
name=f"{club.unix_name}-membres", defaults={"is_meta": True} name=club.unix_name + settings.SITH_MEMBER_SUFFIX,
defaults={"is_meta": True},
)[0] )[0]
club.save() club.save()
club.refresh_from_db() club.refresh_from_db()

View File

@@ -535,6 +535,13 @@ class Counter(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
def __getattribute__(self, name: str):
if name == "edit_groups":
return Group.objects.filter(
name=self.club.unix_name + settings.SITH_BOARD_SUFFIX
).all()
return object.__getattribute__(self, name)
def get_absolute_url(self) -> str: def get_absolute_url(self) -> str:
if self.type == "EBOUTIC": if self.type == "EBOUTIC":
return reverse("eboutic:main") return reverse("eboutic:main")

View File

@@ -7,6 +7,7 @@ import {
interface PagePictureConfig { interface PagePictureConfig {
userId: number; userId: number;
lastPhotoDate?: string;
} }
interface Album { interface Album {
@@ -20,11 +21,28 @@ document.addEventListener("alpine:init", () => {
loading: true, loading: true,
albums: [] as Album[], albums: [] as Album[],
async init() { async fetchPictures(): Promise<PictureSchema[]> {
const localStorageKey = `user${config.userId}Pictures`;
const localStorageDateKey = `user${config.userId}PicturesDate`;
const lastCachedDate = localStorage.getItem(localStorageDateKey);
if (
config.lastPhotoDate !== undefined &&
lastCachedDate !== undefined &&
lastCachedDate >= config.lastPhotoDate
) {
return JSON.parse(localStorage.getItem(localStorageKey));
}
const pictures = await paginated(picturesFetchPictures, { const pictures = await paginated(picturesFetchPictures, {
// biome-ignore lint/style/useNamingConvention: from python api // biome-ignore lint/style/useNamingConvention: from python api
query: { users_identified: [config.userId] }, query: { users_identified: [config.userId] },
} as PicturesFetchPicturesData); } as PicturesFetchPicturesData);
localStorage.setItem(localStorageDateKey, config.lastPhotoDate);
localStorage.setItem(localStorageKey, JSON.stringify(pictures));
return pictures;
},
async init() {
const pictures = await this.fetchPictures();
const groupedAlbums = Object.groupBy(pictures, (i: PictureSchema) => i.album.id); const groupedAlbums = Object.groupBy(pictures, (i: PictureSchema) => i.album.id);
this.albums = Object.values(groupedAlbums).map((pictures: PictureSchema[]) => { this.albums = Object.values(groupedAlbums).map((pictures: PictureSchema[]) => {
return { return {

View File

@@ -15,7 +15,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<main x-data="user_pictures({ userId: {{ object.id }} })"> <main x-data="user_pictures({ userId: {{ object.id }}, lastPhotoDate: '{{ object.last_photo_date }}' })">
{% if user.id == object.id %} {% if user.id == object.id %}
{{ download_button(_("Download all my pictures")) }} {{ download_button(_("Download all my pictures")) }}
{% endif %} {% endif %}

View File

@@ -16,6 +16,7 @@ from typing import Any
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import OuterRef, Subquery
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse from django.urls import reverse
@@ -178,6 +179,13 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
context_object_name = "profile" context_object_name = "profile"
template_name = "sas/user_pictures.jinja" template_name = "sas/user_pictures.jinja"
current_tab = "pictures" current_tab = "pictures"
queryset = User.objects.annotate(
last_photo_date=Subquery(
Picture.objects.filter(people__user=OuterRef("id"))
.order_by("-date")
.values("date")[:1]
)
).all()
# Admin views # Admin views

View File

@@ -405,6 +405,9 @@ SITH_FORUM_PAGE_LENGTH = 30
SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4) SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4)
SITH_SAS_IMAGES_PER_PAGE = 60 SITH_SAS_IMAGES_PER_PAGE = 60
SITH_BOARD_SUFFIX = "-bureau"
SITH_MEMBER_SUFFIX = "-membres"
SITH_PROFILE_DEPARTMENTS = [ SITH_PROFILE_DEPARTMENTS = [
("TC", _("TC")), ("TC", _("TC")),
("IMSI", _("IMSI")), ("IMSI", _("IMSI")),