mirror of
https://github.com/ae-utbm/sith.git
synced 2026-04-25 08:06:14 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
8a2eee113a
|
|||
|
0360d53cd6
|
|||
| f9c5297473 | |||
| 52117b5a24 | |||
| ae72a2e00f | |||
| fdf89ea716 | |||
| 3954f2f170 | |||
| d36d672d0b |
@@ -110,7 +110,9 @@ class Command(BaseCommand):
|
|||||||
p.save(force_lock=True)
|
p.save(force_lock=True)
|
||||||
|
|
||||||
club_root = SithFile.objects.create(name="clubs", owner=root)
|
club_root = SithFile.objects.create(name="clubs", owner=root)
|
||||||
sas = SithFile.objects.create(name="SAS", owner=root, is_in_sas=True)
|
sas = SithFile.objects.create(
|
||||||
|
name="SAS", owner=root, id=settings.SITH_SAS_ROOT_DIR_ID
|
||||||
|
)
|
||||||
main_club = Club.objects.create(
|
main_club = Club.objects.create(
|
||||||
id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort"
|
id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort"
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -886,8 +886,10 @@ class SithFile(models.Model):
|
|||||||
return self.get_parent_path() + "/" + self.name
|
return self.get_parent_path() + "/" + self.name
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first()
|
sas_id = settings.SITH_SAS_ROOT_DIR_ID
|
||||||
self.is_in_sas = sas in self.get_parent_list() or self == sas
|
self.is_in_sas = self.id == sas_id or any(
|
||||||
|
p.id == sas_id for p in self.get_parent_list()
|
||||||
|
)
|
||||||
adding = self._state.adding
|
adding = self._state.adding
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
if adding:
|
if adding:
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<div id="quick-notifications"
|
<div id="quick-notifications"
|
||||||
x-data="{
|
x-data='{
|
||||||
messages: [
|
messages: [
|
||||||
{%- for message in messages -%}
|
{%- for message in messages -%}
|
||||||
{%- if not message.extra_tags -%}
|
{%- if not message.extra_tags -%}
|
||||||
{ tag: '{{ message.tags }}', text: '{{ message }}' },
|
{ tag: {{ message.tags|string|tojson }}, text: {{ message|string|tojson }} },
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
]
|
]
|
||||||
}"
|
}'
|
||||||
@quick-notification-add="(e) => messages.push(e?.detail)"
|
@quick-notification-add="(e) => messages.push(e?.detail)"
|
||||||
@quick-notification-delete="messages = []">
|
@quick-notification-delete="messages = []">
|
||||||
<template x-for="(message, index) in messages">
|
<template x-for="(message, index) in messages">
|
||||||
|
|||||||
@@ -344,3 +344,14 @@ def test_quick_upload_image(
|
|||||||
assert (
|
assert (
|
||||||
parsed["name"] == Path(file.name).stem[: QuickUploadImage.IMAGE_NAME_SIZE - 1]
|
parsed["name"] == Path(file.name).stem[: QuickUploadImage.IMAGE_NAME_SIZE - 1]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_populated_sas_is_in_sas():
|
||||||
|
"""Test that, in the data generated by the populate command,
|
||||||
|
the SAS has value is_in_sas=True.
|
||||||
|
|
||||||
|
If it's not the case, it has no incidence in prod, but it's annoying
|
||||||
|
in dev and may cause misunderstandings.
|
||||||
|
"""
|
||||||
|
assert SithFile.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID).is_in_sas
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-03-23 22:21+0100\n"
|
"POT-Creation-Date: 2026-04-25 01:00+0200\n"
|
||||||
"PO-Revision-Date: 2016-07-18\n"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@@ -181,6 +181,22 @@ msgstr "Vous devez être cotisant pour faire partie d'un club"
|
|||||||
msgid "You are already a member of this club"
|
msgid "You are already a member of this club"
|
||||||
msgstr "Vous êtes déjà membre de ce club."
|
msgstr "Vous êtes déjà membre de ce club."
|
||||||
|
|
||||||
|
#: club/forms.py
|
||||||
|
msgid "Club status"
|
||||||
|
msgstr "État du club"
|
||||||
|
|
||||||
|
#: club/forms.py
|
||||||
|
msgid "Active"
|
||||||
|
msgstr "Actif"
|
||||||
|
|
||||||
|
#: club/forms.py
|
||||||
|
msgid "Inactive"
|
||||||
|
msgstr "Inactif"
|
||||||
|
|
||||||
|
#: club/forms.py
|
||||||
|
msgid "All clubs"
|
||||||
|
msgstr "Tous les clubs"
|
||||||
|
|
||||||
#: club/models.py
|
#: club/models.py
|
||||||
msgid "slug name"
|
msgid "slug name"
|
||||||
msgstr "nom slug"
|
msgstr "nom slug"
|
||||||
@@ -301,37 +317,22 @@ msgstr "Cet email est déjà abonné à cette mailing"
|
|||||||
msgid "Unregistered user"
|
msgid "Unregistered user"
|
||||||
msgstr "Utilisateur non enregistré"
|
msgstr "Utilisateur non enregistré"
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
|
||||||
msgid "Club list"
|
|
||||||
msgstr "Liste des clubs"
|
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
#: club/templates/club/club_list.jinja
|
||||||
msgid "The list of all clubs existing at UTBM."
|
msgid "The list of all clubs existing at UTBM."
|
||||||
msgstr "La liste de tous les clubs existants à l'UTBM"
|
msgstr "La liste de tous les clubs existants à l'UTBM"
|
||||||
|
|
||||||
|
#: club/templates/club/club_list.jinja
|
||||||
|
msgid "Club list"
|
||||||
|
msgstr "Liste des clubs"
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
#: club/templates/club/club_list.jinja
|
||||||
msgid "Filters"
|
msgid "Filters"
|
||||||
msgstr "Filtres"
|
msgstr "Filtres"
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
#: club/templates/club/club_list.jinja core/templates/core/base/header.jinja
|
||||||
msgid "Name"
|
#: forum/templates/forum/macros.jinja matmat/templates/matmat/search_form.jinja
|
||||||
msgstr "Nom"
|
msgid "Search"
|
||||||
|
msgstr "Recherche"
|
||||||
#: club/templates/club/club_list.jinja
|
|
||||||
msgid "Club state"
|
|
||||||
msgstr "Etat du club"
|
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
|
||||||
msgid "Active"
|
|
||||||
msgstr "Actif"
|
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
|
||||||
msgid "Inactive"
|
|
||||||
msgstr "Inactif"
|
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja
|
|
||||||
msgid "All clubs"
|
|
||||||
msgstr "Tous les clubs"
|
|
||||||
|
|
||||||
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
|
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
|
||||||
msgid "New club"
|
msgid "New club"
|
||||||
@@ -1863,11 +1864,6 @@ msgstr "Connexion"
|
|||||||
msgid "Register"
|
msgid "Register"
|
||||||
msgstr "Inscription"
|
msgstr "Inscription"
|
||||||
|
|
||||||
#: core/templates/core/base/header.jinja forum/templates/forum/macros.jinja
|
|
||||||
#: matmat/templates/matmat/search_form.jinja
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Recherche"
|
|
||||||
|
|
||||||
#: core/templates/core/base/header.jinja
|
#: core/templates/core/base/header.jinja
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "Déconnexion"
|
msgstr "Déconnexion"
|
||||||
@@ -4212,6 +4208,47 @@ msgstr ""
|
|||||||
msgid "this page"
|
msgid "this page"
|
||||||
msgstr "cette page"
|
msgstr "cette page"
|
||||||
|
|
||||||
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
|
msgid "Eurockéennes 2025 partnership"
|
||||||
|
msgstr "Partenariat Eurockéennes 2025"
|
||||||
|
|
||||||
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
|
msgid ""
|
||||||
|
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
|
||||||
|
"according to its own privacy policy. By clicking the accept button you "
|
||||||
|
"consent to their terms of services."
|
||||||
|
msgstr ""
|
||||||
|
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut "
|
||||||
|
"collecter des informations utilisateur conformément à sa propre politique de "
|
||||||
|
"confidentialité. En cliquant sur le bouton d'acceptation vous consentez à "
|
||||||
|
"leurs termes de service."
|
||||||
|
|
||||||
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
|
msgid "Privacy policy"
|
||||||
|
msgstr "Politique de confidentialité"
|
||||||
|
|
||||||
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
|
#: trombi/templates/trombi/comment_moderation.jinja
|
||||||
|
msgid "Accept"
|
||||||
|
msgstr "Accepter"
|
||||||
|
|
||||||
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
|
msgid ""
|
||||||
|
"You must be subscribed to benefit from the partnership with the Eurockéennes."
|
||||||
|
msgstr ""
|
||||||
|
"Vous devez être cotisant pour bénéficier du partenariat avec les "
|
||||||
|
"Eurockéennes."
|
||||||
|
|
||||||
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"This partnership offers a discount of up to 33%% on tickets for Friday, "
|
||||||
|
"Saturday and Sunday, as well as the 3-day package from Friday to Sunday."
|
||||||
|
msgstr ""
|
||||||
|
"Ce partenariat permet de profiter d'une réduction jusqu'à 33%% sur les "
|
||||||
|
"billets du vendredi, du samedi et du dimanche, ainsi qu'au forfait 3 jours, "
|
||||||
|
"du vendredi au dimanche."
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
#: eboutic/templates/eboutic/eboutic_main.jinja
|
||||||
msgid "There are no items available for sale"
|
msgid "There are no items available for sale"
|
||||||
msgstr "Aucun article n'est disponible à la vente"
|
msgstr "Aucun article n'est disponible à la vente"
|
||||||
@@ -4985,6 +5022,14 @@ msgstr "Envoyer les images"
|
|||||||
msgid "You already requested moderation for this picture."
|
msgid "You already requested moderation for this picture."
|
||||||
msgstr "Vous avez déjà déposé une demande de retrait pour cette photo."
|
msgstr "Vous avez déjà déposé une demande de retrait pour cette photo."
|
||||||
|
|
||||||
|
#: sas/forms.py
|
||||||
|
msgid "Left"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: sas/forms.py
|
||||||
|
msgid "Right"
|
||||||
|
msgstr "Droite"
|
||||||
|
|
||||||
#: sas/models.py
|
#: sas/models.py
|
||||||
msgid "picture"
|
msgid "picture"
|
||||||
msgstr "photo"
|
msgstr "photo"
|
||||||
@@ -5106,6 +5151,14 @@ msgstr "Photos de %(user_name)s"
|
|||||||
msgid "Download all my pictures"
|
msgid "Download all my pictures"
|
||||||
msgstr "Télécharger toutes mes photos"
|
msgstr "Télécharger toutes mes photos"
|
||||||
|
|
||||||
|
#: sas/views.py
|
||||||
|
msgid ""
|
||||||
|
"Newly rotated image might not be immediately displayed due to your web "
|
||||||
|
"browser's cache"
|
||||||
|
msgstr ""
|
||||||
|
"Les images nouvellements pivotées peuvent ne pas s'afficher immédiatement "
|
||||||
|
"à cause du cache de votre navigateur internet"
|
||||||
|
|
||||||
#: sith/settings.py
|
#: sith/settings.py
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr "Anglais"
|
msgstr "Anglais"
|
||||||
@@ -5638,10 +5691,6 @@ msgstr "fin"
|
|||||||
msgid "Moderate Trombi comments"
|
msgid "Moderate Trombi comments"
|
||||||
msgstr "Modérer les commentaires du Trombi"
|
msgstr "Modérer les commentaires du Trombi"
|
||||||
|
|
||||||
#: trombi/templates/trombi/comment_moderation.jinja
|
|
||||||
msgid "Accept"
|
|
||||||
msgstr "Accepter"
|
|
||||||
|
|
||||||
#: trombi/templates/trombi/comment_moderation.jinja
|
#: trombi/templates/trombi/comment_moderation.jinja
|
||||||
msgid "Reject"
|
msgid "Reject"
|
||||||
msgstr "Refuser"
|
msgstr "Refuser"
|
||||||
@@ -5883,39 +5932,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
|
|||||||
#, python-format
|
#, python-format
|
||||||
msgid "Maximum characters: %(max_length)s"
|
msgid "Maximum characters: %(max_length)s"
|
||||||
msgstr "Nombre de caractères max: %(max_length)s"
|
msgstr "Nombre de caractères max: %(max_length)s"
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid "Eurockéennes 2025 partnership"
|
|
||||||
msgstr "Partenariat Eurockéennes 2025"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid ""
|
|
||||||
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
|
|
||||||
"according to its own privacy policy. By clicking the accept button you "
|
|
||||||
"consent to their terms of services."
|
|
||||||
msgstr ""
|
|
||||||
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut "
|
|
||||||
"collecter des informations utilisateur conformément à sa propre politique de "
|
|
||||||
"confidentialité. En cliquant sur le bouton d'acceptation vous consentez à "
|
|
||||||
"leurs termes de service."
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid "Privacy policy"
|
|
||||||
msgstr "Politique de confidentialité"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid ""
|
|
||||||
"You must be subscribed to benefit from the partnership with the Eurockéennes."
|
|
||||||
msgstr ""
|
|
||||||
"Vous devez être cotisant pour bénéficier du partenariat avec les "
|
|
||||||
"Eurockéennes."
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"This partnership offers a discount of up to 33%% on tickets for Friday, "
|
|
||||||
"Saturday and Sunday, as well as the 3-day package from Friday to Sunday."
|
|
||||||
msgstr ""
|
|
||||||
"Ce partenariat permet de profiter d'une réduction jusqu'à 33%% sur les "
|
|
||||||
"billets du vendredi, du samedi et du dimanche, ainsi qu'au forfait 3 jours, "
|
|
||||||
"du vendredi au dimanche."
|
|
||||||
|
|||||||
@@ -90,3 +90,10 @@ class PictureModerationRequestForm(forms.ModelForm):
|
|||||||
self.instance.author = self.user
|
self.instance.author = self.user
|
||||||
self.instance.picture = self.picture
|
self.instance.picture = self.picture
|
||||||
return super().save(commit)
|
return super().save(commit)
|
||||||
|
|
||||||
|
|
||||||
|
class PictureRotationForm(forms.Form):
|
||||||
|
picture = forms.ModelChoiceField(Picture.objects.all(), required=True)
|
||||||
|
direction = forms.ChoiceField(
|
||||||
|
choices=[("LEFT", _("Left")), ("RIGHT", _("Right"))], required=True
|
||||||
|
)
|
||||||
|
|||||||
+15
-21
@@ -139,20 +139,20 @@ class Picture(SasFile):
|
|||||||
self.compressed.name = new_extension_name
|
self.compressed.name = new_extension_name
|
||||||
|
|
||||||
def rotate(self, degree):
|
def rotate(self, degree):
|
||||||
for attr in ["file", "compressed", "thumbnail"]:
|
im = Image.open(BytesIO(self.file.read()))
|
||||||
name = self.__getattribute__(attr).name
|
self.file.seek(0)
|
||||||
with open(settings.MEDIA_ROOT / name, "r+b") as file:
|
with open(self.file.path, "r+b") as f:
|
||||||
if file:
|
im = im.rotate(degree, expand=True)
|
||||||
im = Image.open(BytesIO(file.read()))
|
im.save(
|
||||||
file.seek(0)
|
fp=f,
|
||||||
im = im.rotate(degree, expand=True)
|
format=self.mime_type.split("/")[-1].upper(),
|
||||||
im.save(
|
quality=90,
|
||||||
fp=file,
|
optimize=True,
|
||||||
format=self.mime_type.split("/")[-1].upper(),
|
progressive=True,
|
||||||
quality=90,
|
)
|
||||||
optimize=True,
|
self.file.seek(0)
|
||||||
progressive=True,
|
self.generate_thumbnails(overwrite=True)
|
||||||
)
|
self.save()
|
||||||
|
|
||||||
def get_next(self):
|
def get_next(self):
|
||||||
if self.is_moderated:
|
if self.is_moderated:
|
||||||
@@ -205,13 +205,7 @@ class AlbumQuerySet(models.QuerySet):
|
|||||||
|
|
||||||
class SASAlbumManager(models.Manager):
|
class SASAlbumManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return (
|
return super().get_queryset().filter(is_in_sas=True, is_folder=True)
|
||||||
super()
|
|
||||||
.get_queryset()
|
|
||||||
.filter(
|
|
||||||
Q(id=settings.SITH_SAS_ROOT_DIR_ID) | Q(is_in_sas=True, is_folder=True)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Album(SasFile):
|
class Album(SasFile):
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
>form {
|
>form {
|
||||||
input, .ts-wrapper {
|
|
||||||
|
input,
|
||||||
|
.ts-wrapper {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
@@ -201,75 +203,83 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
||||||
|
|
||||||
>div>div {
|
@media (min-width: 700px) {
|
||||||
display: flex;
|
max-width: 350px;
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
>*:first-child {
|
|
||||||
min-width: 150px;
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
min-width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>.tools {
|
.infos>div,
|
||||||
|
.tools>div>div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 50%;
|
gap: .35em;
|
||||||
|
}
|
||||||
|
|
||||||
>div {
|
.tools>div,
|
||||||
display: flex;
|
>.infos>div>div {
|
||||||
flex-direction: row;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
>div {
|
>.tools {
|
||||||
>a.button {
|
flex: 1;
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: $primary-neutral-light-color;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
color: black;
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
&:hover {
|
>div>div {
|
||||||
background-color: #aaa;
|
|
||||||
}
|
>form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
>a.btn,
|
||||||
|
>form>button {
|
||||||
|
background-color: $primary-neutral-light-color;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
color: black;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #aaa;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
>a.text.danger {
|
>a.text.danger {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: darkred;
|
color: darkred;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.buttons {
|
&.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
flex-direction: row;
|
||||||
}
|
gap: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -118,15 +118,35 @@
|
|||||||
<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 class="button" href="?rotate_left"><i class="fa-solid fa-rotate-left"></i></a>
|
<a
|
||||||
<a class="button" href="?rotate_right"><i class="fa-solid fa-rotate-right"></i></a>
|
class="btn btn-no-text"
|
||||||
|
: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>
|
||||||
|
<form method="post" action="{{ url("sas:picture_rotate") }}"
|
||||||
|
x-show="{{ user.has_perm("sas.change_sasfile")|tojson}}"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="picture" :value="currentPicture.id">
|
||||||
|
<input type="hidden" name="direction" value="LEFT">
|
||||||
|
<button><i class="fa-solid fa-rotate-left"></i></button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="{{ url("sas:picture_rotate") }}"
|
||||||
|
x-show="{{ user.has_perm("sas.change_sasfile")|tojson}}"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="picture" :value="currentPicture.id">
|
||||||
|
<input type="hidden" name="direction" value="RIGHT">
|
||||||
|
<button><i class="fa-solid fa-rotate-right"></i></button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+150
-63
@@ -12,16 +12,19 @@
|
|||||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
from io import BytesIO
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils.timezone import localdate
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
|
from PIL import Image
|
||||||
from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects
|
from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects
|
||||||
|
|
||||||
from core.baker_recipes import old_subscriber_user, subscriber_user
|
from core.baker_recipes import old_subscriber_user, subscriber_user
|
||||||
@@ -66,6 +69,25 @@ def test_main_page_no_form_for_regular_users(client: Client):
|
|||||||
assert len(forms) == 0
|
assert len(forms) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_main_page_displayed_albums(client: Client):
|
||||||
|
"""Test that the right data is displayed on the SAS main page"""
|
||||||
|
sas = Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID)
|
||||||
|
Album.objects.exclude(id=sas.id).delete()
|
||||||
|
album_a = baker.make(Album, parent=sas, is_moderated=True)
|
||||||
|
album_b = baker.make(Album, parent=album_a, is_moderated=True)
|
||||||
|
album_c = baker.make(Album, parent=sas, is_moderated=True)
|
||||||
|
baker.make(Album, parent=sas, is_moderated=False)
|
||||||
|
client.force_login(subscriber_user.make())
|
||||||
|
res = client.get(reverse("sas:main"))
|
||||||
|
# album_b is not a direct child of the SAS, so it shouldn't be displayed
|
||||||
|
# in the categories, but it should appear in the latest albums.
|
||||||
|
# album_d isn't moderated, so it shouldn't appear at all for a simple user.
|
||||||
|
# Also, the SAS itself shouldn't be listed in the albums.
|
||||||
|
assert res.context_data["latest"] == [album_c, album_b, album_a]
|
||||||
|
assert res.context_data["categories"] == [album_a, album_c]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_main_page_content_anonymous(client: Client):
|
def test_main_page_content_anonymous(client: Client):
|
||||||
"""Test that public users see only an incentive to login"""
|
"""Test that public users see only an incentive to login"""
|
||||||
@@ -91,6 +113,15 @@ def test_album_access_non_subscriber(client: Client):
|
|||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_accessing_sas_from_album_view_is_404(client: Client):
|
||||||
|
"""Test that trying to see the SAS with a regular album view isn't allowed."""
|
||||||
|
res = client.get(
|
||||||
|
reverse("sas:album", kwargs={"album_id": settings.SITH_SAS_ROOT_DIR_ID})
|
||||||
|
)
|
||||||
|
assert res.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestAlbumUpload:
|
class TestAlbumUpload:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -149,11 +180,7 @@ class TestAlbumEdit:
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"user",
|
"user",
|
||||||
[
|
[None, lambda: baker.make(User), subscriber_user.make],
|
||||||
None,
|
|
||||||
lambda: baker.make(User),
|
|
||||||
subscriber_user.make,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
def test_permission_denied(
|
def test_permission_denied(
|
||||||
self,
|
self,
|
||||||
@@ -164,9 +191,10 @@ class TestAlbumEdit:
|
|||||||
if user:
|
if user:
|
||||||
client.force_login(user())
|
client.force_login(user())
|
||||||
|
|
||||||
response = client.get(reverse("sas:album_edit", kwargs={"album_id": album.pk}))
|
url = reverse("sas:album_edit", kwargs={"album_id": album.pk})
|
||||||
|
response = client.get(url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
response = client.post(reverse("sas:album_edit", kwargs={"album_id": album.pk}))
|
response = client.post(url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
def test_sas_root_read_only(self, client: Client, sas_root: Album):
|
def test_sas_root_read_only(self, client: Client, sas_root: Album):
|
||||||
@@ -174,13 +202,10 @@ class TestAlbumEdit:
|
|||||||
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
|
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
|
||||||
)
|
)
|
||||||
client.force_login(moderator)
|
client.force_login(moderator)
|
||||||
response = client.get(
|
url = reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
|
||||||
reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
|
response = client.get(url)
|
||||||
)
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
response = client.post(
|
response = client.post(url)
|
||||||
reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
|
|
||||||
)
|
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -198,7 +223,7 @@ class TestAlbumEdit:
|
|||||||
data = {
|
data = {
|
||||||
"name": album.name[: Album.NAME_MAX_LENGTH],
|
"name": album.name[: Album.NAME_MAX_LENGTH],
|
||||||
"parent": baker.make(Album, parent=album.parent, is_moderated=True).pk,
|
"parent": baker.make(Album, parent=album.parent, is_moderated=True).pk,
|
||||||
"date": timezone.now().strftime("%Y-%m-%d"),
|
"date": localdate().strftime("%Y-%m-%d"),
|
||||||
"file": "/random/path",
|
"file": "/random/path",
|
||||||
"edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID],
|
"edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID],
|
||||||
"recursive": False,
|
"recursive": False,
|
||||||
@@ -210,7 +235,7 @@ class TestAlbumEdit:
|
|||||||
data = {
|
data = {
|
||||||
"name": album.name[: Album.NAME_MAX_LENGTH],
|
"name": album.name[: Album.NAME_MAX_LENGTH],
|
||||||
"parent": album.pk,
|
"parent": album.pk,
|
||||||
"date": timezone.now().strftime("%Y-%m-%d"),
|
"date": localdate().strftime("%Y-%m-%d"),
|
||||||
}
|
}
|
||||||
assert AlbumEditForm(data=data).is_valid()
|
assert AlbumEditForm(data=data).is_valid()
|
||||||
|
|
||||||
@@ -223,19 +248,12 @@ class TestAlbumEdit:
|
|||||||
payload = {
|
payload = {
|
||||||
"name": album.name[: Album.NAME_MAX_LENGTH],
|
"name": album.name[: Album.NAME_MAX_LENGTH],
|
||||||
"parent": album.pk,
|
"parent": album.pk,
|
||||||
"date": timezone.now().strftime("%Y-%m-%d"),
|
"date": localdate().strftime("%Y-%m-%d"),
|
||||||
}
|
}
|
||||||
response = client.post(
|
response = client.post(
|
||||||
reverse(
|
reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
|
||||||
"sas:album_edit",
|
|
||||||
kwargs={"album_id": album.pk},
|
|
||||||
),
|
|
||||||
payload,
|
|
||||||
)
|
|
||||||
assertInHTML(
|
|
||||||
"<li>Boucle dans l'arborescence des dossiers</li>",
|
|
||||||
response.text,
|
|
||||||
)
|
)
|
||||||
|
assertInHTML("<li>Boucle dans l'arborescence des dossiers</li>", response.text)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -247,56 +265,125 @@ class TestAlbumEdit:
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"parent",
|
||||||
|
[
|
||||||
|
lambda: baker.make(
|
||||||
|
Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True
|
||||||
|
),
|
||||||
|
lambda: Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_update(
|
def test_update(
|
||||||
self,
|
self,
|
||||||
client: Client,
|
client: Client,
|
||||||
album: Album,
|
album: Album,
|
||||||
sas_root: Album,
|
sas_root: Album,
|
||||||
user: Callable[[], User],
|
user: Callable[[], User],
|
||||||
|
parent: Callable[[], Album],
|
||||||
|
):
|
||||||
|
client.force_login(user())
|
||||||
|
expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk})
|
||||||
|
payload = {
|
||||||
|
"name": album.name[: Album.NAME_MAX_LENGTH],
|
||||||
|
"parent": parent().id,
|
||||||
|
"date": localdate().strftime("%Y-%m-%d"),
|
||||||
|
"recursive": False,
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
|
||||||
|
)
|
||||||
|
assertRedirects(response, expected_redirect)
|
||||||
|
album.refresh_from_db()
|
||||||
|
assert album.name == payload["name"]
|
||||||
|
assert album.parent.id == payload["parent"]
|
||||||
|
assert localdate(album.date) == localdate()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
class TestPictureRotation:
|
||||||
|
@pytest.fixture
|
||||||
|
def picture(self) -> Picture:
|
||||||
|
# Creating a fake image from scratch is painful
|
||||||
|
# One of the base image in the test set is good enough
|
||||||
|
return Picture.objects.get(name="sli.jpg")
|
||||||
|
|
||||||
|
def load_image(self, file: ContentFile) -> Image.Image:
|
||||||
|
file.seek(0)
|
||||||
|
im = Image.open(BytesIO(file.read()))
|
||||||
|
file.seek(0)
|
||||||
|
return im
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"user",
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
lambda: baker.make(User),
|
||||||
|
subscriber_user.make,
|
||||||
|
old_subscriber_user.make,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_permission_denied(
|
||||||
|
self,
|
||||||
|
client: Client,
|
||||||
|
picture: Picture,
|
||||||
|
user: Callable[[], User] | None,
|
||||||
|
):
|
||||||
|
if user:
|
||||||
|
client.force_login(user())
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"picture": picture.pk,
|
||||||
|
"direction": "LEFT",
|
||||||
|
}
|
||||||
|
url = reverse("sas:picture_rotate")
|
||||||
|
response = client.post(url, payload)
|
||||||
|
if user:
|
||||||
|
assert response.status_code == 403
|
||||||
|
else:
|
||||||
|
assertRedirects(
|
||||||
|
response,
|
||||||
|
reverse(
|
||||||
|
"core:login",
|
||||||
|
query={
|
||||||
|
"next": url,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"user",
|
||||||
|
[
|
||||||
|
lambda: baker.make(User, is_superuser=True),
|
||||||
|
lambda: baker.make(
|
||||||
|
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_rotation(
|
||||||
|
self,
|
||||||
|
client: Client,
|
||||||
|
picture: Picture,
|
||||||
|
user: Callable[[], User],
|
||||||
):
|
):
|
||||||
client.force_login(user())
|
client.force_login(user())
|
||||||
|
|
||||||
# Prepare a good payload
|
|
||||||
expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk})
|
|
||||||
expected_date = timezone.now()
|
|
||||||
payload = {
|
payload = {
|
||||||
"name": album.name[: Album.NAME_MAX_LENGTH],
|
"picture": picture.pk,
|
||||||
"parent": baker.make(Album, parent=sas_root, is_moderated=True).pk,
|
"direction": "LEFT",
|
||||||
"date": expected_date.strftime("%Y-%m-%d"),
|
|
||||||
"recursive": False,
|
|
||||||
}
|
}
|
||||||
|
response = client.post(reverse("sas:picture_rotate"), payload)
|
||||||
# Test successful update
|
assertRedirects(
|
||||||
response = client.post(
|
response, reverse("sas:picture", kwargs={"picture_id": picture.pk})
|
||||||
reverse(
|
|
||||||
"sas:album_edit",
|
|
||||||
kwargs={"album_id": album.pk},
|
|
||||||
),
|
|
||||||
payload,
|
|
||||||
)
|
|
||||||
assertRedirects(response, expected_redirect)
|
|
||||||
updated_album = Album.objects.get(id=album.pk)
|
|
||||||
assert updated_album.name == payload["name"]
|
|
||||||
assert updated_album.parent.id == payload["parent"]
|
|
||||||
assert timezone.localdate(updated_album.date) == timezone.localdate(
|
|
||||||
expected_date
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test root album can be used as parent
|
payload = {
|
||||||
payload["parent"] = sas_root.pk
|
"picture": picture.pk,
|
||||||
response = client.post(
|
"direction": "RIGHT",
|
||||||
reverse(
|
}
|
||||||
"sas:album_edit",
|
response = client.post(reverse("sas:picture_rotate"), payload)
|
||||||
kwargs={"album_id": album.pk},
|
assertRedirects(
|
||||||
),
|
response, reverse("sas:picture", kwargs={"picture_id": picture.pk})
|
||||||
payload,
|
|
||||||
)
|
|
||||||
assertRedirects(response, expected_redirect)
|
|
||||||
updated_album = Album.objects.get(id=album.pk)
|
|
||||||
assert updated_album.name == payload["name"]
|
|
||||||
assert updated_album.parent.id == payload["parent"]
|
|
||||||
assert timezone.localdate(updated_album.date) == timezone.localdate(
|
|
||||||
expected_date
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from sas.views import (
|
|||||||
ModerationView,
|
ModerationView,
|
||||||
PictureAskRemovalView,
|
PictureAskRemovalView,
|
||||||
PictureEditView,
|
PictureEditView,
|
||||||
|
PictureRotateView,
|
||||||
PictureView,
|
PictureView,
|
||||||
SASMainView,
|
SASMainView,
|
||||||
UserPicturesView,
|
UserPicturesView,
|
||||||
@@ -52,6 +53,7 @@ urlpatterns = [
|
|||||||
send_compressed,
|
send_compressed,
|
||||||
name="download_compressed",
|
name="download_compressed",
|
||||||
),
|
),
|
||||||
|
path("picture/rotate", PictureRotateView.as_view(), name="picture_rotate"),
|
||||||
path("picture/<int:picture_id>/download/thumb/", send_thumb, name="download_thumb"),
|
path("picture/<int:picture_id>/download/thumb/", send_thumb, name="download_thumb"),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
||||||
|
|||||||
+38
-13
@@ -15,12 +15,14 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
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
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import SafeString
|
from django.utils.safestring import SafeString
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView, DetailView, TemplateView
|
from django.views.generic import CreateView, DetailView, TemplateView
|
||||||
from django.views.generic.edit import FormView, UpdateView
|
from django.views.generic.edit import FormView, UpdateView
|
||||||
|
|
||||||
@@ -35,9 +37,10 @@ from sas.forms import (
|
|||||||
AlbumEditForm,
|
AlbumEditForm,
|
||||||
PictureEditForm,
|
PictureEditForm,
|
||||||
PictureModerationRequestForm,
|
PictureModerationRequestForm,
|
||||||
|
PictureRotationForm,
|
||||||
PictureUploadForm,
|
PictureUploadForm,
|
||||||
)
|
)
|
||||||
from sas.models import Album, AlbumQuerySet, PeoplePictureRelation, Picture
|
from sas.models import Album, PeoplePictureRelation, Picture
|
||||||
|
|
||||||
|
|
||||||
class AlbumCreateFragment(FragmentMixin, CreateView):
|
class AlbumCreateFragment(FragmentMixin, CreateView):
|
||||||
@@ -85,7 +88,9 @@ class SASMainView(UseFragmentsMixin, TemplateView):
|
|||||||
kwargs["categories"] = list(
|
kwargs["categories"] = list(
|
||||||
albums_qs.filter(parent_id=settings.SITH_SAS_ROOT_DIR_ID).order_by("id")
|
albums_qs.filter(parent_id=settings.SITH_SAS_ROOT_DIR_ID).order_by("id")
|
||||||
)
|
)
|
||||||
kwargs["latest"] = list(albums_qs.order_by("-id")[:5])
|
kwargs["latest"] = list(
|
||||||
|
albums_qs.exclude(id=settings.SITH_SAS_ROOT_DIR_ID).order_by("-id")[:5]
|
||||||
|
)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
@@ -94,20 +99,39 @@ class PictureView(CanViewMixin, DetailView):
|
|||||||
pk_url_kwarg = "picture_id"
|
pk_url_kwarg = "picture_id"
|
||||||
template_name = "sas/picture.jinja"
|
template_name = "sas/picture.jinja"
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.object = self.get_object()
|
|
||||||
if "rotate_right" in request.GET:
|
|
||||||
self.object.rotate(270)
|
|
||||||
if "rotate_left" in request.GET:
|
|
||||||
self.object.rotate(90)
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
return super().get_context_data(**kwargs) | {
|
return super().get_context_data(**kwargs) | {
|
||||||
"album": Album.objects.get(children=self.object)
|
"album": Album.objects.get(children=self.object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PictureRotateView(PermissionRequiredMixin, FormView):
|
||||||
|
form_class = PictureRotationForm
|
||||||
|
template_name = "core/edit.jinja"
|
||||||
|
permission_required = "sas.moderate_sasfile"
|
||||||
|
|
||||||
|
def form_valid(self, form: PictureRotationForm):
|
||||||
|
angles = {"RIGHT": 270, "LEFT": 90}
|
||||||
|
cleaned = form.clean()
|
||||||
|
cleaned["picture"].rotate(angles[cleaned["direction"]])
|
||||||
|
self._success_url = reverse(
|
||||||
|
"sas:picture",
|
||||||
|
kwargs={
|
||||||
|
"picture_id": cleaned["picture"].pk,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
messages.warning(
|
||||||
|
self.request,
|
||||||
|
_(
|
||||||
|
"Newly rotated image might not be immediately displayed due to your web browser's cache"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self._success_url
|
||||||
|
|
||||||
|
|
||||||
def send_album(request, album_id):
|
def send_album(request, album_id):
|
||||||
return send_file(request, album_id, Album)
|
return send_file(request, album_id, Album)
|
||||||
|
|
||||||
@@ -126,6 +150,9 @@ def send_thumb(request, picture_id):
|
|||||||
|
|
||||||
class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView):
|
class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView):
|
||||||
model = Album
|
model = Album
|
||||||
|
# exclude the SAS from the album accessible with this view
|
||||||
|
# the SAS can be viewed only with SASMainView
|
||||||
|
queryset = Album.objects.exclude(id=settings.SITH_SAS_ROOT_DIR_ID)
|
||||||
pk_url_kwarg = "album_id"
|
pk_url_kwarg = "album_id"
|
||||||
template_name = "sas/album.jinja"
|
template_name = "sas/album.jinja"
|
||||||
|
|
||||||
@@ -262,13 +289,11 @@ class PictureAskRemovalView(CanViewMixin, DetailView, FormView):
|
|||||||
|
|
||||||
class AlbumEditView(CanEditMixin, UpdateView):
|
class AlbumEditView(CanEditMixin, UpdateView):
|
||||||
model = Album
|
model = Album
|
||||||
|
queryset = Album.objects.exclude(id=settings.SITH_SAS_ROOT_DIR_ID)
|
||||||
form_class = AlbumEditForm
|
form_class = AlbumEditForm
|
||||||
template_name = "core/edit.jinja"
|
template_name = "core/edit.jinja"
|
||||||
pk_url_kwarg = "album_id"
|
pk_url_kwarg = "album_id"
|
||||||
|
|
||||||
def get_queryset(self) -> AlbumQuerySet:
|
|
||||||
return super().get_queryset().exclude(id=settings.SITH_SAS_ROOT_DIR_ID)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
if form.cleaned_data["recursive"]:
|
if form.cleaned_data["recursive"]:
|
||||||
|
|||||||
Reference in New Issue
Block a user