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.
|
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`.")
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 = '/'
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if user.emailaddress_set.all %}
|
{% if user.emailaddress_set.all %}
|
||||||
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>
|
<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="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">
|
||||||
|
|
|
||||||
|
|
@ -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 :
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
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" %}
|
{% 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 %}
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
|
||||||
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 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'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue