mirror of
https://github.com/ae-utbm/sith.git
synced 2025-04-16 02:50:22 +00:00
commit
60fd72917d
@ -740,8 +740,9 @@ Welcome to the wiki page!
|
|||||||
size=file.size,
|
size=file.size,
|
||||||
)
|
)
|
||||||
pict.file.name = p.name
|
pict.file.name = p.name
|
||||||
pict.clean()
|
pict.full_clean()
|
||||||
pict.generate_thumbnails()
|
pict.generate_thumbnails()
|
||||||
|
pict.save()
|
||||||
|
|
||||||
img_skia = Picture.objects.get(name="skia.jpg")
|
img_skia = Picture.objects.get(name="skia.jpg")
|
||||||
img_sli = Picture.objects.get(name="sli.jpg")
|
img_sli = Picture.objects.get(name="sli.jpg")
|
||||||
|
@ -1,16 +1,29 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from annotated_types import MinLen
|
from annotated_types import MinLen
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from haystack.query import SearchQuerySet
|
from haystack.query import SearchQuerySet
|
||||||
from ninja import FilterSchema, ModelSchema, Schema
|
from ninja import FilterSchema, ModelSchema, Schema, UploadedFile
|
||||||
from pydantic import AliasChoices, Field
|
from pydantic import AliasChoices, Field
|
||||||
|
from pydantic_core.core_schema import ValidationInfo
|
||||||
|
|
||||||
from core.models import Group, SithFile, User
|
from core.models import Group, SithFile, User
|
||||||
|
from core.utils import is_image
|
||||||
|
|
||||||
|
|
||||||
|
class UploadedImage(UploadedFile):
|
||||||
|
@classmethod
|
||||||
|
def _validate(cls, v: Any, info: ValidationInfo) -> Any:
|
||||||
|
super()._validate(v, info)
|
||||||
|
if not is_image(v):
|
||||||
|
msg = _("This file is not a valid image")
|
||||||
|
raise ValueError(msg)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class SimpleUserSchema(ModelSchema):
|
class SimpleUserSchema(ModelSchema):
|
||||||
|
@ -17,15 +17,30 @@ from datetime import date, timedelta
|
|||||||
|
|
||||||
# Image utils
|
# Image utils
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
from django.core.files.uploadedfile import UploadedFile
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.timezone import localdate
|
from django.utils.timezone import localdate
|
||||||
from PIL import ExifTags
|
from PIL import ExifTags
|
||||||
from PIL.Image import Image, Resampling
|
from PIL.Image import Image, Resampling
|
||||||
|
|
||||||
|
RED_PIXEL_PNG: Final[bytes] = (
|
||||||
|
b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
|
||||||
|
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53"
|
||||||
|
b"\xde\x00\x00\x00\x0c\x49\x44\x41\x54\x08\xd7\x63\xf8\xcf\xc0\x00"
|
||||||
|
b"\x00\x03\x01\x01\x00\x18\xdd\x8d\xb0\x00\x00\x00\x00\x49\x45\x4e"
|
||||||
|
b"\x44\xae\x42\x60\x82"
|
||||||
|
)
|
||||||
|
"""A single red pixel, in PNG format.
|
||||||
|
|
||||||
|
Can be used in tests and in dev, when there is a need
|
||||||
|
to generate a dummy image that is considered valid nonetheless
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_start_of_semester(today: date | None = None) -> date:
|
def get_start_of_semester(today: date | None = None) -> date:
|
||||||
"""Return the date of the start of the semester of the given date.
|
"""Return the date of the start of the semester of the given date.
|
||||||
@ -97,6 +112,18 @@ def get_semester_code(d: date | None = None) -> str:
|
|||||||
return "P" + str(start.year)[-2:]
|
return "P" + str(start.year)[-2:]
|
||||||
|
|
||||||
|
|
||||||
|
def is_image(file: UploadedFile):
|
||||||
|
try:
|
||||||
|
im = PIL.Image.open(file.file)
|
||||||
|
im.verify()
|
||||||
|
# go back to the start of the file, without closing it.
|
||||||
|
# Otherwise, further checks on django side will fail
|
||||||
|
file.seek(0)
|
||||||
|
except PIL.UnidentifiedImageError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def resize_image(
|
def resize_image(
|
||||||
im: Image, edge: int, img_format: str, *, optimize: bool = True
|
im: Image, edge: int, img_format: str, *, optimize: bool = True
|
||||||
) -> ContentFile:
|
) -> ContentFile:
|
||||||
|
@ -32,17 +32,10 @@ from django.utils import timezone
|
|||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, Membership
|
||||||
from core.models import Group, Page, SithFile, User
|
from core.models import Group, Page, SithFile, User
|
||||||
|
from core.utils import RED_PIXEL_PNG
|
||||||
from sas.models import Album, PeoplePictureRelation, Picture
|
from sas.models import Album, PeoplePictureRelation, Picture
|
||||||
from subscription.models import Subscription
|
from subscription.models import Subscription
|
||||||
|
|
||||||
RED_PIXEL_PNG: Final[bytes] = (
|
|
||||||
b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
|
|
||||||
b"\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53"
|
|
||||||
b"\xde\x00\x00\x00\x0c\x49\x44\x41\x54\x08\xd7\x63\xf8\xcf\xc0\x00"
|
|
||||||
b"\x00\x03\x01\x01\x00\x18\xdd\x8d\xb0\x00\x00\x00\x00\x49\x45\x4e"
|
|
||||||
b"\x44\xae\x42\x60\x82"
|
|
||||||
)
|
|
||||||
|
|
||||||
USER_PACK_SIZE: Final[int] = 1000
|
USER_PACK_SIZE: Final[int] = 1000
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-04-06 15:54+0200\n"
|
"POT-Creation-Date: 2025-04-08 16:20+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"
|
||||||
@ -116,7 +116,7 @@ msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur"
|
|||||||
msgid "You should specify a role"
|
msgid "You should specify a role"
|
||||||
msgstr "Vous devez choisir un rôle"
|
msgstr "Vous devez choisir un rôle"
|
||||||
|
|
||||||
#: club/forms.py sas/views.py
|
#: club/forms.py sas/forms.py
|
||||||
msgid "You do not have the permission to do that"
|
msgid "You do not have the permission to do that"
|
||||||
msgstr "Vous n'avez pas la permission de faire cela"
|
msgstr "Vous n'avez pas la permission de faire cela"
|
||||||
|
|
||||||
@ -1047,7 +1047,7 @@ msgid "Posters - edit"
|
|||||||
msgstr "Affiche - modifier"
|
msgstr "Affiche - modifier"
|
||||||
|
|
||||||
#: com/templates/com/poster_list.jinja com/templates/com/screen_list.jinja
|
#: com/templates/com/poster_list.jinja com/templates/com/screen_list.jinja
|
||||||
#: sas/templates/sas/main.jinja
|
#: sas/templates/sas/fragments/album_create_form.jinja
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "Créer"
|
msgstr "Créer"
|
||||||
|
|
||||||
@ -1644,6 +1644,10 @@ msgstr "étiquette"
|
|||||||
msgid "operation type"
|
msgid "operation type"
|
||||||
msgstr "type d'opération"
|
msgstr "type d'opération"
|
||||||
|
|
||||||
|
#: core/schemas.py
|
||||||
|
msgid "This file is not a valid image"
|
||||||
|
msgstr "Ce fichier n'est pas une image valide"
|
||||||
|
|
||||||
#: core/templates/core/403.jinja
|
#: core/templates/core/403.jinja
|
||||||
msgid "403, Forbidden"
|
msgid "403, Forbidden"
|
||||||
msgstr "403, Non autorisé"
|
msgstr "403, Non autorisé"
|
||||||
@ -2729,7 +2733,7 @@ msgstr "Ajouter un nouveau dossier"
|
|||||||
msgid "Error creating folder %(folder_name)s: %(msg)s"
|
msgid "Error creating folder %(folder_name)s: %(msg)s"
|
||||||
msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s"
|
msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s"
|
||||||
|
|
||||||
#: core/views/files.py core/views/forms.py sas/forms.py
|
#: core/views/files.py core/views/forms.py
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Error uploading file %(file_name)s: %(msg)s"
|
msgid "Error uploading file %(file_name)s: %(msg)s"
|
||||||
msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s"
|
msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s"
|
||||||
@ -4715,11 +4719,6 @@ msgstr "Ajouter un nouvel album"
|
|||||||
msgid "Upload images"
|
msgid "Upload images"
|
||||||
msgstr "Envoyer les images"
|
msgstr "Envoyer les images"
|
||||||
|
|
||||||
#: sas/forms.py
|
|
||||||
#, python-format
|
|
||||||
msgid "Error creating album %(album)s: %(msg)s"
|
|
||||||
msgstr "Erreur de création de l'album %(album)s : %(msg)s"
|
|
||||||
|
|
||||||
#: sas/forms.py
|
#: sas/forms.py
|
||||||
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."
|
||||||
|
46
sas/api.py
46
sas/api.py
@ -1,7 +1,10 @@
|
|||||||
|
from typing import Any, Literal
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from ninja import Query
|
from ninja import Body, File, Query
|
||||||
from ninja_extra import ControllerBase, api_controller, paginate, route
|
from ninja_extra import ControllerBase, api_controller, paginate, route
|
||||||
from ninja_extra.exceptions import NotFound, PermissionDenied
|
from ninja_extra.exceptions import NotFound, PermissionDenied
|
||||||
from ninja_extra.pagination import PageNumberPaginationExtra
|
from ninja_extra.pagination import PageNumberPaginationExtra
|
||||||
@ -9,8 +12,15 @@ from ninja_extra.permissions import IsAuthenticated
|
|||||||
from ninja_extra.schemas import PaginatedResponseSchema
|
from ninja_extra.schemas import PaginatedResponseSchema
|
||||||
from pydantic import NonNegativeInt
|
from pydantic import NonNegativeInt
|
||||||
|
|
||||||
from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
|
from core.auth.api_permissions import (
|
||||||
|
CanAccessLookup,
|
||||||
|
CanEdit,
|
||||||
|
CanView,
|
||||||
|
IsInGroup,
|
||||||
|
IsRoot,
|
||||||
|
)
|
||||||
from core.models import Notification, User
|
from core.models import Notification, User
|
||||||
|
from core.schemas import UploadedImage
|
||||||
from sas.models import Album, PeoplePictureRelation, Picture
|
from sas.models import Album, PeoplePictureRelation, Picture
|
||||||
from sas.schemas import (
|
from sas.schemas import (
|
||||||
AlbumAutocompleteSchema,
|
AlbumAutocompleteSchema,
|
||||||
@ -92,6 +102,38 @@ class PicturesController(ControllerBase):
|
|||||||
.annotate(album=F("parent__name"))
|
.annotate(album=F("parent__name"))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@route.post(
|
||||||
|
"",
|
||||||
|
permissions=[CanEdit],
|
||||||
|
response={
|
||||||
|
200: None,
|
||||||
|
409: dict[Literal["detail"], dict[str, list[str]]],
|
||||||
|
422: dict[Literal["detail"], list[dict[str, Any]]],
|
||||||
|
},
|
||||||
|
url_name="upload_picture",
|
||||||
|
)
|
||||||
|
def upload_picture(self, album_id: Body[int], picture: File[UploadedImage]):
|
||||||
|
album = self.get_object_or_exception(Album, pk=album_id)
|
||||||
|
user = self.context.request.user
|
||||||
|
self_moderate = user.has_perm("sas.moderate_sasfile")
|
||||||
|
new = Picture(
|
||||||
|
parent=album,
|
||||||
|
name=picture.name,
|
||||||
|
file=picture,
|
||||||
|
owner=user,
|
||||||
|
is_moderated=self_moderate,
|
||||||
|
is_folder=False,
|
||||||
|
mime_type=picture.content_type,
|
||||||
|
)
|
||||||
|
if self_moderate:
|
||||||
|
new.moderator = user
|
||||||
|
try:
|
||||||
|
new.generate_thumbnails()
|
||||||
|
new.full_clean()
|
||||||
|
new.save()
|
||||||
|
except ValidationError as e:
|
||||||
|
return self.create_response({"detail": dict(e)}, status_code=409)
|
||||||
|
|
||||||
@route.get(
|
@route.get(
|
||||||
"/{picture_id}/identified",
|
"/{picture_id}/identified",
|
||||||
permissions=[IsAuthenticated, CanView],
|
permissions=[IsAuthenticated, CanView],
|
||||||
|
70
sas/forms.py
70
sas/forms.py
@ -1,6 +1,7 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.models import User
|
from core.models import User
|
||||||
@ -11,55 +12,28 @@ from sas.models import Album, Picture, PictureModerationRequest
|
|||||||
from sas.widgets.ajax_select import AutoCompleteSelectAlbum
|
from sas.widgets.ajax_select import AutoCompleteSelectAlbum
|
||||||
|
|
||||||
|
|
||||||
class SASForm(forms.Form):
|
class AlbumCreateForm(forms.ModelForm):
|
||||||
album_name = forms.CharField(
|
class Meta:
|
||||||
label=_("Add a new album"), max_length=Album.NAME_MAX_LENGTH, required=False
|
model = Album
|
||||||
)
|
fields = ["name", "parent"]
|
||||||
images = MultipleImageField(
|
labels = {"name": _("Add a new album")}
|
||||||
label=_("Upload images"),
|
widgets = {"parent": forms.HiddenInput}
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def process(self, parent, owner, files, *, automodere=False):
|
def __init__(self, *args, owner: User, **kwargs):
|
||||||
try:
|
super().__init__(*args, **kwargs)
|
||||||
if self.cleaned_data["album_name"] != "":
|
self.instance.owner = owner
|
||||||
album = Album(
|
if owner.has_perm("sas.moderate_sasfile"):
|
||||||
parent=parent,
|
self.instance.is_moderated = True
|
||||||
name=self.cleaned_data["album_name"],
|
self.instance.moderator = owner
|
||||||
owner=owner,
|
|
||||||
is_moderated=automodere,
|
def clean(self):
|
||||||
)
|
if not self.instance.owner.can_edit(self.instance.parent):
|
||||||
album.clean()
|
raise ValidationError(_("You do not have the permission to do that"))
|
||||||
album.save()
|
return super().clean()
|
||||||
except Exception as e:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
class PictureUploadForm(forms.Form):
|
||||||
_("Error creating album %(album)s: %(msg)s")
|
images = MultipleImageField(label=_("Upload images"), required=False)
|
||||||
% {"album": self.cleaned_data["album_name"], "msg": repr(e)},
|
|
||||||
)
|
|
||||||
for f in files:
|
|
||||||
new_file = Picture(
|
|
||||||
parent=parent,
|
|
||||||
name=f.name,
|
|
||||||
file=f,
|
|
||||||
owner=owner,
|
|
||||||
mime_type=f.content_type,
|
|
||||||
size=f.size,
|
|
||||||
is_folder=False,
|
|
||||||
is_moderated=automodere,
|
|
||||||
)
|
|
||||||
if automodere:
|
|
||||||
new_file.moderator = owner
|
|
||||||
try:
|
|
||||||
new_file.clean()
|
|
||||||
new_file.generate_thumbnails()
|
|
||||||
new_file.save()
|
|
||||||
except Exception as e:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Error uploading file %(file_name)s: %(msg)s")
|
|
||||||
% {"file_name": f, "msg": repr(e)},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PictureEditForm(forms.ModelForm):
|
class PictureEditForm(forms.ModelForm):
|
||||||
|
@ -134,7 +134,6 @@ class Picture(SasFile):
|
|||||||
self.thumbnail.name = new_extension_name
|
self.thumbnail.name = new_extension_name
|
||||||
self.compressed = compressed
|
self.compressed = compressed
|
||||||
self.compressed.name = new_extension_name
|
self.compressed.name = new_extension_name
|
||||||
self.save()
|
|
||||||
|
|
||||||
def rotate(self, degree):
|
def rotate(self, degree):
|
||||||
for attr in ["file", "compressed", "thumbnail"]:
|
for attr in ["file", "compressed", "thumbnail"]:
|
||||||
@ -235,6 +234,8 @@ class Album(SasFile):
|
|||||||
return Album.objects.filter(parent=self)
|
return Album.objects.filter(parent=self)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
|
if self.id == settings.SITH_SAS_ROOT_DIR_ID:
|
||||||
|
return reverse("sas:main")
|
||||||
return reverse("sas:album", kwargs={"album_id": self.id})
|
return reverse("sas:album", kwargs={"album_id": self.id})
|
||||||
|
|
||||||
def get_download_url(self):
|
def get_download_url(self):
|
||||||
|
@ -5,8 +5,10 @@ import {
|
|||||||
type AlbumSchema,
|
type AlbumSchema,
|
||||||
type PictureSchema,
|
type PictureSchema,
|
||||||
type PicturesFetchPicturesData,
|
type PicturesFetchPicturesData,
|
||||||
|
type PicturesUploadPictureErrors,
|
||||||
albumFetchAlbum,
|
albumFetchAlbum,
|
||||||
picturesFetchPictures,
|
picturesFetchPictures,
|
||||||
|
picturesUploadPicture,
|
||||||
} from "#openapi";
|
} from "#openapi";
|
||||||
|
|
||||||
interface AlbumPicturesConfig {
|
interface AlbumPicturesConfig {
|
||||||
@ -78,4 +80,49 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
Alpine.data("pictureUpload", (albumId: number) => ({
|
||||||
|
errors: [] as string[],
|
||||||
|
pictures: [],
|
||||||
|
sending: false,
|
||||||
|
progress: null as HTMLProgressElement,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.progress = this.$refs.progress;
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendPictures() {
|
||||||
|
const input = this.$refs.pictures as HTMLInputElement;
|
||||||
|
const files = input.files;
|
||||||
|
this.errors = [];
|
||||||
|
this.progress.value = 0;
|
||||||
|
this.progress.max = files.length;
|
||||||
|
this.sending = true;
|
||||||
|
for (const file of files) {
|
||||||
|
await this.sendPicture(file);
|
||||||
|
}
|
||||||
|
this.sending = false;
|
||||||
|
// This should trigger a reload of the pictures of the `picture` Alpine data
|
||||||
|
this.$dispatch("pictures-upload-done");
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendPicture(file: File) {
|
||||||
|
const res = await picturesUploadPicture({
|
||||||
|
// biome-ignore lint/style/useNamingConvention: api is snake_case
|
||||||
|
body: { album_id: albumId, picture: file },
|
||||||
|
});
|
||||||
|
if (!res.response.ok) {
|
||||||
|
let msg = "";
|
||||||
|
if (res.response.status === 422) {
|
||||||
|
msg = (res.error as PicturesUploadPictureErrors[422]).detail
|
||||||
|
.map((err: Record<"ctx", Record<"error", string>>) => err.ctx.error)
|
||||||
|
.join(" ; ");
|
||||||
|
} else {
|
||||||
|
msg = Object.values(res.error.detail).join(" ; ");
|
||||||
|
}
|
||||||
|
this.errors.push(`${file.name} : ${msg}`);
|
||||||
|
}
|
||||||
|
this.progress.value += 1;
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
<div class="text">{% trans %}To be moderated{% endtrans %}</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
{% if edit_mode %}
|
{% if is_sas_admin %}
|
||||||
<input type="checkbox" name="file_list" :value="album.id">
|
<input type="checkbox" name="file_list" :value="album.id">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
@ -86,7 +86,7 @@
|
|||||||
<h4>{% trans %}Pictures{% endtrans %}</h4>
|
<h4>{% trans %}Pictures{% endtrans %}</h4>
|
||||||
<br>
|
<br>
|
||||||
{{ download_button(_("Download album")) }}
|
{{ download_button(_("Download album")) }}
|
||||||
<div class="photos" :aria-busy="loading">
|
<div class="photos" :aria-busy="loading" @pictures-upload-done.window="fetchPictures">
|
||||||
<template x-for="picture in getPage(page)">
|
<template x-for="picture in getPage(page)">
|
||||||
<a :href="picture.sas_url">
|
<a :href="picture.sas_url">
|
||||||
<div class="photo" :class="{not_moderated: !picture.is_moderated}">
|
<div class="photo" :class="{not_moderated: !picture.is_moderated}">
|
||||||
@ -110,13 +110,28 @@
|
|||||||
|
|
||||||
{% if is_sas_admin %}
|
{% if is_sas_admin %}
|
||||||
</form>
|
</form>
|
||||||
<form class="add-files" id="upload_form" action="" method="post" enctype="multipart/form-data">
|
{{ album_create_fragment }}
|
||||||
|
<form
|
||||||
|
class="add-files"
|
||||||
|
id="upload_form"
|
||||||
|
x-data="pictureUpload({{ album.id }})"
|
||||||
|
@submit.prevent="sendPictures()"
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="inputs">
|
<div class="inputs">
|
||||||
{{ form.as_p() }}
|
<p>
|
||||||
|
<label for="{{ upload_form.images.id_for_label }}">{{ upload_form.images.label }} :</label>
|
||||||
|
{{ upload_form.images|add_attr("x-ref=pictures") }}
|
||||||
|
<span class="helptext">{{ upload_form.images.help_text }}</span>
|
||||||
|
</p>
|
||||||
<input type="submit" value="{% trans %}Upload{% endtrans %}" />
|
<input type="submit" value="{% trans %}Upload{% endtrans %}" />
|
||||||
|
<progress x-ref="progress" x-show="sending"></progress>
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="errorlist">
|
||||||
|
<template x-for="error in errors">
|
||||||
|
<li class="error" x-text="error"></li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -126,115 +141,3 @@
|
|||||||
{{ timezone.now() - start }}
|
{{ timezone.now() - start }}
|
||||||
</p>
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block script %}
|
|
||||||
{{ super() }}
|
|
||||||
<script>
|
|
||||||
// Todo: migrate to alpine.js if we have some time
|
|
||||||
$("form#upload_form").submit(function (event) {
|
|
||||||
let formData = new FormData($(this)[0]);
|
|
||||||
|
|
||||||
if(!formData.get('album_name') && !formData.get('images').name)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!formData.get('images').name) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
let errorList;
|
|
||||||
if((errorList = this.querySelector('#upload_form ul.errorlist.nonfield')) === null) {
|
|
||||||
errorList = document.createElement('ul');
|
|
||||||
errorList.classList.add('errorlist', 'nonfield');
|
|
||||||
this.insertBefore(errorList, this.firstElementChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
while(errorList.childElementCount > 0)
|
|
||||||
errorList.removeChild(errorList.firstElementChild);
|
|
||||||
|
|
||||||
let progress;
|
|
||||||
if((progress = this.querySelector('progress')) === null) {
|
|
||||||
progress = document.createElement('progress');
|
|
||||||
progress.value = 0;
|
|
||||||
let p = document.createElement('p');
|
|
||||||
p.appendChild(progress);
|
|
||||||
this.insertBefore(p, this.lastElementChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dataHolder;
|
|
||||||
|
|
||||||
if(formData.get('album_name')) {
|
|
||||||
dataHolder = new FormData();
|
|
||||||
dataHolder.set('csrfmiddlewaretoken', '{{ csrf_token }}');
|
|
||||||
dataHolder.set('album_name', formData.get('album_name'));
|
|
||||||
$.ajax({
|
|
||||||
method: 'POST',
|
|
||||||
url: "{{ url('sas:album_upload', album_id=object.id) }}",
|
|
||||||
data: dataHolder,
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
success: onSuccess
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let images = formData.getAll('images');
|
|
||||||
let imagesCount = images.length;
|
|
||||||
let completeCount = 0;
|
|
||||||
|
|
||||||
let poolSize = 1;
|
|
||||||
let imagePool = [];
|
|
||||||
|
|
||||||
while(images.length > 0 && imagePool.length < poolSize) {
|
|
||||||
let image = images.shift();
|
|
||||||
imagePool.push(image);
|
|
||||||
sendImage(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendImage(image) {
|
|
||||||
dataHolder = new FormData();
|
|
||||||
dataHolder.set('csrfmiddlewaretoken', '{{ csrf_token }}');
|
|
||||||
dataHolder.set('images', image);
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
method: 'POST',
|
|
||||||
url: "{{ url('sas:album_upload', album_id=object.id) }}",
|
|
||||||
data: dataHolder,
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
})
|
|
||||||
.fail(onSuccess.bind(undefined, image))
|
|
||||||
.done(onSuccess.bind(undefined, image))
|
|
||||||
.always(next.bind(undefined, image));
|
|
||||||
}
|
|
||||||
|
|
||||||
function next(image, _, __) {
|
|
||||||
let index = imagePool.indexOf(image);
|
|
||||||
let nextImage = images.shift();
|
|
||||||
|
|
||||||
if(index !== -1)
|
|
||||||
imagePool.splice(index, 1);
|
|
||||||
|
|
||||||
if(nextImage) {
|
|
||||||
imagePool.push(nextImage);
|
|
||||||
sendImage(nextImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSuccess(image, data, _, __) {
|
|
||||||
let errors = [];
|
|
||||||
|
|
||||||
if ($(data.responseText).find('.errorlist.nonfield')[0])
|
|
||||||
errors = Array.from($(data.responseText).find('.errorlist.nonfield')[0].children);
|
|
||||||
|
|
||||||
while(errors.length > 0)
|
|
||||||
errorList.appendChild(errors.shift());
|
|
||||||
|
|
||||||
progress.value = ++completeCount / imagesCount;
|
|
||||||
if(progress.value === 1 && errorList.children.length === 0)
|
|
||||||
document.location.reload()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
18
sas/templates/sas/fragments/album_create_form.jinja
Normal file
18
sas/templates/sas/fragments/album_create_form.jinja
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<form
|
||||||
|
class="add-files"
|
||||||
|
hx-post="{{ url("sas:album_create") }}"
|
||||||
|
hx-disabled-elt="find input[type='submit']"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="inputs">
|
||||||
|
<div>
|
||||||
|
<label for="{{ form.name.id_for_label }}">{{ form.name.label }}</label>
|
||||||
|
{{ form.name }}
|
||||||
|
</div>
|
||||||
|
{{ form.parent }}
|
||||||
|
<input type="submit" value="{% trans %}Create{% endtrans %}" />
|
||||||
|
</div>
|
||||||
|
{{ form.non_field_errors() }}
|
||||||
|
{{ form.name.errors }}
|
||||||
|
</form>
|
@ -61,23 +61,8 @@
|
|||||||
|
|
||||||
{% if is_sas_admin %}
|
{% if is_sas_admin %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
{{ album_create_fragment }}
|
||||||
<form class="add-files" action="" method="post" enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="inputs">
|
|
||||||
<div>
|
|
||||||
<label for="{{ form.album_name.name }}">{{ form.album_name.label }}</label>
|
|
||||||
{{ form.album_name }}
|
|
||||||
</div>
|
|
||||||
<input type="submit" value="{% trans %}Create{% endtrans %}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ form.non_field_errors() }}
|
|
||||||
{{ form.album_name.errors }}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</main>
|
</main>
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
|
import pytest
|
||||||
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.uploadedfile import SimpleUploadedFile
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.test import TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from model_bakery.recipe import Recipe
|
from model_bakery.recipe import Recipe
|
||||||
|
|
||||||
from core.baker_recipes import old_subscriber_user, subscriber_user
|
from core.baker_recipes import old_subscriber_user, subscriber_user
|
||||||
from core.models import Group, SithFile, User
|
from core.models import Group, SithFile, User
|
||||||
|
from core.utils import RED_PIXEL_PNG
|
||||||
from sas.baker_recipes import picture_recipe
|
from sas.baker_recipes import picture_recipe
|
||||||
from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest
|
from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest
|
||||||
|
|
||||||
@ -241,3 +244,45 @@ class TestAlbumSearch(TestSas):
|
|||||||
# - 1 for pagination
|
# - 1 for pagination
|
||||||
# - 1 for the actual results
|
# - 1 for the actual results
|
||||||
self.client.get(reverse("api:search-album"))
|
self.client.get(reverse("api:search-album"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_upload_picture(client: Client):
|
||||||
|
sas = SithFile.objects.get(pk=settings.SITH_SAS_ROOT_DIR_ID)
|
||||||
|
album = baker.make(Album, is_in_sas=True, parent=sas, name="test album")
|
||||||
|
user = baker.make(User, is_superuser=True)
|
||||||
|
client.force_login(user)
|
||||||
|
img = SimpleUploadedFile(
|
||||||
|
name="img.png", content=RED_PIXEL_PNG, content_type="image/png"
|
||||||
|
)
|
||||||
|
res = client.post(
|
||||||
|
reverse("api:upload_picture"), {"album_id": album.id, "picture": img}
|
||||||
|
)
|
||||||
|
assert res.status_code == 200
|
||||||
|
picture = Picture.objects.filter(parent_id=album.id).first()
|
||||||
|
assert picture is not None
|
||||||
|
assert picture.name == "img.png"
|
||||||
|
assert picture.owner == user
|
||||||
|
assert picture.file.name == "SAS/test album/img.png"
|
||||||
|
assert picture.compressed.name == ".compressed/SAS/test album/img.webp"
|
||||||
|
assert picture.thumbnail.name == ".thumbnails/SAS/test album/img.webp"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_upload_invalid_picture(client: Client):
|
||||||
|
sas = SithFile.objects.get(pk=settings.SITH_SAS_ROOT_DIR_ID)
|
||||||
|
album = baker.make(Album, is_in_sas=True, parent=sas, name="test album")
|
||||||
|
user = baker.make(User, is_superuser=True)
|
||||||
|
client.force_login(user)
|
||||||
|
file = SimpleUploadedFile(
|
||||||
|
name="file.txt",
|
||||||
|
content=b"azerty",
|
||||||
|
content_type="image/png", # the server shouldn't blindly trust the content_type
|
||||||
|
)
|
||||||
|
res = client.post(
|
||||||
|
reverse("api:upload_picture"), {"album_id": album.id, "picture": file}
|
||||||
|
)
|
||||||
|
assert res.status_code == 422
|
||||||
|
assert res.json()["detail"][0]["ctx"]["error"] == (
|
||||||
|
"Ce fichier n'est pas une image valide"
|
||||||
|
)
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from sas.views import (
|
from sas.views import (
|
||||||
|
AlbumCreateFragment,
|
||||||
AlbumEditView,
|
AlbumEditView,
|
||||||
AlbumUploadView,
|
|
||||||
AlbumView,
|
AlbumView,
|
||||||
ModerationView,
|
ModerationView,
|
||||||
PictureAskRemovalView,
|
PictureAskRemovalView,
|
||||||
@ -35,9 +35,6 @@ urlpatterns = [
|
|||||||
path("", SASMainView.as_view(), name="main"),
|
path("", SASMainView.as_view(), name="main"),
|
||||||
path("moderation/", ModerationView.as_view(), name="moderation"),
|
path("moderation/", ModerationView.as_view(), name="moderation"),
|
||||||
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
|
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
|
||||||
path(
|
|
||||||
"album/<int:album_id>/upload/", AlbumUploadView.as_view(), name="album_upload"
|
|
||||||
),
|
|
||||||
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
|
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
|
||||||
path("album/<int:album_id>/preview/", send_album, name="album_preview"),
|
path("album/<int:album_id>/preview/", send_album, name="album_preview"),
|
||||||
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
|
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
|
||||||
@ -59,4 +56,5 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
||||||
),
|
),
|
||||||
|
path("fragment/album-create", AlbumCreateFragment.as_view(), name="album_create"),
|
||||||
]
|
]
|
||||||
|
146
sas/views.py
146
sas/views.py
@ -16,48 +16,63 @@ 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.http import Http404, HttpResponse, 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, reverse_lazy
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.safestring import SafeString
|
||||||
from django.views.generic import DetailView, TemplateView
|
from django.views.generic import CreateView, DetailView, TemplateView
|
||||||
from django.views.generic.edit import FormMixin, FormView, UpdateView
|
from django.views.generic.edit import FormView, UpdateView
|
||||||
|
|
||||||
from core.auth.mixins import CanEditMixin, CanViewMixin
|
from core.auth.mixins import CanEditMixin, CanViewMixin
|
||||||
from core.models import SithFile, User
|
from core.models import SithFile, User
|
||||||
|
from core.views import UseFragmentsMixin
|
||||||
from core.views.files import FileView, send_file
|
from core.views.files import FileView, send_file
|
||||||
|
from core.views.mixins import FragmentMixin, FragmentRenderer
|
||||||
from core.views.user import UserTabsMixin
|
from core.views.user import UserTabsMixin
|
||||||
from sas.forms import (
|
from sas.forms import (
|
||||||
|
AlbumCreateForm,
|
||||||
AlbumEditForm,
|
AlbumEditForm,
|
||||||
PictureEditForm,
|
PictureEditForm,
|
||||||
PictureModerationRequestForm,
|
PictureModerationRequestForm,
|
||||||
SASForm,
|
PictureUploadForm,
|
||||||
)
|
)
|
||||||
from sas.models import Album, Picture
|
from sas.models import Album, Picture
|
||||||
|
|
||||||
|
|
||||||
class SASMainView(FormView):
|
class AlbumCreateFragment(FragmentMixin, CreateView):
|
||||||
form_class = SASForm
|
model = Album
|
||||||
template_name = "sas/main.jinja"
|
form_class = AlbumCreateForm
|
||||||
success_url = reverse_lazy("sas:main")
|
template_name = "sas/fragments/album_create_form.jinja"
|
||||||
|
reload_on_redirect = True
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def get_form_kwargs(self):
|
||||||
self.form = self.get_form()
|
return super().get_form_kwargs() | {"owner": self.request.user}
|
||||||
parent = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first()
|
|
||||||
files = request.FILES.getlist("images")
|
def render_fragment(
|
||||||
root = User.objects.filter(username="root").first()
|
self, request, owner: User | None = None, **kwargs
|
||||||
if request.user.is_authenticated and request.user.is_in_group(
|
) -> SafeString:
|
||||||
pk=settings.SITH_GROUP_SAS_ADMIN_ID
|
self.object = None
|
||||||
):
|
self.owner = owner or self.request.user
|
||||||
if self.form.is_valid():
|
return super().render_fragment(request, **kwargs)
|
||||||
self.form.process(
|
|
||||||
parent=parent, owner=root, files=files, automodere=True
|
def get_success_url(self):
|
||||||
)
|
parent = self.object.parent
|
||||||
if self.form.is_valid():
|
parent.__class__ = Album
|
||||||
return super().form_valid(self.form)
|
return parent.get_absolute_url()
|
||||||
else:
|
|
||||||
self.form.add_error(None, _("You do not have the permission to do that"))
|
|
||||||
return self.form_invalid(self.form)
|
class SASMainView(UseFragmentsMixin, TemplateView):
|
||||||
|
template_name = "sas/main.jinja"
|
||||||
|
|
||||||
|
def get_fragments(self) -> dict[str, FragmentRenderer]:
|
||||||
|
form_init = {"parent": SithFile.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID)}
|
||||||
|
return {
|
||||||
|
"album_create_fragment": AlbumCreateFragment.as_fragment(initial=form_init)
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_fragment_data(self) -> dict[str, dict[str, Any]]:
|
||||||
|
root_user = User.objects.get(pk=settings.SITH_ROOT_USER_ID)
|
||||||
|
return {"album_create_fragment": {"owner": root_user}}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
@ -104,88 +119,45 @@ def send_thumb(request, picture_id):
|
|||||||
return send_file(request, picture_id, Picture, "thumbnail")
|
return send_file(request, picture_id, Picture, "thumbnail")
|
||||||
|
|
||||||
|
|
||||||
class AlbumUploadView(CanViewMixin, DetailView, FormMixin):
|
class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView):
|
||||||
model = Album
|
model = Album
|
||||||
form_class = SASForm
|
|
||||||
pk_url_kwarg = "album_id"
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
self.object = self.get_object()
|
|
||||||
if not self.object.file:
|
|
||||||
self.object.generate_thumbnail()
|
|
||||||
self.form = self.get_form()
|
|
||||||
parent = SithFile.objects.filter(id=self.object.id).first()
|
|
||||||
files = request.FILES.getlist("images")
|
|
||||||
if request.user.is_subscribed and self.form.is_valid():
|
|
||||||
self.form.process(
|
|
||||||
parent=parent,
|
|
||||||
owner=request.user,
|
|
||||||
files=files,
|
|
||||||
automodere=(
|
|
||||||
request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
|
|
||||||
or request.user.is_root
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if self.form.is_valid():
|
|
||||||
return HttpResponse(str(self.form.errors), status=200)
|
|
||||||
return HttpResponse(str(self.form.errors), status=500)
|
|
||||||
|
|
||||||
|
|
||||||
class AlbumView(CanViewMixin, DetailView, FormMixin):
|
|
||||||
model = Album
|
|
||||||
form_class = SASForm
|
|
||||||
pk_url_kwarg = "album_id"
|
pk_url_kwarg = "album_id"
|
||||||
template_name = "sas/album.jinja"
|
template_name = "sas/album.jinja"
|
||||||
|
|
||||||
|
def get_fragments(self) -> dict[str, FragmentRenderer]:
|
||||||
|
return {
|
||||||
|
"album_create_fragment": AlbumCreateFragment.as_fragment(
|
||||||
|
initial={"parent": self.object}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
self.asked_page = int(request.GET.get("page", 1))
|
self.asked_page = int(request.GET.get("page", 1))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise Http404 from e
|
raise Http404 from e
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self.form = self.get_form()
|
|
||||||
if "clipboard" not in request.session:
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
return super().get(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if not self.object.file:
|
if not self.object.file:
|
||||||
self.object.generate_thumbnail()
|
self.object.generate_thumbnail()
|
||||||
self.form = self.get_form()
|
|
||||||
if "clipboard" not in request.session:
|
|
||||||
request.session["clipboard"] = []
|
|
||||||
if request.user.can_edit(self.object): # Handle the copy-paste functions
|
if request.user.can_edit(self.object): # Handle the copy-paste functions
|
||||||
FileView.handle_clipboard(request, self.object)
|
FileView.handle_clipboard(request, self.object)
|
||||||
parent = SithFile.objects.filter(id=self.object.id).first()
|
return HttpResponseRedirect(self.request.path)
|
||||||
files = request.FILES.getlist("images")
|
|
||||||
if request.user.is_authenticated and request.user.is_subscribed:
|
|
||||||
if self.form.is_valid():
|
|
||||||
self.form.process(
|
|
||||||
parent=parent,
|
|
||||||
owner=request.user,
|
|
||||||
files=files,
|
|
||||||
automodere=request.user.is_in_group(
|
|
||||||
pk=settings.SITH_GROUP_SAS_ADMIN_ID
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if self.form.is_valid():
|
|
||||||
return super().form_valid(self.form)
|
|
||||||
else:
|
|
||||||
self.form.add_error(None, _("You do not have the permission to do that"))
|
|
||||||
return self.form_invalid(self.form)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_fragment_data(self) -> dict[str, dict[str, Any]]:
|
||||||
return reverse("sas:album", kwargs={"album_id": self.object.id})
|
return {"album_create_fragment": {"owner": self.request.user}}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
kwargs["form"] = self.form
|
if ids := self.request.session.get("clipboard", None):
|
||||||
kwargs["clipboard"] = SithFile.objects.filter(
|
kwargs["clipboard"] = SithFile.objects.filter(id__in=ids)
|
||||||
id__in=self.request.session["clipboard"]
|
kwargs["upload_form"] = PictureUploadForm()
|
||||||
)
|
# if True, the albums will be fetched with a request to the API
|
||||||
|
# if False, the section won't be displayed at all
|
||||||
kwargs["show_albums"] = (
|
kwargs["show_albums"] = (
|
||||||
Album.objects.viewable_by(self.request.user)
|
Album.objects.viewable_by(self.request.user)
|
||||||
.filter(parent_id=self.object.id)
|
.filter(parent_id=self.object.id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user