Merge branch 'photograph_upload' into 'master'
Photograph interface See merge request bde/photo21!11
This commit is contained in:
commit
29e9dba141
17 changed files with 351 additions and 55 deletions
|
|
@ -17,7 +17,7 @@ class CustomSignupForm(SignupForm):
|
|||
"""
|
||||
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"):
|
||||
raise forms.ValidationError(
|
||||
_("Must end with `@crans.org` or `@ens-paris-saclay.fr`.")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -42,19 +42,19 @@ msgstr ""
|
|||
msgid "hash"
|
||||
msgstr ""
|
||||
|
||||
#: photo21/settings.py:153
|
||||
#: photo21/settings.py:163
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: photo21/settings.py:154
|
||||
#: photo21/settings.py:164
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: photo21/settings.py:155
|
||||
#: photo21/settings.py:165
|
||||
msgid "Spanish"
|
||||
msgstr ""
|
||||
|
||||
#: photo21/settings.py:156
|
||||
#: photo21/settings.py:166
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ msgstr ""
|
|||
msgid "E-mail Addresses"
|
||||
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
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
|
@ -225,27 +225,34 @@ msgstr ""
|
|||
msgid "The ENS Paris-Saclay pictures server."
|
||||
msgstr ""
|
||||
|
||||
#: photo21/templates/base.html:35
|
||||
#: photo21/templates/base.html:36
|
||||
msgid "Galleries"
|
||||
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"
|
||||
msgstr "Gestion"
|
||||
|
||||
#: photo21/templates/base.html:60
|
||||
#: photo21/templates/base.html:67
|
||||
msgid "Log out"
|
||||
msgstr ""
|
||||
|
||||
#: photo21/templates/base.html:70
|
||||
#: photo21/templates/base.html:77
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
|
||||
#: photo21/templates/base.html:79
|
||||
#: photo21/templates/base.html:86
|
||||
msgid "Sign up"
|
||||
msgstr "Inscription"
|
||||
|
||||
#: photo21/templates/index.html:50
|
||||
#: photo21/templates/index.html:49
|
||||
msgid "Connected as"
|
||||
msgstr "Connecté en tant que"
|
||||
|
||||
|
|
@ -268,6 +275,40 @@ msgstr ""
|
|||
msgid "Add a 3rd Party Account"
|
||||
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
|
||||
msgid "start date"
|
||||
msgstr "date de début"
|
||||
|
|
@ -276,53 +317,57 @@ msgstr "date de début"
|
|||
msgid "end date"
|
||||
msgstr "date de fin"
|
||||
|
||||
#: photologue_custom/models.py:51
|
||||
msgid "owner"
|
||||
msgstr "propriétaire"
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:4
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:9
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:7
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:12
|
||||
msgid "Latest photo galleries"
|
||||
msgstr ""
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:15
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:18
|
||||
msgid "Filter by year"
|
||||
msgstr ""
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:32
|
||||
#: photologue_custom/templates/photologue/gallery_archive.html:35
|
||||
msgid "No galleries were found"
|
||||
msgstr ""
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:4
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:9
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:7
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:12
|
||||
#, python-format
|
||||
msgid "Galleries for %(show_year)s"
|
||||
msgstr ""
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:14
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:17
|
||||
msgid "View all galleries"
|
||||
msgstr ""
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:26
|
||||
#: photologue_custom/templates/photologue/gallery_archive_year.html:29
|
||||
msgid "No galleries were found."
|
||||
msgstr ""
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_detail.html:35
|
||||
#: photologue_custom/templates/photologue/gallery_detail.html:38
|
||||
msgid "to"
|
||||
msgstr "au"
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_detail.html:49
|
||||
#: photologue_custom/templates/photologue/gallery_detail.html:54
|
||||
msgid "All pictures"
|
||||
msgstr "Toutes les photos"
|
||||
|
||||
#: photologue_custom/templates/photologue/gallery_detail.html:63
|
||||
#: photologue_custom/templates/photologue/gallery_detail.html:75
|
||||
msgid "Download all gallery"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,16 @@ ALLOWED_HOSTS = [
|
|||
"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
|
||||
|
||||
|
|
@ -193,6 +203,7 @@ EMAIL_HOST_PASSWORD = os.getenv('EMAIL_PASSWORD', None)
|
|||
# Mail will be sent from this address
|
||||
SERVER_EMAIL = "photos@crans.org"
|
||||
DEFAULT_FROM_EMAIL = f"Serveur photos <{SERVER_EMAIL}>"
|
||||
EMAIL_SUBJECT_PREFIX = '[Serveur photos] '
|
||||
|
||||
# After login redirect user to transfer page
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body">
|
||||
{% if user.emailaddress_set.all %}
|
||||
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
<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="32x32" href="{% static "favicon-32x32.png" %}">
|
||||
<meta name="theme-color" content="#212529">
|
||||
{% block extracss %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
|
|
@ -34,10 +35,16 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
{% url 'photologue:pl-gallery-archive' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">{% trans 'Galleries' %}</a>
|
||||
</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 %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:index' %}">{% trans 'Manage' %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:index' %}">{% trans 'Manage' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
||||
<hr/>
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
{% trans "Connected as" %} <code>{{ request.user.username }}</code>.
|
||||
{% endif %}
|
||||
{% trans "Connected as" %} <code>{{ request.user.username }}</code>.
|
||||
<form action="{% url 'set_language' %}" method="post" style="max-width: 10em;">
|
||||
{% csrf_token %}
|
||||
Changer la langue :
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from django.contrib import admin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from photologue.admin import GalleryAdmin as GalleryAdminDefault
|
||||
from photologue.admin import PhotoAdmin as PhotoAdminDefault
|
||||
from photologue.models import Gallery, Photo, PhotoEffect, PhotoSize, Watermark
|
||||
|
|
@ -32,6 +33,16 @@ class PhotoAdmin(PhotoAdminDefault):
|
|||
model.
|
||||
"""
|
||||
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)
|
||||
|
|
|
|||
73
photologue_custom/forms.py
Normal file
73
photologue_custom/forms.py
Normal 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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n %}
|
||||
{# Hide upload as zip #}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
{% extends "photologue/root.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Latest photo galleries" %}{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{% extends "photologue/root.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% blocktrans with show_year=year|date:"Y" %}Galleries for {{ show_year }}{% endblocktrans %}{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{% extends "photologue/root.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{{ gallery.title }}{% endblock %}
|
||||
|
|
@ -61,14 +64,12 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="lightgallery">
|
||||
{% for photo in photos %}
|
||||
<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 }}">
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="card-body row" id="lightgallery">
|
||||
{% 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 }}">
|
||||
<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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'gallery-download' gallery.slug %}" class="btn btn-secondary btn-sm">{% trans 'Download all gallery' %}</a>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<div class="card-body">
|
||||
<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.description %}<p class="card-text small mb-0">{{ gallery.description|safe }}</p>{% endif %}
|
||||
<a href="{{ gallery.get_absolute_url }}" class="stretched-link"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
{% extends "photologue/root.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load photologue_tags i18n %}
|
||||
|
||||
{% block title %}{{ object.title }}{% endblock %}
|
||||
|
|
|
|||
69
photologue_custom/templates/photologue/upload.html
Normal file
69
photologue_custom/templates/photologue/upload.html
Normal 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 %}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
from django.urls import path, re_path
|
||||
|
||||
from .views import (CustomGalleryArchiveIndexView,
|
||||
CustomGalleryYearArchiveView, CustomGalleryDetailView,
|
||||
GalleryDownload, TagDetail)
|
||||
from .views import (CustomGalleryArchiveIndexView, CustomGalleryDetailView,
|
||||
CustomGalleryYearArchiveView, GalleryDownload,
|
||||
GalleryUpload, TagDetail)
|
||||
|
||||
urlpatterns = [
|
||||
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>/<int:owner>/', CustomGalleryDetailView.as_view(), name='pl-gallery-owner'),
|
||||
path('gallery/<slug:slug>/download/', GalleryDownload.as_view(), name='gallery-download'),
|
||||
path('upload/', GalleryUpload.as_view(), name='gallery-upload'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,13 +5,24 @@ import os
|
|||
import zipfile
|
||||
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.views.generic import DetailView
|
||||
from photologue.models import Gallery
|
||||
from django.urls import reverse_lazy
|
||||
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 PIL import Image
|
||||
from taggit.models import Tag
|
||||
|
||||
from .forms import UploadForm
|
||||
from .models import PhotoExtended
|
||||
|
||||
|
||||
class TagDetail(LoginRequiredMixin, DetailView):
|
||||
model = Tag
|
||||
|
|
@ -23,7 +34,8 @@ class TagDetail(LoginRequiredMixin, DetailView):
|
|||
current_tag = self.get_object().slug
|
||||
context = super().get_context_data(**kwargs)
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -51,12 +63,14 @@ class CustomGalleryDetailView(DetailView):
|
|||
|
||||
def get_context_data(self, **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
|
||||
context['owners'] = []
|
||||
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)
|
||||
|
||||
# Filter on owner
|
||||
|
|
@ -86,3 +100,58 @@ class GalleryDownload(LoginRequiredMixin, DetailView):
|
|||
response = HttpResponse(byte_data.getvalue(), content_type='application/x-zip-compressed')
|
||||
response['Content-Disposition'] = f"attachment; filename={gallery.slug}.zip"
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue