Compare commits

..

3 Commits

Author SHA1 Message Date
thomas girod f9c5297473 Merge pull request #1358 from ae-utbm/sas-style
Sas style improvement
2026-04-22 22:37:24 +02:00
imperosol 52117b5a24 add og tags to sas main page
Quand quelqu'un qui n'a pas le droit tente d'accéder au SAS, il reçoit un HTTP 200 au lieu d'un 403. C'est pas forcément le plus pertinent, mais autant en profiter pour mettre les tags og.
2026-04-22 15:02:03 +02:00
imperosol ae72a2e00f improve SAS picture tools style 2026-04-22 15:02:03 +02:00
7 changed files with 89 additions and 91 deletions
+5 -24
View File
@@ -1,22 +1,16 @@
from pathlib import Path from typing import Any
from typing import TYPE_CHECKING, Any
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from PIL import Image
from core.models import User from core.models import User
from core.utils import resize_image
from core.views import MultipleImageField from core.views import MultipleImageField
from core.views.forms import SelectDate from core.views.forms import SelectDate
from core.views.widgets.ajax_select import AutoCompleteSelectMultipleGroup from core.views.widgets.ajax_select import AutoCompleteSelectMultipleGroup
from sas.models import Album, Picture, PictureModerationRequest from sas.models import Album, Picture, PictureModerationRequest
from sas.widgets.ajax_select import AutoCompleteSelectAlbum from sas.widgets.ajax_select import AutoCompleteSelectAlbum
if TYPE_CHECKING:
from django.db.models.fields.files import FieldFile
class AlbumCreateForm(forms.ModelForm): class AlbumCreateForm(forms.ModelForm):
class Meta: class Meta:
@@ -55,30 +49,17 @@ class AlbumEditForm(forms.ModelForm):
class Meta: class Meta:
model = Album model = Album
fields = ["name", "date", "file", "parent", "edit_groups"] fields = ["name", "date", "file", "parent", "edit_groups"]
widgets = {"edit_groups": AutoCompleteSelectMultipleGroup, "date": SelectDate} widgets = {
"edit_groups": AutoCompleteSelectMultipleGroup,
}
name = forms.CharField(max_length=Album.NAME_MAX_LENGTH, label=_("file name")) name = forms.CharField(max_length=Album.NAME_MAX_LENGTH, label=_("file name"))
date = forms.DateField(label=_("Date"), widget=SelectDate, required=True)
recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False)
parent = forms.ModelChoiceField( parent = forms.ModelChoiceField(
Album.objects.all(), required=True, widget=AutoCompleteSelectAlbum Album.objects.all(), required=True, widget=AutoCompleteSelectAlbum
) )
def clean_file(self):
# if a file was given in the form, resize it
f: FieldFile = self.cleaned_data["file"]
if self.errors or not f or "file" not in self.changed_data:
return f
f.file = resize_image(Image.open(f.file), 200, "WEBP")
return f
def save(self, commit=True): # noqa: FBT002
if self.instance.file:
self.instance.file.name = str(Path(self.instance.name) / "thumb.webp")
self.instance = super().save(commit=commit)
if not self.instance.file:
self.instance.generate_thumbnail()
return self.instance
class PictureModerationRequestForm(forms.ModelForm): class PictureModerationRequestForm(forms.ModelForm):
"""Form to create a PictureModerationRequest. """Form to create a PictureModerationRequest.
+10 -4
View File
@@ -110,7 +110,7 @@ class Picture(SasFile):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("sas:picture", kwargs={"picture_id": self.id}) return reverse("sas:picture", kwargs={"picture_id": self.id})
def generate_thumbnails(self): def generate_thumbnails(self, *, overwrite=False):
im = Image.open(BytesIO(self.file.read())) im = Image.open(BytesIO(self.file.read()))
with contextlib.suppress(Exception): with contextlib.suppress(Exception):
im = exif_auto_rotate(im) im = exif_auto_rotate(im)
@@ -126,6 +126,10 @@ class Picture(SasFile):
file = resize_image(im, max(im.size), extension, optimize=False) file = resize_image(im, max(im.size), extension, optimize=False)
thumb = resize_image(im, 200, "webp") thumb = resize_image(im, 200, "webp")
compressed = resize_image(im, 1200, "webp") compressed = resize_image(im, 1200, "webp")
if overwrite:
self.file.delete()
self.thumbnail.delete()
self.compressed.delete()
new_extension_name = str(Path(self.name).with_suffix(".webp")) new_extension_name = str(Path(self.name).with_suffix(".webp"))
self.file = file self.file = file
self.file.name = self.name self.file.name = self.name
@@ -241,15 +245,17 @@ class Album(SasFile):
return reverse("sas:album_preview", kwargs={"album_id": self.id}) return reverse("sas:album_preview", kwargs={"album_id": self.id})
def generate_thumbnail(self): def generate_thumbnail(self):
p = self.children_pictures.order_by("?").first() or ( p = (
self.children_albums.exclude(Q(file=None) | Q(file="")) self.children_pictures.order_by("?").first()
or self.children_albums.exclude(file=None)
.exclude(file="")
.order_by("?") .order_by("?")
.first() .first()
) )
if p and p.file: if p and p.file:
image = resize_image(Image.open(BytesIO(p.file.read())), 200, "webp") image = resize_image(Image.open(BytesIO(p.file.read())), 200, "webp")
self.file = image self.file = image
self.file.name = str(Path(self.name) / "thumb.webp") self.file.name = f"{self.name}/thumb.webp"
self.save() self.save()
+24 -29
View File
@@ -134,7 +134,7 @@
--loading-size: 20px --loading-size: 20px
} }
@media (max-width: 1000px) { @media (min-width: 700px) and (max-width: 1000px) {
max-width: calc(50% - 5px); max-width: calc(50% - 5px);
} }
@@ -201,57 +201,52 @@
} }
} }
.general { #pict .general {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 20px; gap: 3em;
justify-content: space-evenly;
@media (max-width: 1000px) { @media (max-width: 1000px) {
gap: 1em;
flex-direction: column; flex-direction: column;
} }
>.infos { .infos, .tools {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 50%; gap: .5em;
@media (min-width: 700px) {
max-width: 350px;
}
}
.infos > div, .tools > div > div {
display: flex;
flex-direction: column;
gap: .35em;
}
>div>div { .tools > div, >.infos >div>div {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
>*:first-child {
min-width: 150px;
@media (max-width: 1000px) {
min-width: auto;
}
}
}
} }
>.tools { >.tools {
display: flex; flex: 1;
flex-direction: column;
width: 50%;
>div { >div>div {
display: flex; >a.btn {
flex-direction: row;
justify-content: space-between;
>div {
>a.button {
box-sizing: border-box;
background-color: $primary-neutral-light-color; background-color: $primary-neutral-light-color;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 10px; padding: 0;
color: black; color: black;
border-radius: 5px;
width: 40px; width: 40px;
height: 40px; height: 40px;
font-size: 20px;
&:hover { &:hover {
background-color: #aaa; background-color: #aaa;
@@ -268,9 +263,9 @@
&.buttons { &.buttons {
display: flex; display: flex;
flex-direction: row;
gap: 5px; gap: 5px;
} }
} }
} }
}
} }
+4 -4
View File
@@ -2,19 +2,19 @@
<a href="{{ url('sas:album', album_id=a.id) }}"> <a href="{{ url('sas:album', album_id=a.id) }}">
{% if a.file %} {% if a.file %}
{% set img = a.get_download_url() %} {% set img = a.get_download_url() %}
{% set alt = a.name %} {% set src = a.name %}
{% elif a.children.filter(is_folder=False, is_moderated=True).exists() %} {% elif a.children.filter(is_folder=False, is_moderated=True).exists() %}
{% set picture = a.children.filter(is_folder=False).first().as_picture %} {% set picture = a.children.filter(is_folder=False).first().as_picture %}
{% set img = picture.get_download_thumb_url() %} {% set img = picture.get_download_thumb_url() %}
{% set alt = picture.name %} {% set src = picture.name %}
{% else %} {% else %}
{% set img = static('core/img/sas.jpg') %} {% set img = static('core/img/sas.jpg') %}
{% set alt = "sas.jpg" %} {% set src = "sas.jpg" %}
{% endif %} {% endif %}
<div <div
class="album{% if not a.is_moderated %} not_moderated{% endif %}" class="album{% if not a.is_moderated %} not_moderated{% endif %}"
> >
<img src="{{ img }}" alt="{{ alt }}" loading="lazy" /> <img src="{{ img }}" alt="{{ src }}" loading="lazy" />
{% if not a.is_moderated %} {% if not a.is_moderated %}
<div class="overlay">&nbsp;</div> <div class="overlay">&nbsp;</div>
<div class="text">{% trans %}To be moderated{% endtrans %}</div> <div class="text">{% trans %}To be moderated{% endtrans %}</div>
+11
View File
@@ -12,6 +12,17 @@
{% trans %}See all the photos taken during events organised by the AE.{% endtrans %} {% trans %}See all the photos taken during events organised by the AE.{% endtrans %}
{%- endblock %} {%- endblock %}
{% block metatags %}
<meta property="og:url" content="{{ request.build_absolute_uri() }}" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Stock à souvenirs" />
<meta
property="og:description"
content="Retrouvez toutes les photos prises durant les événements organisés par l'AE."
/>
<meta property="og:image" content="{{ request.build_absolute_uri(static("core/img/logo_no_text.png")) }}" />
{% endblock %}
{% set is_sas_admin = user.is_root or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) %} {% set is_sas_admin = user.is_root or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) %}
{% from "sas/macros.jinja" import display_album %} {% from "sas/macros.jinja" import display_album %}
+10 -5
View File
@@ -118,15 +118,20 @@
<a class="text" :href="currentPicture.full_size_url"> <a class="text" :href="currentPicture.full_size_url">
{% trans %}HD version{% endtrans %} {% trans %}HD version{% endtrans %}
</a> </a>
<br> <a class="text danger " :href="currentPicture.report_url">
<a class="text danger" :href="currentPicture.report_url">
{% trans %}Ask for removal{% endtrans %} {% trans %}Ask for removal{% endtrans %}
</a> </a>
</div> </div>
<div class="buttons"> <div class="buttons">
<a class="button" :href="currentPicture.edit_url"><i class="fa-regular fa-pen-to-square edit-action"></i></a> <a
<a class="button" href="?rotate_left"><i class="fa-solid fa-rotate-left"></i></a> class="btn btn-no-text"
<a class="button" href="?rotate_right"><i class="fa-solid fa-rotate-right"></i></a> :href="currentPicture.edit_url"
x-show="{{ user.has_perm("sas.change_sasfile")|tojson }} || currentPicture.owner.id === {{ user.id }}"
>
<i class="fa-regular fa-pen-to-square edit-action"></i>
</a>
<a class="btn btn-no-text" href="?rotate_left"><i class="fa-solid fa-rotate-left"></i></a>
<a class="btn btn-no-text" href="?rotate_right"><i class="fa-solid fa-rotate-right"></i></a>
</div> </div>
</div> </div>
</div> </div>
+3 -3
View File
@@ -16,7 +16,6 @@ from typing import Any
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db.models import Count, OuterRef, Subquery from django.db.models import Count, OuterRef, Subquery
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@@ -153,8 +152,9 @@ class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if not request.user.can_edit(self.object): if not self.object.file:
raise PermissionDenied self.object.generate_thumbnail()
if request.user.can_edit(self.object): # Handle the copy-paste functions
FileView.handle_clipboard(request, self.object) FileView.handle_clipboard(request, self.object)
return HttpResponseRedirect(self.request.path) return HttpResponseRedirect(self.request.path)