diff --git a/photologue/forms.py b/photologue/forms.py index a2d114b..10a757d 100644 --- a/photologue/forms.py +++ b/photologue/forms.py @@ -7,12 +7,15 @@ import datetime from crispy_forms.helper import FormHelper from crispy_forms.layout import Div, Layout, Submit from django import forms +from django.contrib.auth import get_user_model from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ -from django_select2.forms import ModelSelect2MultipleWidget +from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget from .models import Gallery, Tag +User = get_user_model() + class MultipleFileInput(forms.ClearableFileInput): allow_multiple_selected = True @@ -87,11 +90,27 @@ class UploadForm(forms.Form): ) def __init__(self, *args, **kwargs): + is_staff = kwargs.pop("is_staff", False) super().__init__(*args, **kwargs) + if is_staff: + self.fields["owner"] = forms.ModelChoiceField( + User.objects.all(), + label=_("Upload as"), + required=False, + empty_label=_("-- Myself --"), + widget=ModelSelect2Widget( + model=User, + search_fields=["username__icontains", "first_name__icontains", "last_name__icontains"], + attrs={ + "data-minimum-input-length": 0, + "data-placeholder": "-- Myself --", + }, + ), + ) self.helper = FormHelper() self.helper.include_media = False self.helper.use_custom_control = False - self.helper.layout = Layout( + layout_fields = [ "file_field", "gallery", "new_gallery_title", @@ -102,8 +121,11 @@ class UploadForm(forms.Form): ), "new_gallery_description", "new_gallery_tags", - Submit("submit", _("Upload"), css_class="btn btn-success mt-2"), - ) + ] + if is_staff: + layout_fields.append("owner") + layout_fields.append(Submit("submit", _("Upload"), css_class="btn btn-success mt-2")) + self.helper.layout = Layout(*layout_fields) def clean(self): cleaned_data = super().clean() @@ -119,6 +141,9 @@ class UploadForm(forms.Form): return cleaned_data + def get_owner(self, fallback): + return self.cleaned_data.get("owner") or fallback + def get_or_create_gallery(self): """ Get or create gallery diff --git a/photologue/static/upload.js b/photologue/static/upload.js index c879213..584ec42 100644 --- a/photologue/static/upload.js +++ b/photologue/static/upload.js @@ -95,11 +95,12 @@ pausebtn.addEventListener('click', () => { pausebtn.className = pause ? 'btn btn-success' : 'btn btn-warning'; }); -async function uploadFile(file, galleryID, csrfvalue) { +async function uploadFile(file, galleryID, csrfvalue, ownerID) { const sendform = new FormData(); sendform.append('csrfmiddlewaretoken', csrfvalue); sendform.append('file_field', file); sendform.append('gallery', galleryID); + if (ownerID) sendform.append('owner', ownerID); const start = performance.now(); const res = await fetch('/upload/', { method: 'POST', @@ -146,6 +147,8 @@ ctnbtn.addEventListener('click', async () => { } const galleryID = returned.galleryID; + const ownerSelect = document.getElementById('id_owner'); + const ownerID = ownerSelect ? ownerSelect.value : null; uploadInput.disabled = true; gallerySelect.disabled = true; @@ -194,7 +197,7 @@ ctnbtn.addEventListener('click', async () => { while (!pause && active < concurrency && queue.length > 0) { const file = queue.shift(); active++; - uploadFile(file, galleryID, csrfvalue) + uploadFile(file, galleryID, csrfvalue, ownerID) .catch(e => console.error(e)) .finally(() => { completed++; tuneConcurrency(); active--; updateProgress(); next(); }); } diff --git a/photologue/templates/photologue/upload.html b/photologue/templates/photologue/upload.html index 7d4e0d0..249ea89 100644 --- a/photologue/templates/photologue/upload.html +++ b/photologue/templates/photologue/upload.html @@ -22,9 +22,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Drag and drop photos here" %} {% crispy form %} -

- {% trans "Owner will be" %} {{ request.user.get_full_name }} ({{ request.user.username}}). -

diff --git a/photologue/views.py b/photologue/views.py index 722b345..93116d2 100644 --- a/photologue/views.py +++ b/photologue/views.py @@ -299,15 +299,20 @@ class GalleryUpload(PermissionRequiredMixin, FormView): success_url = reverse_lazy("photologue:pl-gallery-upload") permission_required = "photologue.add_gallery" - def _upload_media(self, model, file_field, file_obj, gallery, gallery_dir, post_save=None): + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["is_staff"] = self.request.user.is_staff + return kwargs + + def _upload_media(self, model, file_field, file_obj, gallery, gallery_dir, owner, post_save=None): """ Create a media object, save it to the DB, schedule file save on commit. Returns True if uploaded, False if already exists. """ title = Path(file_obj.name).stem - if model.objects.filter(title=title, owner=self.request.user, galleries=gallery).exists(): + if model.objects.filter(title=title, owner=owner, galleries=gallery).exists(): return False - obj = model(title=title, slug=unique_slug(model, title), owner=self.request.user) + obj = model(title=title, slug=unique_slug(model, title), owner=owner) file_path = str(gallery_dir / file_obj.name) with transaction.atomic(): obj.save() @@ -345,6 +350,10 @@ class GalleryUpload(PermissionRequiredMixin, FormView): gallery_year = Path(str(gallery.date_start.year)) gallery_dir = gallery_year / gallery.slug + owner = form.get_owner(self.request.user) + if owner != self.request.user and not self.request.user.is_staff: + from django.core.exceptions import PermissionDenied + raise PermissionDenied # Upload pictures and videos uploaded_photo_name = [] @@ -352,9 +361,9 @@ class GalleryUpload(PermissionRequiredMixin, FormView): files = form.cleaned_data["file_field"] for photo_file in files: if is_photo(photo_file): - uploaded = self._upload_media(Photo, "image", photo_file, gallery, gallery_dir) + uploaded = self._upload_media(Photo, "image", photo_file, gallery, gallery_dir, owner) elif is_video(photo_file): - uploaded = self._upload_media(Video, "file", photo_file, gallery, gallery_dir, post_save=generate_video_thumbnail) + uploaded = self._upload_media(Video, "file", photo_file, gallery, gallery_dir, owner, post_save=generate_video_thumbnail) else: messages.error(self.request, f"{photo_file.name} is not a recognized image or video") jsondata["code"] = 400