Merge branch 'photograph_upload' into 'master'

Photograph interface

See merge request bde/photo21!11
This commit is contained in:
erdnaxe 2021-10-17 20:33:47 +02:00
commit 29e9dba141
17 changed files with 351 additions and 55 deletions

View file

@ -17,7 +17,7 @@ class CustomSignupForm(SignupForm):
""" """
Check that the email address ends with a trusted domain. Check that the email address ends with a trusted domain.
""" """
email = self.cleaned_data.get("email") email = super().clean_email()
if not email.endswith("@crans.org") and not email.endswith("@ens-paris-saclay.fr"): if not email.endswith("@crans.org") and not email.endswith("@ens-paris-saclay.fr"):
raise forms.ValidationError( raise forms.ValidationError(
_("Must end with `@crans.org` or `@ens-paris-saclay.fr`.") _("Must end with `@crans.org` or `@ens-paris-saclay.fr`.")

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-13 13:52+0000\n" "POT-Creation-Date: 2021-10-15 12:15+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -42,19 +42,19 @@ msgstr ""
msgid "hash" msgid "hash"
msgstr "" msgstr ""
#: photo21/settings.py:153 #: photo21/settings.py:163
msgid "German" msgid "German"
msgstr "" msgstr ""
#: photo21/settings.py:154 #: photo21/settings.py:164
msgid "English" msgid "English"
msgstr "" msgstr ""
#: photo21/settings.py:155 #: photo21/settings.py:165
msgid "Spanish" msgid "Spanish"
msgstr "" msgstr ""
#: photo21/settings.py:156 #: photo21/settings.py:166
msgid "French" msgid "French"
msgstr "" msgstr ""
@ -108,7 +108,7 @@ msgstr ""
msgid "E-mail Addresses" msgid "E-mail Addresses"
msgstr "" msgstr ""
#: photo21/templates/account/email.html:9 photo21/templates/base.html:51 #: photo21/templates/account/email.html:9 photo21/templates/base.html:58
#: photo21/templates/socialaccount/connections.html:9 #: photo21/templates/socialaccount/connections.html:9
msgid "Account" msgid "Account"
msgstr "Compte" msgstr "Compte"
@ -225,27 +225,34 @@ msgstr ""
msgid "The ENS Paris-Saclay pictures server." msgid "The ENS Paris-Saclay pictures server."
msgstr "" msgstr ""
#: photo21/templates/base.html:35 #: photo21/templates/base.html:36
msgid "Galleries" msgid "Galleries"
msgstr "Galeries" msgstr "Galeries"
#: photo21/templates/base.html:39 #: photo21/templates/base.html:41
#: photologue_custom/templates/photologue/upload.html:6
#: photologue_custom/templates/photologue/upload.html:54
#: photologue_custom/templates/photologue/upload.html:65
msgid "Upload"
msgstr "Téléversement"
#: photo21/templates/base.html:46
msgid "Manage" msgid "Manage"
msgstr "Gestion" msgstr "Gestion"
#: photo21/templates/base.html:60 #: photo21/templates/base.html:67
msgid "Log out" msgid "Log out"
msgstr "" msgstr ""
#: photo21/templates/base.html:70 #: photo21/templates/base.html:77
msgid "Log in" msgid "Log in"
msgstr "" msgstr ""
#: photo21/templates/base.html:79 #: photo21/templates/base.html:86
msgid "Sign up" msgid "Sign up"
msgstr "Inscription" msgstr "Inscription"
#: photo21/templates/index.html:50 #: photo21/templates/index.html:49
msgid "Connected as" msgid "Connected as"
msgstr "Connecté en tant que" msgstr "Connecté en tant que"
@ -268,6 +275,40 @@ msgstr ""
msgid "Add a 3rd Party Account" msgid "Add a 3rd Party Account"
msgstr "" msgstr ""
#: photologue_custom/admin.py:45 photologue_custom/models.py:51
msgid "owner"
msgstr "propriétaire"
#: photologue_custom/forms.py:22
msgid "Gallery"
msgstr "Galerie"
#: photologue_custom/forms.py:24
msgid ""
"Select a gallery to add these images to. Leave this empty to create a new "
"gallery from the supplied title."
msgstr ""
#: photologue_custom/forms.py:28
msgid "New gallery title"
msgstr "Titre de la nouvelle galerie"
#: photologue_custom/forms.py:33
msgid "New gallery event start date"
msgstr "Date de début de l'évènement de la nouvelle galerie"
#: photologue_custom/forms.py:38
msgid "New gallery event end date"
msgstr "Date de fin de l'évènement de la nouvelle galerie"
#: photologue_custom/forms.py:46
msgid "A gallery with that title already exists."
msgstr ""
#: photologue_custom/forms.py:55
msgid "Select an existing gallery, or enter a title for a new gallery."
msgstr ""
#: photologue_custom/models.py:23 #: photologue_custom/models.py:23
msgid "start date" msgid "start date"
msgstr "date de début" msgstr "date de début"
@ -276,53 +317,57 @@ msgstr "date de début"
msgid "end date" msgid "end date"
msgstr "date de fin" msgstr "date de fin"
#: photologue_custom/models.py:51 #: photologue_custom/templates/photologue/gallery_archive.html:7
msgid "owner" #: photologue_custom/templates/photologue/gallery_archive.html:12
msgstr "propriétaire"
#: photologue_custom/templates/photologue/gallery_archive.html:4
#: photologue_custom/templates/photologue/gallery_archive.html:9
msgid "Latest photo galleries" msgid "Latest photo galleries"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/gallery_archive.html:15 #: photologue_custom/templates/photologue/gallery_archive.html:18
msgid "Filter by year" msgid "Filter by year"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/gallery_archive.html:32 #: photologue_custom/templates/photologue/gallery_archive.html:35
msgid "No galleries were found" msgid "No galleries were found"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/gallery_archive_year.html:4 #: photologue_custom/templates/photologue/gallery_archive_year.html:7
#: photologue_custom/templates/photologue/gallery_archive_year.html:9 #: photologue_custom/templates/photologue/gallery_archive_year.html:12
#, python-format #, python-format
msgid "Galleries for %(show_year)s" msgid "Galleries for %(show_year)s"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/gallery_archive_year.html:14 #: photologue_custom/templates/photologue/gallery_archive_year.html:17
msgid "View all galleries" msgid "View all galleries"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/gallery_archive_year.html:26 #: photologue_custom/templates/photologue/gallery_archive_year.html:29
msgid "No galleries were found." msgid "No galleries were found."
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/gallery_detail.html:35 #: photologue_custom/templates/photologue/gallery_detail.html:38
msgid "to" msgid "to"
msgstr "au" msgstr "au"
#: photologue_custom/templates/photologue/gallery_detail.html:49 #: photologue_custom/templates/photologue/gallery_detail.html:54
msgid "All pictures" msgid "All pictures"
msgstr "Toutes les photos" msgstr "Toutes les photos"
#: photologue_custom/templates/photologue/gallery_detail.html:63 #: photologue_custom/templates/photologue/gallery_detail.html:75
msgid "Download all gallery" msgid "Download all gallery"
msgstr "Télécharger toute la galerie" msgstr "Télécharger toute la galerie"
#: photologue_custom/templates/photologue/photo_detail.html:10 #: photologue_custom/templates/photologue/photo_detail.html:13
msgid "Published" msgid "Published"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/photo_detail.html:22 #: photologue_custom/templates/photologue/photo_detail.html:25
msgid "This photo is found in the following galleries" msgid "This photo is found in the following galleries"
msgstr "" msgstr ""
#: photologue_custom/templates/photologue/upload.html:59
msgid "Drag and drop photos here"
msgstr "Glissez et déposez les photos ici"
#: photologue_custom/templates/photologue/upload.html:63
msgid "Owner will be"
msgstr "Le propriétaire sera"

View file

@ -35,6 +35,16 @@ ALLOWED_HOSTS = [
"photos-dev.crans.org", "photos-dev.crans.org",
] ]
# Admins receive server errors, this is useful to be notified of potential bugs
ADMINS = [
('erdnaxe', 'a+photo21@crans.org'),
]
# Managers receive notifications about new photos upload
MANAGERS = [
('moderation', 'photos@crans.org'),
]
# Application definition # Application definition
@ -193,6 +203,7 @@ EMAIL_HOST_PASSWORD = os.getenv('EMAIL_PASSWORD', None)
# Mail will be sent from this address # Mail will be sent from this address
SERVER_EMAIL = "photos@crans.org" SERVER_EMAIL = "photos@crans.org"
DEFAULT_FROM_EMAIL = f"Serveur photos <{SERVER_EMAIL}>" DEFAULT_FROM_EMAIL = f"Serveur photos <{SERVER_EMAIL}>"
EMAIL_SUBJECT_PREFIX = '[Serveur photos] '
# After login redirect user to transfer page # After login redirect user to transfer page
LOGIN_REDIRECT_URL = '/' LOGIN_REDIRECT_URL = '/'

View file

@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<link rel="stylesheet" href="{% static "bootstrap5/css/bootstrap.min.css" %}"> <link rel="stylesheet" href="{% static "bootstrap5/css/bootstrap.min.css" %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static "favicon-16x16.png" %}"> <link rel="icon" type="image/png" sizes="16x16" href="{% static "favicon-16x16.png" %}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static "favicon-32x32.png" %}"> <link rel="icon" type="image/png" sizes="32x32" href="{% static "favicon-32x32.png" %}">
<meta name="theme-color" content="#212529">
{% block extracss %}{% endblock %} {% block extracss %}{% endblock %}
</head> </head>
<body class="bg-light"> <body class="bg-light">
@ -34,10 +35,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% url 'photologue:pl-gallery-archive' as url %} {% url 'photologue:pl-gallery-archive' as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">{% trans 'Galleries' %}</a> <a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">{% trans 'Galleries' %}</a>
</li> </li>
{% if perms.photologue.add_gallery %}
<li class="nav-item">
{% url 'gallery-upload' as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">{% trans 'Upload' %}</a>
</li>
{% endif %}
{% if request.user.is_staff %} {% if request.user.is_staff %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'admin:index' %}">{% trans 'Manage' %}</a> <a class="nav-link" href="{% url 'admin:index' %}">{% trans 'Manage' %}</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">

View file

@ -46,9 +46,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<hr/> <hr/>
{% if request.user.is_authenticated %} {% trans "Connected as" %} <code>{{ request.user.username }}</code>.
{% trans "Connected as" %} <code>{{ request.user.username }}</code>.
{% endif %}
<form action="{% url 'set_language' %}" method="post" style="max-width: 10em;"> <form action="{% url 'set_language' %}" method="post" style="max-width: 10em;">
{% csrf_token %} {% csrf_token %}
Changer la langue : Changer la langue :

View file

@ -1,4 +1,5 @@
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from photologue.admin import GalleryAdmin as GalleryAdminDefault from photologue.admin import GalleryAdmin as GalleryAdminDefault
from photologue.admin import PhotoAdmin as PhotoAdminDefault from photologue.admin import PhotoAdmin as PhotoAdminDefault
from photologue.models import Gallery, Photo, PhotoEffect, PhotoSize, Watermark from photologue.models import Gallery, Photo, PhotoEffect, PhotoSize, Watermark
@ -32,6 +33,16 @@ class PhotoAdmin(PhotoAdminDefault):
model. model.
""" """
inlines = [PhotoExtendedInline, ] inlines = [PhotoExtendedInline, ]
list_display = ('title', 'date_taken', 'date_added',
'is_public', 'view_count', 'admin_thumbnail', 'get_owner')
list_filter = ['date_added', 'is_public', 'extended__owner']
def get_owner(self, obj):
if not hasattr(obj, 'extended'):
return "No owner"
return obj.extended.owner.username
get_owner.admin_order_field = 'owner'
get_owner.short_description = _('owner')
admin.site.unregister(Gallery) admin.site.unregister(Gallery)

View file

@ -0,0 +1,73 @@
import datetime
from django import forms
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from photologue.models import Gallery
from .models import GalleryExtended
class UploadForm(forms.Form):
file_field = forms.FileField(
label="",
widget=forms.FileInput(attrs={
'accept': 'image/*',
'multiple': True,
'class': 'mb-3',
}),
)
gallery = forms.ModelChoiceField(
Gallery.objects.all(),
label=_('Gallery'),
required=False,
help_text=_('Select a gallery to add these images to. Leave this empty to '
'create a new gallery from the supplied title.')
)
new_gallery_title = forms.CharField(
label=_('New gallery title'),
max_length=250,
required=False,
)
date_start = forms.DateField(
label=_('New gallery event start date'),
initial=datetime.date.today,
required=False,
)
date_end = forms.DateField(
label=_('New gallery event end date'),
initial=datetime.date.today,
required=False,
)
def clean_new_gallery_title(self):
title = self.cleaned_data['new_gallery_title']
if title and Gallery.objects.filter(title=title).exists():
raise forms.ValidationError(_('A gallery with that title already exists.'))
return title
def clean(self):
cleaned_data = super().clean()
# Check that either an existing gallery is chosen, or new_gallery_title is filled
if not (bool(cleaned_data['gallery']) ^ bool(cleaned_data.get('new_gallery_title', None))):
raise forms.ValidationError(
_('Select an existing gallery, or enter a title for a new gallery.'))
return cleaned_data
def get_or_create_gallery(self):
"""
Get or create gallery
"""
gallery = self.cleaned_data['gallery']
if not gallery:
# Create new gallery
title = self.cleaned_data.get('new_gallery_title')
gallery = Gallery.objects.create(title=title, slug=slugify(title))
GalleryExtended.objects.create(
gallery=gallery,
date_start=self.cleaned_data['date_start'],
date_end=self.cleaned_data['date_end'],
)
return gallery

View file

@ -0,0 +1,3 @@
{% extends "admin/change_list.html" %}
{% load i18n %}
{# Hide upload as zip #}

View file

@ -1,4 +1,7 @@
{% extends "photologue/root.html" %} {% extends "photologue/root.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Latest photo galleries" %}{% endblock %} {% block title %}{% trans "Latest photo galleries" %}{% endblock %}

View file

@ -1,4 +1,7 @@
{% extends "photologue/root.html" %} {% extends "photologue/root.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %} {% load i18n %}
{% block title %}{% blocktrans with show_year=year|date:"Y" %}Galleries for {{ show_year }}{% endblocktrans %}{% endblock %} {% block title %}{% blocktrans with show_year=year|date:"Y" %}Galleries for {{ show_year }}{% endblocktrans %}{% endblock %}

View file

@ -1,4 +1,7 @@
{% extends "photologue/root.html" %} {% extends "photologue/root.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load static i18n %} {% load static i18n %}
{% block title %}{{ gallery.title }}{% endblock %} {% block title %}{{ gallery.title }}{% endblock %}
@ -61,14 +64,12 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<div class="card-body"> <div class="card-body row" id="lightgallery">
<div id="lightgallery"> {% for photo in photos %}
{% for photo in photos %} <a class="col-6 col-md-3 mb-2 text-center" href="{{ photo.get_absolute_url }}" data-src="{{ photo.get_display_url }}" data-download-url="{{ photo.image.url }}">
<a href="{{ photo.get_absolute_url }}" data-src="{{ photo.get_display_url }}" data-download-url="{{ photo.image.url }}"> <img src="{{ photo.get_thumbnail_url }}" class="img-thumbnail" alt="{{ photo.title }}{% if photo.date_taken %} - {{ photo.date_taken|date }} {{ photo.date_taken|time }}{% endif %} - {{ photo.extended.owner.get_full_name }}">
<img src="{{ photo.get_thumbnail_url }}" class="img-thumbnail" alt="{{ photo.title }}{% if photo.date_taken %} - {{ photo.date_taken|date }} {{ photo.date_taken|time }}{% endif %} - {{ photo.extended.owner.get_full_name }}"> </a>
</a> {% endfor %}
{% endfor %}
</div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<a href="{% url 'gallery-download' gallery.slug %}" class="btn btn-secondary btn-sm">{% trans 'Download all gallery' %}</a> <a href="{% url 'gallery-download' gallery.slug %}" class="btn btn-secondary btn-sm">{% trans 'Download all gallery' %}</a>

View file

@ -7,7 +7,6 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{ gallery.title }}</h5> <h5 class="card-title">{{ gallery.title }}</h5>
{% if gallery.extended.date_start %}<p class="card-text text-muted small mb-0">{{ gallery.extended.date_start }}{% if gallery.extended.date_end and gallery.extended.date_end != gallery.extended.date_start %} - {{ gallery.extended.date_end }}{% endif %}</p>{% endif %} {% if gallery.extended.date_start %}<p class="card-text text-muted small mb-0">{{ gallery.extended.date_start }}{% if gallery.extended.date_end and gallery.extended.date_end != gallery.extended.date_start %} - {{ gallery.extended.date_end }}{% endif %}</p>{% endif %}
{% if gallery.description %}<p class="card-text small mb-0">{{ gallery.description|safe }}</p>{% endif %}
<a href="{{ gallery.get_absolute_url }}" class="stretched-link"></a> <a href="{{ gallery.get_absolute_url }}" class="stretched-link"></a>
</div> </div>
</div> </div>

View file

@ -1,4 +1,7 @@
{% extends "photologue/root.html" %} {% extends "photologue/root.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load photologue_tags i18n %} {% load photologue_tags i18n %}
{% block title %}{{ object.title }}{% endblock %} {% block title %}{{ object.title }}{% endblock %}

View file

@ -0,0 +1,69 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n crispy_forms_tags %}
{% block title %}{% trans "Upload" %}{% endblock %}
{% block extracss %}
<style>
.upload-drop-zone {
height: 10em;
line-height: 10em;
border-width: 2px;
color: #a3a3a3;
border-style: dashed;
border-color: #a3a3a3;
border-radius: 0.5em;
text-align: center;
margin-bottom: 0.5em;
}
.upload-drop-zone.drop {
color: #222;
border-color: #222;
background-color: rgba(163, 163, 163, 0.274);
}
</style>
{% endblock %}
{% block extrajs %}
<script>
const dropZone = document.getElementById('drop-zone');
const uploadInput = document.getElementById('id_file_field');
dropZone.ondrop = function(e) {
e.preventDefault();
this.className = 'upload-drop-zone';
console.log(e.dataTransfer.files)
uploadInput.files = e.dataTransfer.files;
}
dropZone.ondragover = function() {
this.className = 'upload-drop-zone drop';
return false;
}
dropZone.ondragleave = function() {
this.className = 'upload-drop-zone';
return false;
}
</script>
{% endblock %}
{% block content %}
<h1>{% trans "Upload" %}</h1>
<div class="card">
<div class="card-body">
<form method="post" enctype="multipart/form-data">{% csrf_token %}
<div class="upload-drop-zone" id="drop-zone">
{% trans "Drag and drop photos here" %}
</div>
{{ form|crispy }}
<p class="mt-3">
{% trans "Owner will be" %} <code>{{ request.user.get_full_name }} ({{ request.user.username}})</code>.
</p>
<button type="submit" class="btn btn-success">{% trans "Upload" %}</button>
</form>
</div>
</div>
{% endblock %}

View file

@ -1,8 +1,8 @@
from django.urls import path, re_path from django.urls import path, re_path
from .views import (CustomGalleryArchiveIndexView, from .views import (CustomGalleryArchiveIndexView, CustomGalleryDetailView,
CustomGalleryYearArchiveView, CustomGalleryDetailView, CustomGalleryYearArchiveView, GalleryDownload,
GalleryDownload, TagDetail) GalleryUpload, TagDetail)
urlpatterns = [ urlpatterns = [
path('tag/<slug:slug>/', TagDetail.as_view(), name='tag-detail'), path('tag/<slug:slug>/', TagDetail.as_view(), name='tag-detail'),
@ -11,4 +11,5 @@ urlpatterns = [
path('gallery/<slug:slug>/', CustomGalleryDetailView.as_view(), name='pl-gallery'), path('gallery/<slug:slug>/', CustomGalleryDetailView.as_view(), name='pl-gallery'),
path('gallery/<slug:slug>/<int:owner>/', CustomGalleryDetailView.as_view(), name='pl-gallery-owner'), path('gallery/<slug:slug>/<int:owner>/', CustomGalleryDetailView.as_view(), name='pl-gallery-owner'),
path('gallery/<slug:slug>/download/', GalleryDownload.as_view(), name='gallery-download'), path('gallery/<slug:slug>/download/', GalleryDownload.as_view(), name='gallery-download'),
path('upload/', GalleryUpload.as_view(), name='gallery-upload'),
] ]

View file

@ -5,13 +5,24 @@ import os
import zipfile import zipfile
from io import BytesIO from io import BytesIO
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages
from django.contrib.auth.mixins import (LoginRequiredMixin,
PermissionRequiredMixin)
from django.core.mail import mail_managers
from django.db import IntegrityError
from django.http import HttpResponse from django.http import HttpResponse
from django.views.generic import DetailView from django.urls import reverse_lazy
from photologue.models import Gallery from django.utils.text import slugify
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormView
from photologue.models import Gallery, Photo
from photologue.views import GalleryArchiveIndexView, GalleryYearArchiveView from photologue.views import GalleryArchiveIndexView, GalleryYearArchiveView
from PIL import Image
from taggit.models import Tag from taggit.models import Tag
from .forms import UploadForm
from .models import PhotoExtended
class TagDetail(LoginRequiredMixin, DetailView): class TagDetail(LoginRequiredMixin, DetailView):
model = Tag model = Tag
@ -23,7 +34,8 @@ class TagDetail(LoginRequiredMixin, DetailView):
current_tag = self.get_object().slug current_tag = self.get_object().slug
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['galleries'] = Gallery.objects.on_site().is_public() \ context['galleries'] = Gallery.objects.on_site().is_public() \
.filter(extended__tags__slug=current_tag) .filter(extended__tags__slug=current_tag) \
.order_by('-extended__date_start')
return context return context
@ -51,12 +63,14 @@ class CustomGalleryDetailView(DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['photos'] = self.object.public()
# Query with extended and owner to reduce database lag
context['photos'] = self.object.public().select_related('extended__owner')
# List owners # List owners
context['owners'] = [] context['owners'] = []
for photo in context['photos']: for photo in context['photos']:
if photo.extended.owner not in context['owners']: if hasattr(photo, 'extended') and photo.extended.owner not in context['owners']:
context['owners'].append(photo.extended.owner) context['owners'].append(photo.extended.owner)
# Filter on owner # Filter on owner
@ -86,3 +100,58 @@ class GalleryDownload(LoginRequiredMixin, DetailView):
response = HttpResponse(byte_data.getvalue(), content_type='application/x-zip-compressed') response = HttpResponse(byte_data.getvalue(), content_type='application/x-zip-compressed')
response['Content-Disposition'] = f"attachment; filename={gallery.slug}.zip" response['Content-Disposition'] = f"attachment; filename={gallery.slug}.zip"
return response return response
class GalleryUpload(PermissionRequiredMixin, FormView):
"""
Form to upload new photos in a gallery
"""
form_class = UploadForm
template_name = "photologue/upload.html"
success_url = reverse_lazy("gallery-upload")
permission_required = 'photologue.add_gallery'
def form_valid(self, form):
# Upload photos
# We take files from the request to support multiple upload
files = self.request.FILES.getlist('file_field')
gallery = form.get_or_create_gallery()
failed_upload = 0
for photo_file in files:
# Check that we have a valid image
print(photo_file, type(photo_file))
try:
opened = Image.open(photo_file)
opened.verify()
except Exception:
# Pillow doesn't recognize it as an image, skip it
messages.error(self.request, f"{photo_file.name} was not recognized as an image")
failed_upload += 1
continue
title = f"{gallery.title} - {photo_file.name}"
try:
photo = Photo(title=title, slug=slugify(title))
photo.image.save(photo_file.name, photo_file)
photo.save()
photo.galleries.set([gallery])
PhotoExtended.objects.create(photo=photo, owner=self.request.user)
except IntegrityError:
messages.error(self.request, f"{photo_file.name} was not uploaded. Maybe the photo was already uploaded.")
failed_upload += 1
# Notify user then managers
if not failed_upload:
messages.success(self.request, "All photos has been successfully uploaded.")
else:
n_success = len(files) - failed_upload
messages.warning(self.request, f"Only {n_success} photos were successfully uploaded !")
gallery_title = form.cleaned_data['gallery'] or form.cleaned_data.get('new_gallery_title', '')
photos = ", ".join(f.name for f in files)
mail_managers(
subject="New photos upload",
message=f"{self.request.user.username} has uploaded in `{gallery_title}`: {photos}",
)
return super().form_valid(form)