Merge photologue_custom into photologue

This commit is contained in:
Alexandre Iooss 2022-01-30 11:09:55 +01:00
parent 8beedb3626
commit d865a6eb1f
36 changed files with 549 additions and 566 deletions

100
photologue/forms.py Normal file
View file

@ -0,0 +1,100 @@
import datetime
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Div, Layout, Submit
from django import forms
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from .models import Gallery, Tag
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,
empty_label=_('-- Create a new gallery --'),
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,
)
new_gallery_date_start = forms.DateField(
label=_('New gallery event start date'),
initial=datetime.date.today,
required=False,
)
new_gallery_date_end = forms.DateField(
label=_('New gallery event end date'),
initial=datetime.date.today,
required=False,
)
new_gallery_tags = forms.ModelMultipleChoiceField(
Tag.objects.all(),
label=_('New gallery tags'),
required=False,
help_text=_('Hold down "Control", or "Command" on a Mac, to select more than one.')
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.use_custom_control = False
self.helper.layout = Layout(
'file_field',
'gallery',
'new_gallery_title',
Div(
Div('new_gallery_date_start', css_class='col'),
Div('new_gallery_date_end', css_class='col'),
css_class='row'
),
'new_gallery_tags',
Submit('submit', _('Upload'), css_class='btn btn-success mt-2')
)
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),
date_start=self.cleaned_data['new_gallery_date_start'],
date_end=self.cleaned_data['new_gallery_date_end'],
)
for tag in self.cleaned_data['new_gallery_tags']:
gallery.tags.add(tag)
return gallery

View file

@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Photologue\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-30 07:09+0000\n"
"POT-Creation-Date: 2022-01-30 09:55+0000\n"
"PO-Revision-Date: 2017-12-03 14:47+0000\n"
"Last-Translator: Richard Barran <richard@arbee-design.co.uk>\n"
"Language-Team: German (http://www.transifex.com/richardbarran/django-"
@ -22,75 +22,79 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: photologue/models.py:86
#: photologue/admin.py:30 photologue/models.py:499
msgid "owner"
msgstr ""
#: photologue/models.py:84
msgid "Very Low"
msgstr "Sehr niedrig"
#: photologue/models.py:87
#: photologue/models.py:85
msgid "Low"
msgstr "Niedrig"
#: photologue/models.py:88
#: photologue/models.py:86
msgid "Medium-Low"
msgstr "Mittel-niedrig"
#: photologue/models.py:89
#: photologue/models.py:87
msgid "Medium"
msgstr "Mittel"
#: photologue/models.py:90
#: photologue/models.py:88
msgid "Medium-High"
msgstr "Mittel-hoch"
#: photologue/models.py:91
#: photologue/models.py:89
msgid "High"
msgstr "Hoch"
#: photologue/models.py:92
#: photologue/models.py:90
msgid "Very High"
msgstr "Sehr hoch"
#: photologue/models.py:97
#: photologue/models.py:95
msgid "Top"
msgstr "Oben"
#: photologue/models.py:98
#: photologue/models.py:96
msgid "Right"
msgstr "Rechts"
#: photologue/models.py:99
#: photologue/models.py:97
msgid "Bottom"
msgstr "Unten"
#: photologue/models.py:100
#: photologue/models.py:98
msgid "Left"
msgstr "Links"
#: photologue/models.py:101
#: photologue/models.py:99
msgid "Center (Default)"
msgstr "Mitte (Standard)"
#: photologue/models.py:105
#: photologue/models.py:103
msgid "Flip left to right"
msgstr "Horizontal spiegeln"
#: photologue/models.py:106
#: photologue/models.py:104
msgid "Flip top to bottom"
msgstr "Vertikal spiegeln"
#: photologue/models.py:107
#: photologue/models.py:105
msgid "Rotate 90 degrees counter-clockwise"
msgstr "Um 90° nach links drehen"
#: photologue/models.py:108
#: photologue/models.py:106
msgid "Rotate 90 degrees clockwise"
msgstr "Um 90° nach rechts drehen"
#: photologue/models.py:109
#: photologue/models.py:107
msgid "Rotate 180 degrees"
msgstr "Um 180° drehen"
#: photologue/models.py:119
#: photologue/models.py:117
#, python-format
msgid ""
"Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO-"
@ -101,106 +105,122 @@ msgstr ""
"\". Bildfilter werden nach der Reihe angewendet. Folgende Filter sind "
"verfügbar: %s."
#: photologue/models.py:141
#: photologue/models.py:139
msgid "date published"
msgstr "Veröffentlichungsdatum"
#: photologue/models.py:143 photologue/models.py:474
#: photologue/models.py:141 photologue/models.py:485
msgid "title"
msgstr "Titel"
#: photologue/models.py:146
#: photologue/models.py:144
msgid "title slug"
msgstr "Kurztitel"
#: photologue/models.py:149 photologue/models.py:480
#: photologue/models.py:147 photologue/models.py:491 photologue/models.py:697
msgid "A \"slug\" is a unique URL-friendly title for an object."
msgstr ""
"Ein Kurztitel (\"slug\") ist ein eindeutiger, URL-geeigneter Titel für ein "
"Objekt."
#: photologue/models.py:150
msgid "start date"
msgstr ""
#: photologue/models.py:155
msgid "end date"
msgstr ""
#: photologue/models.py:157
msgid "description"
msgstr "Beschreibung"
#: photologue/models.py:152 photologue/models.py:485
#: photologue/models.py:162 photologue/models.py:703
msgid "tags"
msgstr ""
#: photologue/models.py:165 photologue/models.py:506
msgid "is public"
msgstr "ist öffentlich"
#: photologue/models.py:154
#: photologue/models.py:167
msgid "Public galleries will be displayed in the default views."
msgstr "Öffentliche Galerien werden in den Standard-Views angezeigt."
#: photologue/models.py:158 photologue/models.py:495
#: photologue/models.py:171 photologue/models.py:514
msgid "photos"
msgstr "Fotos"
#: photologue/models.py:166
#: photologue/models.py:177
msgid "gallery"
msgstr "Galerie"
#: photologue/models.py:167
#: photologue/models.py:178
msgid "galleries"
msgstr "Galerien"
#: photologue/models.py:202
#: photologue/models.py:213
msgid "count"
msgstr "Anzahl"
#: photologue/models.py:210
#: photologue/models.py:221
msgid "image"
msgstr "Bild"
#: photologue/models.py:213
#: photologue/models.py:224
msgid "date taken"
msgstr "Aufnahmedatum"
#: photologue/models.py:216
#: photologue/models.py:227
msgid "Date image was taken; is obtained from the image EXIF data."
msgstr ""
"Datum, an dem das Foto geschossen wurde; ausgelesen aus den EXIF-Daten."
#: photologue/models.py:217
#: photologue/models.py:228
msgid "view count"
msgstr "Anzahl an Aufrufen"
#: photologue/models.py:220
#: photologue/models.py:231
msgid "crop from"
msgstr "Beschneiden von"
#: photologue/models.py:243
#: photologue/models.py:254
msgid "An \"admin_thumbnail\" photo size has not been defined."
msgstr "Es ist keine Fotogröße \"admin_thumbnail\" definiert."
#: photologue/models.py:250
#: photologue/models.py:261
msgid "Thumbnail"
msgstr "Vorschaubild"
#: photologue/models.py:477
#: photologue/models.py:488 photologue/models.py:696
msgid "slug"
msgstr "Kurztitel"
#: photologue/models.py:481
#: photologue/models.py:492
msgid "caption"
msgstr "Bildunterschrift"
#: photologue/models.py:483
#: photologue/models.py:494
msgid "date added"
msgstr "Datum des Eintrags"
#: photologue/models.py:487
#: photologue/models.py:504
msgid "license"
msgstr ""
#: photologue/models.py:508
msgid "Public photographs will be displayed in the default views."
msgstr "Öffentliche Fotos werden in den Standard-Views angezeigt."
#: photologue/models.py:494
#: photologue/models.py:513
msgid "photo"
msgstr "Foto"
#: photologue/models.py:556
#: photologue/models.py:575 photologue/models.py:691
msgid "name"
msgstr "Name"
#: photologue/models.py:560
#: photologue/models.py:579
msgid ""
"Photo size name should contain only letters, numbers and underscores. "
"Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"."
@ -209,41 +229,41 @@ msgstr ""
"enthalten. Beispiele: \"thumbnail\", \"display\", \"small\", "
"\"main_page_widget\"."
#: photologue/models.py:567
#: photologue/models.py:586
msgid "width"
msgstr "Breite"
#: photologue/models.py:570
#: photologue/models.py:589
msgid ""
"If width is set to \"0\" the image will be scaled to the supplied height."
msgstr ""
"Wenn die Breite auf \"0\" gesetzt ist, wird das Bild proportional auf die "
"angebene Höhe skaliert."
#: photologue/models.py:571
#: photologue/models.py:590
msgid "height"
msgstr "Höhe"
#: photologue/models.py:574
#: photologue/models.py:593
msgid ""
"If height is set to \"0\" the image will be scaled to the supplied width"
msgstr ""
"Wenn die Höhe auf \"0\" gesetzt ist, wird das Bild proportional auf die "
"angebene Breite skaliert."
#: photologue/models.py:575
#: photologue/models.py:594
msgid "quality"
msgstr "Qualität"
#: photologue/models.py:578
#: photologue/models.py:597
msgid "JPEG image quality."
msgstr "JPEG-Bildqualität"
#: photologue/models.py:579
#: photologue/models.py:598
msgid "upscale images?"
msgstr "Bilder hochskalieren?"
#: photologue/models.py:581
#: photologue/models.py:600
msgid ""
"If selected the image will be scaled up if necessary to fit the supplied "
"dimensions. Cropped sizes will be upscaled regardless of this setting."
@ -252,32 +272,32 @@ msgstr ""
"Beschnittene Größen werden unabhängig von dieser Einstellung bei Bedarf "
"hochskaliert."
#: photologue/models.py:585
#: photologue/models.py:604
msgid "crop to fit?"
msgstr "Zuschneiden?"
#: photologue/models.py:587
#: photologue/models.py:606
msgid ""
"If selected the image will be scaled and cropped to fit the supplied "
"dimensions."
msgstr ""
"Soll das Bild auf das angegebene Format skaliert und beschnitten werden?"
#: photologue/models.py:589
#: photologue/models.py:608
msgid "pre-cache?"
msgstr "Vorausspeichern?"
#: photologue/models.py:591
#: photologue/models.py:610
msgid "If selected this photo size will be pre-cached as photos are added."
msgstr ""
"Soll diese Bildgröße im Voraus gespeichert (pre-cached) werden, wenn Fotos "
"hinzugefügt werden?"
#: photologue/models.py:592
#: photologue/models.py:611
msgid "increment view count?"
msgstr "Bildzähler?"
#: photologue/models.py:594
#: photologue/models.py:613
msgid ""
"If selected the image's \"view_count\" will be incremented when this photo "
"size is displayed."
@ -285,32 +305,32 @@ msgstr ""
"Soll der Ansichts-Zähler (view-count) hochgezählt werden, wenn ein Foto "
"dieser Größe angezeigt wird?"
#: photologue/models.py:599
#: photologue/models.py:618
msgid "photo size"
msgstr "Foto-Größe"
#: photologue/models.py:600
#: photologue/models.py:619
msgid "photo sizes"
msgstr "Foto-Größen"
#: photologue/models.py:617
#: photologue/models.py:636
msgid "Can only crop photos if both width and height dimensions are set."
msgstr ""
"Fotos können nur zugeschnitten werden, wenn Breite und Höhe angegeben sind."
#: photologue_custom/admin.py:43 photologue_custom/models.py:51
msgid "owner"
#: photologue/models.py:702
msgid "tag"
msgstr ""
#: photologue_custom/forms.py:34
#: photologue_custom/forms.py:22
msgid "Gallery"
msgstr "Galerie"
#: photologue_custom/forms.py:36
#: photologue_custom/forms.py:24
msgid "-- Create a new gallery --"
msgstr ""
#: photologue_custom/forms.py:37
#: photologue_custom/forms.py:25
msgid ""
"Select a gallery to add these images to. Leave this empty to create a new "
"gallery from the supplied title."
@ -318,59 +338,43 @@ msgstr ""
"Wähle eine Galerie aus, zu der diese Bilder hinzugefügt werden sollen. Lasse "
"dieses Feld leer, um eine neue Galerie mit dem angegeben Titel zu erzeugen."
#: photologue_custom/forms.py:41
#, fuzzy
#| msgid "View all galleries"
#: photologue_custom/forms.py:29
msgid "New gallery title"
msgstr "Zeige alle Galerien."
msgstr ""
#: photologue_custom/forms.py:46
#: photologue_custom/forms.py:34
msgid "New gallery event start date"
msgstr ""
#: photologue_custom/forms.py:51
#: photologue_custom/forms.py:39
msgid "New gallery event end date"
msgstr ""
#: photologue_custom/forms.py:57
#, fuzzy
#| msgid "gallery"
#: photologue_custom/forms.py:45
msgid "New gallery tags"
msgstr "Galerie"
msgstr ""
#: photologue_custom/forms.py:59
#: photologue_custom/forms.py:47
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
#: photologue_custom/forms.py:76
#: photologue_custom/forms.py:64
#: photologue_custom/templates/photologue/upload.html:6
#: photologue_custom/templates/photologue/upload.html:73
msgid "Upload"
msgstr "Hochladen"
#: photologue_custom/forms.py:82
#: photologue_custom/forms.py:70
msgid "A gallery with that title already exists."
msgstr "Es existiert bereits eine Gallerie mit diesem Titel."
#: photologue_custom/forms.py:91
#: photologue_custom/forms.py:79
msgid "Select an existing gallery, or enter a title for a new gallery."
msgstr ""
"Wähle eine existierende Galerie aus oder gib einen Titel für eine neue "
"Galerie ein."
#: photologue_custom/models.py:23
msgid "start date"
msgstr ""
#: photologue_custom/models.py:28
msgid "end date"
msgstr ""
#: photologue_custom/models.py:56
msgid "license"
msgstr ""
#: photologue_custom/templates/photologue/gallery_archive.html:7
#: photologue_custom/templates/photologue/gallery_archive.html:12
msgid "Latest photo galleries"
@ -403,16 +407,12 @@ msgid "to"
msgstr ""
#: photologue_custom/templates/photologue/gallery_detail.html:57
#, fuzzy
#| msgid "All photos"
msgid "All pictures"
msgstr "Alle Fotos"
msgstr ""
#: photologue_custom/templates/photologue/gallery_detail.html:78
#, fuzzy
#| msgid "View all galleries"
msgid "Download all gallery"
msgstr "Zeige alle Galerien."
msgstr ""
#: photologue_custom/templates/photologue/photo_detail.html:13
msgid "Published"

View file

@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Photologue\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-30 07:09+0000\n"
"POT-Creation-Date: 2022-01-30 09:55+0000\n"
"PO-Revision-Date: 2017-12-03 14:46+0000\n"
"Last-Translator: Richard Barran <richard@arbee-design.co.uk>\n"
"Language-Team: Spanish (Spain) (http://www.transifex.com/richardbarran/"
@ -23,75 +23,79 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: photologue/models.py:86
#: photologue/admin.py:30 photologue/models.py:499
msgid "owner"
msgstr ""
#: photologue/models.py:84
msgid "Very Low"
msgstr "Muy bajo"
#: photologue/models.py:87
#: photologue/models.py:85
msgid "Low"
msgstr "Bajo"
#: photologue/models.py:88
#: photologue/models.py:86
msgid "Medium-Low"
msgstr "Medio-bajo"
#: photologue/models.py:89
#: photologue/models.py:87
msgid "Medium"
msgstr "Medio"
#: photologue/models.py:90
#: photologue/models.py:88
msgid "Medium-High"
msgstr "Medio-alto"
#: photologue/models.py:91
#: photologue/models.py:89
msgid "High"
msgstr "Alto"
#: photologue/models.py:92
#: photologue/models.py:90
msgid "Very High"
msgstr "Muy alto"
#: photologue/models.py:97
#: photologue/models.py:95
msgid "Top"
msgstr "Arriba"
#: photologue/models.py:98
#: photologue/models.py:96
msgid "Right"
msgstr "Derecha"
#: photologue/models.py:99
#: photologue/models.py:97
msgid "Bottom"
msgstr "Abajo"
#: photologue/models.py:100
#: photologue/models.py:98
msgid "Left"
msgstr "Izquierda"
#: photologue/models.py:101
#: photologue/models.py:99
msgid "Center (Default)"
msgstr "Centro (por defecto)"
#: photologue/models.py:105
#: photologue/models.py:103
msgid "Flip left to right"
msgstr "Voltear de izquerda a derecha"
#: photologue/models.py:106
#: photologue/models.py:104
msgid "Flip top to bottom"
msgstr "Voltear de arriba a abajo"
#: photologue/models.py:107
#: photologue/models.py:105
msgid "Rotate 90 degrees counter-clockwise"
msgstr "Rotar 90 grados en sentido horario"
#: photologue/models.py:108
#: photologue/models.py:106
msgid "Rotate 90 degrees clockwise"
msgstr "Rotar 90 grados en sentido antihorario"
#: photologue/models.py:109
#: photologue/models.py:107
msgid "Rotate 180 degrees"
msgstr "Rotar 180 grados"
#: photologue/models.py:119
#: photologue/models.py:117
#, python-format
msgid ""
"Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO-"
@ -102,103 +106,119 @@ msgstr ""
">FILTRO_DOS->FILTRO_TRES\". Los filtros de imagen se aplicarán en orden. Los "
"siguientes filtros están disponibles: %s."
#: photologue/models.py:141
#: photologue/models.py:139
msgid "date published"
msgstr "fecha de publicación"
#: photologue/models.py:143 photologue/models.py:474
#: photologue/models.py:141 photologue/models.py:485
msgid "title"
msgstr "título"
#: photologue/models.py:146
#: photologue/models.py:144
msgid "title slug"
msgstr "título slug"
#: photologue/models.py:149 photologue/models.py:480
#: photologue/models.py:147 photologue/models.py:491 photologue/models.py:697
msgid "A \"slug\" is a unique URL-friendly title for an object."
msgstr "Un \"slug\" es un único título URL-amigable para un objeto."
#: photologue/models.py:150
msgid "start date"
msgstr ""
#: photologue/models.py:155
msgid "end date"
msgstr ""
#: photologue/models.py:157
msgid "description"
msgstr "descripción"
#: photologue/models.py:152 photologue/models.py:485
#: photologue/models.py:162 photologue/models.py:703
msgid "tags"
msgstr ""
#: photologue/models.py:165 photologue/models.py:506
msgid "is public"
msgstr "es público"
#: photologue/models.py:154
#: photologue/models.py:167
msgid "Public galleries will be displayed in the default views."
msgstr "Las galerías públicas serán mostradas en las vistas por defecto."
#: photologue/models.py:158 photologue/models.py:495
#: photologue/models.py:171 photologue/models.py:514
msgid "photos"
msgstr "fotos"
#: photologue/models.py:166
#: photologue/models.py:177
msgid "gallery"
msgstr "galería"
#: photologue/models.py:167
#: photologue/models.py:178
msgid "galleries"
msgstr "galerías"
#: photologue/models.py:202
#: photologue/models.py:213
msgid "count"
msgstr "contar"
#: photologue/models.py:210
#: photologue/models.py:221
msgid "image"
msgstr "imagen"
#: photologue/models.py:213
#: photologue/models.py:224
msgid "date taken"
msgstr "fecha en la que se tomó"
#: photologue/models.py:216
#: photologue/models.py:227
msgid "Date image was taken; is obtained from the image EXIF data."
msgstr "La fecha de la imagen fue obtenida por información EXIF de la imagen."
#: photologue/models.py:217
#: photologue/models.py:228
msgid "view count"
msgstr "Contador de visitas"
#: photologue/models.py:220
#: photologue/models.py:231
msgid "crop from"
msgstr "Recortar desde"
#: photologue/models.py:243
#: photologue/models.py:254
msgid "An \"admin_thumbnail\" photo size has not been defined."
msgstr "El tamaño de foto de \"miniatura de admin\" no ha sido definido."
#: photologue/models.py:250
#: photologue/models.py:261
msgid "Thumbnail"
msgstr "Miniatura"
#: photologue/models.py:477
#: photologue/models.py:488 photologue/models.py:696
msgid "slug"
msgstr "slug"
#: photologue/models.py:481
#: photologue/models.py:492
msgid "caption"
msgstr "pie de foto"
#: photologue/models.py:483
#: photologue/models.py:494
msgid "date added"
msgstr "fecha añadida"
#: photologue/models.py:487
#: photologue/models.py:504
msgid "license"
msgstr ""
#: photologue/models.py:508
msgid "Public photographs will be displayed in the default views."
msgstr "Las fotos públicas serán mostradas en las vistas por defecto."
#: photologue/models.py:494
#: photologue/models.py:513
msgid "photo"
msgstr "foto"
#: photologue/models.py:556
#: photologue/models.py:575 photologue/models.py:691
msgid "name"
msgstr "nombre"
#: photologue/models.py:560
#: photologue/models.py:579
msgid ""
"Photo size name should contain only letters, numbers and underscores. "
"Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"."
@ -206,41 +226,41 @@ msgstr ""
"El nombre del tamaño solo puede contener letras, números y subrayados. Por "
"ejemplo:\"miniaturas\", \"muestra\", \"muestra_principal\"."
#: photologue/models.py:567
#: photologue/models.py:586
msgid "width"
msgstr "anchura"
#: photologue/models.py:570
#: photologue/models.py:589
msgid ""
"If width is set to \"0\" the image will be scaled to the supplied height."
msgstr ""
"Si la anchura se establece a \"0\" la imagen será escalada hasta la altura "
"proporcionada"
#: photologue/models.py:571
#: photologue/models.py:590
msgid "height"
msgstr "altura"
#: photologue/models.py:574
#: photologue/models.py:593
msgid ""
"If height is set to \"0\" the image will be scaled to the supplied width"
msgstr ""
"Si la altura se establece a \"0\" la imagen será escalada hasta la anchura "
"proporcionada"
#: photologue/models.py:575
#: photologue/models.py:594
msgid "quality"
msgstr "calidad"
#: photologue/models.py:578
#: photologue/models.py:597
msgid "JPEG image quality."
msgstr "Calidad de imagen JPEG."
#: photologue/models.py:579
#: photologue/models.py:598
msgid "upscale images?"
msgstr "¿Aumentar imágenes?"
#: photologue/models.py:581
#: photologue/models.py:600
msgid ""
"If selected the image will be scaled up if necessary to fit the supplied "
"dimensions. Cropped sizes will be upscaled regardless of this setting."
@ -249,11 +269,11 @@ msgstr ""
"las dimensiones proporcionadas. Los tamaños recortados serán aumentados de "
"acuerdo a esta opción."
#: photologue/models.py:585
#: photologue/models.py:604
msgid "crop to fit?"
msgstr "¿Recortar hasta ajustar?"
#: photologue/models.py:587
#: photologue/models.py:606
msgid ""
"If selected the image will be scaled and cropped to fit the supplied "
"dimensions."
@ -261,21 +281,21 @@ msgstr ""
"Si se selecciona la imagen será escalada y recortada para ajustarse a las "
"dimensiones proporcionadas."
#: photologue/models.py:589
#: photologue/models.py:608
msgid "pre-cache?"
msgstr "¿pre-cachear?"
#: photologue/models.py:591
#: photologue/models.py:610
msgid "If selected this photo size will be pre-cached as photos are added."
msgstr ""
"Si se selecciona, este tamaño de foto será pre-cacheado cuando se añadan "
"nuevas fotos."
#: photologue/models.py:592
#: photologue/models.py:611
msgid "increment view count?"
msgstr "¿incrementar contador de visualizaciones?"
#: photologue/models.py:594
#: photologue/models.py:613
msgid ""
"If selected the image's \"view_count\" will be incremented when this photo "
"size is displayed."
@ -283,31 +303,31 @@ msgstr ""
"Si se selecciona el \"contador de visualizaciones\" se incrementará cuando "
"esta foto sea visualizada."
#: photologue/models.py:599
#: photologue/models.py:618
msgid "photo size"
msgstr "tamaño de foto"
#: photologue/models.py:600
#: photologue/models.py:619
msgid "photo sizes"
msgstr "tamaños de foto"
#: photologue/models.py:617
#: photologue/models.py:636
msgid "Can only crop photos if both width and height dimensions are set."
msgstr "Solo puede recortar las fotos si ancho y alto están establecidos."
#: photologue_custom/admin.py:43 photologue_custom/models.py:51
msgid "owner"
#: photologue/models.py:702
msgid "tag"
msgstr ""
#: photologue_custom/forms.py:34
#: photologue_custom/forms.py:22
msgid "Gallery"
msgstr "Galería"
#: photologue_custom/forms.py:36
#: photologue_custom/forms.py:24
msgid "-- Create a new gallery --"
msgstr ""
#: photologue_custom/forms.py:37
#: photologue_custom/forms.py:25
msgid ""
"Select a gallery to add these images to. Leave this empty to create a new "
"gallery from the supplied title."
@ -315,58 +335,42 @@ msgstr ""
"Seleccione una galería para agregarle estas imágenes. Déjelo vacío para "
"crear una nueva galería a partir de este título."
#: photologue_custom/forms.py:41
#, fuzzy
#| msgid "View all galleries"
#: photologue_custom/forms.py:29
msgid "New gallery title"
msgstr "Ver todas las galerías"
msgstr ""
#: photologue_custom/forms.py:46
#: photologue_custom/forms.py:34
msgid "New gallery event start date"
msgstr ""
#: photologue_custom/forms.py:51
#: photologue_custom/forms.py:39
msgid "New gallery event end date"
msgstr ""
#: photologue_custom/forms.py:57
#, fuzzy
#| msgid "gallery"
#: photologue_custom/forms.py:45
msgid "New gallery tags"
msgstr "galería"
msgstr ""
#: photologue_custom/forms.py:59
#: photologue_custom/forms.py:47
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
#: photologue_custom/forms.py:76
#: photologue_custom/forms.py:64
#: photologue_custom/templates/photologue/upload.html:6
#: photologue_custom/templates/photologue/upload.html:73
msgid "Upload"
msgstr "Subir"
#: photologue_custom/forms.py:82
#: photologue_custom/forms.py:70
msgid "A gallery with that title already exists."
msgstr "Ya existe una galería con ese título."
#: photologue_custom/forms.py:91
#: photologue_custom/forms.py:79
msgid "Select an existing gallery, or enter a title for a new gallery."
msgstr ""
"Seleccione una galería existente o ingrese un nuevo nombre para la galería."
#: photologue_custom/models.py:23
msgid "start date"
msgstr ""
#: photologue_custom/models.py:28
msgid "end date"
msgstr ""
#: photologue_custom/models.py:56
msgid "license"
msgstr ""
#: photologue_custom/templates/photologue/gallery_archive.html:7
#: photologue_custom/templates/photologue/gallery_archive.html:12
msgid "Latest photo galleries"
@ -399,16 +403,12 @@ msgid "to"
msgstr ""
#: photologue_custom/templates/photologue/gallery_detail.html:57
#, fuzzy
#| msgid "All photos"
msgid "All pictures"
msgstr "Todas las fotos"
msgstr ""
#: photologue_custom/templates/photologue/gallery_detail.html:78
#, fuzzy
#| msgid "View all galleries"
msgid "Download all gallery"
msgstr "Ver todas las galerías"
msgstr ""
#: photologue_custom/templates/photologue/photo_detail.html:13
msgid "Published"

View file

@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Photologue\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-30 07:09+0000\n"
"POT-Creation-Date: 2022-01-30 10:06+0000\n"
"PO-Revision-Date: 2017-12-03 14:47+0000\n"
"Last-Translator: Richard Barran <richard@arbee-design.co.uk>\n"
"Language-Team: French (http://www.transifex.com/richardbarran/django-"
@ -21,75 +21,131 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: photologue/models.py:86
#: photologue/admin.py:30 photologue/models.py:499
msgid "owner"
msgstr "propriétaire"
#: photologue/forms.py:23
msgid "Gallery"
msgstr "Galerie"
#: photologue/forms.py:25
msgid "-- Create a new gallery --"
msgstr "-- Créer une nouvelle galerie --"
#: photologue/forms.py:26
msgid ""
"Select a gallery to add these images to. Leave this empty to create a new "
"gallery from the supplied title."
msgstr ""
"Sélectionner une galerie à laquelle ajouter ces images. Laisser ce champ "
"vide pour créer une nouvelle galerie à partir du titre indiqué."
#: photologue/forms.py:30
msgid "New gallery title"
msgstr "Titre de la nouvelle galerie"
#: photologue/forms.py:35
msgid "New gallery event start date"
msgstr "Date de début de l'évènement de la nouvelle galerie"
#: photologue/forms.py:40
msgid "New gallery event end date"
msgstr "Date de fin de l'évènement de la nouvelle galerie"
#: photologue/forms.py:46
msgid "New gallery tags"
msgstr "Balises de la nouvelle galerie"
#: photologue/forms.py:48
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
#: photologue/forms.py:65 photologue/templates/photologue/upload.html:6
#: photologue/templates/photologue/upload.html:73
msgid "Upload"
msgstr "Télécharger"
#: photologue/forms.py:71
msgid "A gallery with that title already exists."
msgstr "Une galerie portant ce nom existe déjà."
#: photologue/forms.py:80
msgid "Select an existing gallery, or enter a title for a new gallery."
msgstr ""
"Sélectionner une galerie existante ou entrer un titre pour une nouvelle "
"galerie."
#: photologue/models.py:84
msgid "Very Low"
msgstr "Très Bas"
#: photologue/models.py:87
#: photologue/models.py:85
msgid "Low"
msgstr "Bas"
#: photologue/models.py:88
#: photologue/models.py:86
msgid "Medium-Low"
msgstr "Moyen-Bas"
#: photologue/models.py:89
#: photologue/models.py:87
msgid "Medium"
msgstr "Moyen"
#: photologue/models.py:90
#: photologue/models.py:88
msgid "Medium-High"
msgstr "Moyen-Haut"
#: photologue/models.py:91
#: photologue/models.py:89
msgid "High"
msgstr "Haut"
#: photologue/models.py:92
#: photologue/models.py:90
msgid "Very High"
msgstr "Très Haut"
#: photologue/models.py:97
#: photologue/models.py:95
msgid "Top"
msgstr "Sommet"
#: photologue/models.py:98
#: photologue/models.py:96
msgid "Right"
msgstr "Droite"
#: photologue/models.py:99
#: photologue/models.py:97
msgid "Bottom"
msgstr "Bas"
#: photologue/models.py:100
#: photologue/models.py:98
msgid "Left"
msgstr "Gauche"
#: photologue/models.py:101
#: photologue/models.py:99
msgid "Center (Default)"
msgstr "Centré (par défaut)"
#: photologue/models.py:105
#: photologue/models.py:103
msgid "Flip left to right"
msgstr "Inversion de gauche à droite"
#: photologue/models.py:106
#: photologue/models.py:104
msgid "Flip top to bottom"
msgstr "Inversion de haut en bas"
#: photologue/models.py:107
#: photologue/models.py:105
msgid "Rotate 90 degrees counter-clockwise"
msgstr "Rotation de 90 degrés dans le sens anti-horloger"
#: photologue/models.py:108
#: photologue/models.py:106
msgid "Rotate 90 degrees clockwise"
msgstr "Rotation de 90 degrés dans le sens horloger"
#: photologue/models.py:109
#: photologue/models.py:107
msgid "Rotate 180 degrees"
msgstr "Rotation de 180 degrés"
#: photologue/models.py:119
#: photologue/models.py:117
#, python-format
msgid ""
"Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO-"
@ -100,107 +156,123 @@ msgstr ""
">FILTRE_DEUX->FILTRE_TROIS\". Les filtres d'image seront appliqués dans "
"l'ordre. Les filtres suivants sont disponibles: %s."
#: photologue/models.py:141
#: photologue/models.py:139
msgid "date published"
msgstr "date de publication"
#: photologue/models.py:143 photologue/models.py:474
#: photologue/models.py:141 photologue/models.py:485
msgid "title"
msgstr "titre"
#: photologue/models.py:146
#: photologue/models.py:144
msgid "title slug"
msgstr "version abrégée du titre"
#: photologue/models.py:149 photologue/models.py:480
#: photologue/models.py:147 photologue/models.py:491 photologue/models.py:697
msgid "A \"slug\" is a unique URL-friendly title for an object."
msgstr ""
"Un \"slug\" est un titre abrégé et unique, compatible avec les URL, pour un "
"objet."
#: photologue/models.py:150
msgid "start date"
msgstr "date de début"
#: photologue/models.py:155
msgid "end date"
msgstr "date de fin"
#: photologue/models.py:157
msgid "description"
msgstr "description"
#: photologue/models.py:152 photologue/models.py:485
#: photologue/models.py:162 photologue/models.py:703
msgid "tags"
msgstr "balises"
#: photologue/models.py:165 photologue/models.py:506
msgid "is public"
msgstr "est public"
#: photologue/models.py:154
#: photologue/models.py:167
msgid "Public galleries will be displayed in the default views."
msgstr "Les galeries publiques seront affichée dans les vues par défaut."
#: photologue/models.py:158 photologue/models.py:495
#: photologue/models.py:171 photologue/models.py:514
msgid "photos"
msgstr "photos"
#: photologue/models.py:166
#: photologue/models.py:177
msgid "gallery"
msgstr "galerie"
#: photologue/models.py:167
#: photologue/models.py:178
msgid "galleries"
msgstr "galleries"
#: photologue/models.py:202
#: photologue/models.py:213
msgid "count"
msgstr "nombre"
#: photologue/models.py:210
#: photologue/models.py:221
msgid "image"
msgstr "image"
#: photologue/models.py:213
#: photologue/models.py:224
msgid "date taken"
msgstr "date de prise de vue"
#: photologue/models.py:216
#: photologue/models.py:227
msgid "Date image was taken; is obtained from the image EXIF data."
msgstr ""
"La date à laquelle l'image a été prise ; obtenue à partir des données EXIF "
"de l'image."
#: photologue/models.py:217
#: photologue/models.py:228
msgid "view count"
msgstr "nombre"
#: photologue/models.py:220
#: photologue/models.py:231
msgid "crop from"
msgstr "découper à partir de"
#: photologue/models.py:243
#: photologue/models.py:254
msgid "An \"admin_thumbnail\" photo size has not been defined."
msgstr "Une taille de photo \"admin_thumbnail\" n'a pas encore été définie."
#: photologue/models.py:250
#: photologue/models.py:261
msgid "Thumbnail"
msgstr "Miniature"
#: photologue/models.py:477
#: photologue/models.py:488 photologue/models.py:696
msgid "slug"
msgstr "libellé court"
#: photologue/models.py:481
#: photologue/models.py:492
msgid "caption"
msgstr "légende"
#: photologue/models.py:483
#: photologue/models.py:494
msgid "date added"
msgstr "date d'ajout"
#: photologue/models.py:487
#: photologue/models.py:504
msgid "license"
msgstr "licence"
#: photologue/models.py:508
msgid "Public photographs will be displayed in the default views."
msgstr "Les photographies publique seront affichées dans les vues par défaut."
#: photologue/models.py:494
#: photologue/models.py:513
msgid "photo"
msgstr "photo"
#: photologue/models.py:556
#: photologue/models.py:575 photologue/models.py:691
msgid "name"
msgstr "nom"
#: photologue/models.py:560
#: photologue/models.py:579
msgid ""
"Photo size name should contain only letters, numbers and underscores. "
"Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"."
@ -209,41 +281,41 @@ msgstr ""
"chiffres et des caractères de soulignement. Exemples: \"miniature\", "
"\"affichage\", \"petit\", \"widget_page_principale\"."
#: photologue/models.py:567
#: photologue/models.py:586
msgid "width"
msgstr "largeur"
#: photologue/models.py:570
#: photologue/models.py:589
msgid ""
"If width is set to \"0\" the image will be scaled to the supplied height."
msgstr ""
"Si la largeur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
"la hauteur fournie."
#: photologue/models.py:571
#: photologue/models.py:590
msgid "height"
msgstr "hauteur"
#: photologue/models.py:574
#: photologue/models.py:593
msgid ""
"If height is set to \"0\" the image will be scaled to the supplied width"
msgstr ""
"Si la hauteur est réglée à \"0\" l l'image sera redimensionnée par rapport à "
"la largeur fournie."
#: photologue/models.py:575
#: photologue/models.py:594
msgid "quality"
msgstr "qualité"
#: photologue/models.py:578
#: photologue/models.py:597
msgid "JPEG image quality."
msgstr "Qualité JPEG de l'image."
#: photologue/models.py:579
#: photologue/models.py:598
msgid "upscale images?"
msgstr "agrandir les images ?"
#: photologue/models.py:581
#: photologue/models.py:600
msgid ""
"If selected the image will be scaled up if necessary to fit the supplied "
"dimensions. Cropped sizes will be upscaled regardless of this setting."
@ -252,11 +324,11 @@ msgstr ""
"dimensions fournies. Les dimensions ajustées seront agrandies sans prendre "
"en compte ce paramètre."
#: photologue/models.py:585
#: photologue/models.py:604
msgid "crop to fit?"
msgstr "découper pour adapter à la taille ?"
#: photologue/models.py:587
#: photologue/models.py:606
msgid ""
"If selected the image will be scaled and cropped to fit the supplied "
"dimensions."
@ -264,21 +336,21 @@ msgstr ""
"Si sélectionné l'image sera redimensionnée et recadrée pour coïncider avec "
"les dimensions fournies."
#: photologue/models.py:589
#: photologue/models.py:608
msgid "pre-cache?"
msgstr "mise en cache ?"
#: photologue/models.py:591
#: photologue/models.py:610
msgid "If selected this photo size will be pre-cached as photos are added."
msgstr ""
"Si sélectionné cette taille de photo sera mise en cache au moment au les "
"photos sont ajoutées."
#: photologue/models.py:592
#: photologue/models.py:611
msgid "increment view count?"
msgstr "incrémenter le nombre d'affichages ?"
#: photologue/models.py:594
#: photologue/models.py:613
msgid ""
"If selected the image's \"view_count\" will be incremented when this photo "
"size is displayed."
@ -286,148 +358,75 @@ msgstr ""
"Si sélectionné le \"view_count\" (nombre d'affichage) de l'image sera "
"incrémenté quand cette taille de photo sera affichée."
#: photologue/models.py:599
#: photologue/models.py:618
msgid "photo size"
msgstr "taille de la photo"
#: photologue/models.py:600
#: photologue/models.py:619
msgid "photo sizes"
msgstr "tailles des photos"
#: photologue/models.py:617
#: photologue/models.py:636
msgid "Can only crop photos if both width and height dimensions are set."
msgstr ""
"La hauteur et la largeur doivent être toutes les deux définies pour "
"retailler des photos."
#: photologue_custom/admin.py:43 photologue_custom/models.py:51
msgid "owner"
#: photologue/models.py:702
msgid "tag"
msgstr ""
#: photologue_custom/forms.py:34
msgid "Gallery"
msgstr "Galerie"
#: photologue_custom/forms.py:36
msgid "-- Create a new gallery --"
msgstr ""
#: photologue_custom/forms.py:37
msgid ""
"Select a gallery to add these images to. Leave this empty to create a new "
"gallery from the supplied title."
msgstr ""
"Sélectionner une galerie à laquelle ajouter ces images. Laisser ce champ "
"vide pour créer une nouvelle galerie à partir du titre indiqué."
#: photologue_custom/forms.py:41
#, fuzzy
#| msgid "View all galleries"
msgid "New gallery title"
msgstr "Afficher toutes les galeries"
#: photologue_custom/forms.py:46
msgid "New gallery event start date"
msgstr ""
#: photologue_custom/forms.py:51
msgid "New gallery event end date"
msgstr ""
#: photologue_custom/forms.py:57
#, fuzzy
#| msgid "gallery uploads"
msgid "New gallery tags"
msgstr "gallery uploads"
#: photologue_custom/forms.py:59
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
#: photologue_custom/forms.py:76
#: photologue_custom/templates/photologue/upload.html:6
#: photologue_custom/templates/photologue/upload.html:73
msgid "Upload"
msgstr "Télécharger"
#: photologue_custom/forms.py:82
msgid "A gallery with that title already exists."
msgstr "Une galerie portant ce nom existe déjà."
#: photologue_custom/forms.py:91
msgid "Select an existing gallery, or enter a title for a new gallery."
msgstr ""
"Sélectionner une galerie existante ou entrer un titre pour une nouvelle "
"galerie."
#: photologue_custom/models.py:23
msgid "start date"
msgstr ""
#: photologue_custom/models.py:28
msgid "end date"
msgstr ""
#: photologue_custom/models.py:56
msgid "license"
msgstr ""
#: photologue_custom/templates/photologue/gallery_archive.html:7
#: photologue_custom/templates/photologue/gallery_archive.html:12
#: photologue/templates/photologue/gallery_archive.html:7
#: photologue/templates/photologue/gallery_archive.html:12
msgid "Latest photo galleries"
msgstr "Dernières galeries de photos"
#: photologue_custom/templates/photologue/gallery_archive.html:18
#: photologue/templates/photologue/gallery_archive.html:18
msgid "Filter by year"
msgstr "Filtrer par année"
#: photologue_custom/templates/photologue/gallery_archive.html:35
#: photologue/templates/photologue/gallery_archive.html:35
msgid "No galleries were found"
msgstr "Aucune galerie trouvée"
#: photologue_custom/templates/photologue/gallery_archive_year.html:7
#: photologue_custom/templates/photologue/gallery_archive_year.html:12
#: photologue/templates/photologue/gallery_archive_year.html:7
#: photologue/templates/photologue/gallery_archive_year.html:12
#, python-format
msgid "Galleries for %(show_year)s"
msgstr "Galeries de %(show_year)s"
#: photologue_custom/templates/photologue/gallery_archive_year.html:17
#: photologue/templates/photologue/gallery_archive_year.html:17
msgid "View all galleries"
msgstr "Afficher toutes les galeries"
#: photologue_custom/templates/photologue/gallery_archive_year.html:29
#: photologue/templates/photologue/gallery_archive_year.html:29
msgid "No galleries were found."
msgstr "Aucune galerie trouvée."
#: photologue_custom/templates/photologue/gallery_detail.html:41
#: photologue/templates/photologue/gallery_detail.html:41
msgid "to"
msgstr ""
msgstr "au"
#: photologue_custom/templates/photologue/gallery_detail.html:57
#, fuzzy
#| msgid "All photos"
#: photologue/templates/photologue/gallery_detail.html:57
msgid "All pictures"
msgstr "Toutes les photos"
#: photologue_custom/templates/photologue/gallery_detail.html:78
#, fuzzy
#| msgid "View all galleries"
#: photologue/templates/photologue/gallery_detail.html:78
msgid "Download all gallery"
msgstr "Afficher toutes les galeries"
msgstr "Télécharger toute la galerie"
#: photologue_custom/templates/photologue/photo_detail.html:13
#: photologue/templates/photologue/photo_detail.html:13
msgid "Published"
msgstr "Publiée le"
#: photologue_custom/templates/photologue/photo_detail.html:25
#: photologue/templates/photologue/photo_detail.html:25
msgid "This photo is found in the following galleries"
msgstr "Cette photo se trouve dans les galeries suivantes"
#: photologue_custom/templates/photologue/upload.html:78
#: photologue/templates/photologue/upload.html:78
msgid "Drag and drop photos here"
msgstr ""
msgstr "Glissez et déposez les photos ici"
#: photologue_custom/templates/photologue/upload.html:82
#: photologue/templates/photologue/upload.html:82
msgid "Owner will be"
msgstr ""
msgstr "Le propriétaire sera"

View file

@ -0,0 +1,71 @@
import hashlib
from django.core.management.base import BaseCommand, CommandError
from photologue.models import Gallery
class Command(BaseCommand):
help = 'List all duplicate for chosen galleries'
def add_arguments(self, parser):
parser.add_argument(
'--slugs', nargs='+', help='Try to find duplicate in the selected galleries', default=[])
parser.add_argument('-a', '--all', action='store_true',
help='Try to find duplicate in all galleries, overide any slugs given')
parser.add_argument('-d', '--delete', action='store_true')
def handle(self, *args, **options):
# Collect all required galleries
if options['all']:
galleries = Gallery.objects.all()
else:
galleries = []
for slug in options['slugs']:
gallery_query = Gallery.objects.filter(slug=slug)
if not gallery_query:
raise CommandError(f"Slug {slug} does not correspond to a "
"gallery in the database.")
galleries += gallery_query
# Find duplicates in all galleries
for gallery in galleries:
duplicates = find_duplicate(gallery)
self.stdout.write(f"Gallery {gallery.slug}:")
for original, copies in duplicates:
self.stdout.write(f" {original.slug} is duplicated:", ending='')
for copy in copies:
self.stdout.write(f" {copy.slug}")
# Delete them if --delete
if options['delete']:
self.stdout.write(
' Deleting duplicate in {} :'.format(gallery.slug))
for (_original, copies) in duplicates:
for copy in copies:
self.stdout.write(
' Deleting {}...'.format(copy.slug))
copy.delete()
def find_duplicate(gallery):
# Dict of all already checked photos
non_duplicate = {}
# Dict of all found duplicate {h0 : (original:[duplicates])}
duplicate = {}
for photo in gallery.photos.all():
with photo.image.open("rb") as f:
h0 = hashlib.sha256(f.read()).digest()
if h0 not in non_duplicate:
# Photo is not a duplicate
non_duplicate[h0] = photo
elif h0 in duplicate:
if len(photo.slug) > len(duplicate[h0][0].slug):
duplicate[h0][1] += [photo]
else:
duplicate[h0][1] += [duplicate[h0][0]]
duplicate[h0][0] = photo
else:
duplicate[h0] = [non_duplicate[h0], [photo]]
# Return only value because hash aren't usefull
return duplicate.values()

View file

@ -0,0 +1,37 @@
import os
from pathlib import Path
from django.conf import settings
from django.core.management.base import BaseCommand
from photologue.models import Gallery
class Command(BaseCommand):
help = 'Rename uploaded media file to match gallery and photo names'
def add_arguments(self, parser):
parser.add_argument('--apply', action='store_true')
def handle(self, *args, **options):
media_dir = Path(settings.MEDIA_ROOT)
for gallery in Gallery.objects.all():
# Create gallery directory
gallery_year = str(gallery.date_start.year)
gallery_dir = Path('photos') / gallery_year / gallery.slug
gallery_path = media_dir / gallery_dir
if not gallery_path.exists():
self.stdout.write(f"Creating {gallery_dir}")
if options["apply"]:
gallery_path.mkdir(parents=True)
# Move photos in gallery folder
for photo in gallery.photos.all():
photo_name = str(gallery_dir / photo.image.name.split("/")[-1])
if photo.image.name == photo_name:
continue
self.stdout.write(f" Moving {photo.image.name} -> {photo_name}")
if options["apply"]:
if not (media_dir / photo_name).exists():
os.rename(photo.image.path, media_dir / photo_name)
photo.image.name = photo_name
photo.save()

View file

@ -1,12 +1,12 @@
# Generated by Django 3.2.11 on 2022-01-30 08:32
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import taggit.managers
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.db import migrations, models
from django.template.defaultfilters import slugify
numens = User.objects.get(username="Numens").id

View file

@ -0,0 +1,108 @@
.lg-outer .lg-thumb-outer {
background-color: #0d0a0a;
width: 100%;
max-height: 350px;
overflow: hidden;
float: left;
}
.lg-outer .lg-thumb-outer.lg-grab .lg-thumb-item {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: -o-grab;
cursor: -ms-grab;
cursor: grab;
}
.lg-outer .lg-thumb-outer.lg-grabbing .lg-thumb-item {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: -o-grabbing;
cursor: -ms-grabbing;
cursor: grabbing;
}
.lg-outer .lg-thumb-outer.lg-dragging .lg-thumb {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}
.lg-outer .lg-thumb-outer.lg-rebuilding-thumbnails .lg-thumb {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}
.lg-outer .lg-thumb-outer.lg-thumb-align-middle {
text-align: center;
}
.lg-outer .lg-thumb-outer.lg-thumb-align-left {
text-align: left;
}
.lg-outer .lg-thumb-outer.lg-thumb-align-right {
text-align: right;
}
.lg-outer.lg-single-item .lg-thumb-outer {
display: none;
}
.lg-outer .lg-thumb {
padding: 5px 0;
height: 100%;
margin-bottom: -5px;
display: inline-block;
vertical-align: middle;
}
@media (min-width: 768px) {
.lg-outer .lg-thumb {
padding: 10px 0;
}
}
.lg-outer .lg-thumb-item {
cursor: pointer;
float: left;
overflow: hidden;
height: 100%;
border-radius: 2px;
margin-bottom: 5px;
will-change: border-color;
}
@media (min-width: 768px) {
.lg-outer .lg-thumb-item {
border-radius: 4px;
border: 2px solid #fff;
-webkit-transition: border-color 0.25s ease;
-o-transition: border-color 0.25s ease;
transition: border-color 0.25s ease;
}
}
.lg-outer .lg-thumb-item.active, .lg-outer .lg-thumb-item:hover {
border-color: #a90707;
}
.lg-outer .lg-thumb-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.lg-outer.lg-can-toggle .lg-item {
padding-bottom: 0;
}
.lg-outer .lg-toggle-thumb:after {
content: '\e1ff';
}
.lg-outer.lg-animate-thumb .lg-thumb {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}

View file

@ -0,0 +1,84 @@
.lg-outer.lg-css3.lg-zoom-dragging .lg-item.lg-complete.lg-zoomable .lg-img-wrap,
.lg-outer.lg-css3.lg-zoom-dragging .lg-item.lg-complete.lg-zoomable .lg-image {
-webkit-transition-duration: 0ms !important;
transition-duration: 0ms !important;
}
.lg-outer.lg-use-transition-for-zoom .lg-item.lg-complete.lg-zoomable .lg-img-wrap {
will-change: transform;
-webkit-transition: -webkit-transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s;
-moz-transition: -moz-transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s;
-o-transition: -o-transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s;
transition: transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s;
}
.lg-outer.lg-use-transition-for-zoom.lg-zoom-drag-transition .lg-item.lg-complete.lg-zoomable .lg-img-wrap {
will-change: transform;
-webkit-transition: -webkit-transform 0.8s cubic-bezier(0, 0, 0.25, 1) 0s;
-moz-transition: -moz-transform 0.8s cubic-bezier(0, 0, 0.25, 1) 0s;
-o-transition: -o-transform 0.8s cubic-bezier(0, 0, 0.25, 1) 0s;
transition: transform 0.8s cubic-bezier(0, 0, 0.25, 1) 0s;
}
.lg-outer .lg-item.lg-complete.lg-zoomable .lg-img-wrap {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
}
.lg-outer .lg-item.lg-complete.lg-zoomable .lg-image,
.lg-outer .lg-item.lg-complete.lg-zoomable .lg-dummy-img {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
will-change: opacity, transform;
-webkit-transition: -webkit-transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s, opacity 0.15s !important;
-moz-transition: -moz-transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s, opacity 0.15s !important;
-o-transition: -o-transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s, opacity 0.15s !important;
transition: transform 0.5s cubic-bezier(0.12, 0.415, 0.01, 1.19) 0s, opacity 0.15s !important;
-webkit-transform-origin: 0 0;
-moz-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
}
.lg-icon.lg-zoom-in:after {
content: '\e311';
}
.lg-icon.lg-actual-size {
font-size: 20px;
}
.lg-icon.lg-actual-size:after {
content: '\e033';
}
.lg-icon.lg-zoom-out {
opacity: 0.5;
pointer-events: none;
}
.lg-icon.lg-zoom-out:after {
content: '\e312';
}
.lg-zoomed .lg-icon.lg-zoom-out {
opacity: 1;
pointer-events: auto;
}
.lg-outer[data-lg-slide-type='video'] .lg-zoom-in,
.lg-outer[data-lg-slide-type='video'] .lg-actual-size,
.lg-outer[data-lg-slide-type='video'] .lg-zoom-out, .lg-outer[data-lg-slide-type='iframe'] .lg-zoom-in,
.lg-outer[data-lg-slide-type='iframe'] .lg-actual-size,
.lg-outer[data-lg-slide-type='iframe'] .lg-zoom-out, .lg-outer.lg-first-slide-loading .lg-zoom-in,
.lg-outer.lg-first-slide-loading .lg-actual-size,
.lg-outer.lg-first-slide-loading .lg-zoom-out {
opacity: 0.75;
pointer-events: none;
}

View file

@ -0,0 +1,721 @@
@font-face {
font-family: 'lg';
src: url("../fonts/lg.woff2?io9a6k") format("woff2"), url("../fonts/lg.ttf?io9a6k") format("truetype"), url("../fonts/lg.woff?io9a6k") format("woff"), url("../fonts/lg.svg?io9a6k#lg") format("svg");
font-weight: normal;
font-style: normal;
font-display: block;
}
.lg-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'lg' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.lg-container {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
.lg-next,
.lg-prev {
background-color: rgba(0, 0, 0, 0.45);
border-radius: 2px;
color: #999;
cursor: pointer;
display: block;
font-size: 22px;
margin-top: -10px;
padding: 8px 10px 9px;
position: absolute;
top: 50%;
z-index: 1080;
outline: none;
border: none;
}
.lg-next.disabled,
.lg-prev.disabled {
opacity: 0 !important;
cursor: default;
}
.lg-next:hover:not(.disabled),
.lg-prev:hover:not(.disabled) {
color: #fff;
}
.lg-single-item .lg-next, .lg-single-item
.lg-prev {
display: none;
}
.lg-next {
right: 20px;
}
.lg-next:before {
content: '\e095';
}
.lg-prev {
left: 20px;
}
.lg-prev:after {
content: '\e094';
}
@-webkit-keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@-moz-keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@-ms-keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@keyframes lg-right-end {
0% {
left: 0;
}
50% {
left: -30px;
}
100% {
left: 0;
}
}
@-webkit-keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
@-moz-keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
@-ms-keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
@keyframes lg-left-end {
0% {
left: 0;
}
50% {
left: 30px;
}
100% {
left: 0;
}
}
.lg-outer.lg-right-end .lg-object {
-webkit-animation: lg-right-end 0.3s;
-o-animation: lg-right-end 0.3s;
animation: lg-right-end 0.3s;
position: relative;
}
.lg-outer.lg-left-end .lg-object {
-webkit-animation: lg-left-end 0.3s;
-o-animation: lg-left-end 0.3s;
animation: lg-left-end 0.3s;
position: relative;
}
.lg-toolbar {
z-index: 1082;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.lg-media-overlap .lg-toolbar {
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4));
}
.lg-toolbar .lg-icon {
color: #999;
cursor: pointer;
float: right;
font-size: 24px;
height: 47px;
line-height: 27px;
padding: 10px 0;
text-align: center;
width: 50px;
text-decoration: none !important;
outline: medium none;
will-change: color;
-webkit-transition: color 0.2s linear;
-o-transition: color 0.2s linear;
transition: color 0.2s linear;
background: none;
border: none;
box-shadow: none;
}
.lg-toolbar .lg-icon.lg-icon-18 {
font-size: 18px;
}
.lg-toolbar .lg-icon:hover {
color: #fff;
}
.lg-toolbar .lg-close:after {
content: '\e070';
}
.lg-toolbar .lg-maximize {
font-size: 22px;
}
.lg-toolbar .lg-maximize:after {
content: '\e90a';
}
.lg-toolbar .lg-download:after {
content: '\e0f2';
}
.lg-sub-html {
color: #eee;
font-size: 16px;
padding: 10px 40px;
text-align: center;
z-index: 1080;
opacity: 0;
-webkit-transition: opacity 0.2s ease-out 0s;
-o-transition: opacity 0.2s ease-out 0s;
transition: opacity 0.2s ease-out 0s;
}
.lg-sub-html h4 {
margin: 0;
font-size: 13px;
font-weight: bold;
}
.lg-sub-html p {
font-size: 12px;
margin: 5px 0 0;
}
.lg-sub-html a {
color: inherit;
}
.lg-sub-html a:hover {
text-decoration: underline;
}
.lg-media-overlap .lg-sub-html {
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.6));
}
.lg-item .lg-sub-html {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
.lg-error-msg {
font-size: 14px;
color: #999;
}
.lg-counter {
color: #999;
display: inline-block;
font-size: 16px;
padding-left: 20px;
padding-top: 12px;
height: 47px;
vertical-align: middle;
}
.lg-closing .lg-toolbar,
.lg-closing .lg-prev,
.lg-closing .lg-next,
.lg-closing .lg-sub-html {
opacity: 0;
-webkit-transition: -webkit-transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
-moz-transition: -moz-transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
-o-transition: -o-transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
transition: transform 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.08 cubic-bezier(0, 0, 0.25, 1) 0s, color 0.08 linear;
}
body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-object {
opacity: 0;
will-change: opacity;
-webkit-transition: opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
-moz-transition: opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
-o-transition: opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
transition: opacity 250ms cubic-bezier(0, 0, 0.25, 1) !important;
}
body:not(.lg-from-hash) .lg-outer.lg-start-zoom .lg-item.lg-complete .lg-object {
opacity: 1;
}
.lg-group:after {
content: '';
display: table;
clear: both;
}
.lg-container {
display: none;
outline: none;
}
.lg-container.lg-show {
display: block;
}
.lg-on {
scroll-behavior: unset;
}
.lg-toolbar,
.lg-prev,
.lg-next,
.lg-pager-outer,
.lg-hide-sub-html .lg-sub-html {
opacity: 0;
will-change: transform, opacity;
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
-moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
-o-transition: -o-transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.25s cubic-bezier(0, 0, 0.25, 1) 0s;
}
.lg-show-in .lg-toolbar,
.lg-show-in .lg-prev,
.lg-show-in .lg-next,
.lg-show-in .lg-pager-outer {
opacity: 1;
}
.lg-show-in.lg-hide-sub-html .lg-sub-html {
opacity: 1;
}
.lg-show-in .lg-hide-items .lg-prev {
opacity: 0;
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
.lg-show-in .lg-hide-items .lg-next {
opacity: 0;
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
.lg-show-in .lg-hide-items .lg-toolbar {
opacity: 0;
-webkit-transform: translate3d(0, -10px, 0);
transform: translate3d(0, -10px, 0);
}
.lg-show-in .lg-hide-items.lg-hide-sub-html .lg-sub-html {
opacity: 0;
-webkit-transform: translate3d(0, 20px, 0);
transform: translate3d(0, 20px, 0);
}
.lg-outer {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 1050;
text-align: left;
opacity: 0.001;
outline: none;
will-change: auto;
overflow: hidden;
-webkit-transition: opacity 0.15s ease 0s;
-o-transition: opacity 0.15s ease 0s;
transition: opacity 0.15s ease 0s;
}
.lg-outer * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.lg-outer.lg-zoom-from-image {
opacity: 1;
}
.lg-outer.lg-visible {
opacity: 1;
}
.lg-outer.lg-css3 .lg-item:not(.lg-start-end-progress).lg-prev-slide, .lg-outer.lg-css3 .lg-item:not(.lg-start-end-progress).lg-next-slide, .lg-outer.lg-css3 .lg-item:not(.lg-start-end-progress).lg-current {
-webkit-transition-duration: inherit !important;
transition-duration: inherit !important;
-webkit-transition-timing-function: inherit !important;
transition-timing-function: inherit !important;
}
.lg-outer.lg-css3.lg-dragging .lg-item.lg-prev-slide, .lg-outer.lg-css3.lg-dragging .lg-item.lg-next-slide, .lg-outer.lg-css3.lg-dragging .lg-item.lg-current {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
opacity: 1;
}
.lg-outer.lg-grab img.lg-object {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: -o-grab;
cursor: -ms-grab;
cursor: grab;
}
.lg-outer.lg-grabbing img.lg-object {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: -o-grabbing;
cursor: -ms-grabbing;
cursor: grabbing;
}
.lg-outer .lg-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.lg-outer .lg-inner {
width: 100%;
position: absolute;
left: 0;
top: 0;
bottom: 0;
-webkit-transition: opacity 0s;
-o-transition: opacity 0s;
transition: opacity 0s;
white-space: nowrap;
}
.lg-outer .lg-item {
will-change: transform, opacity;
display: none !important;
}
.lg-outer .lg-item:not(.lg-start-end-progress) {
background: url("../images/loading.gif") no-repeat scroll center center transparent;
}
.lg-outer.lg-css3 .lg-prev-slide,
.lg-outer.lg-css3 .lg-current,
.lg-outer.lg-css3 .lg-next-slide {
display: inline-block !important;
}
.lg-outer.lg-css .lg-current {
display: inline-block !important;
}
.lg-outer .lg-item,
.lg-outer .lg-img-wrap {
display: inline-block;
text-align: center;
position: absolute;
width: 100%;
height: 100%;
}
.lg-outer .lg-item:before,
.lg-outer .lg-img-wrap:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
}
.lg-outer .lg-img-wrap {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
white-space: nowrap;
font-size: 0;
}
.lg-outer .lg-item.lg-complete {
background-image: none;
}
.lg-outer .lg-item.lg-current {
z-index: 1060;
}
.lg-outer .lg-object {
display: inline-block;
vertical-align: middle;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
position: relative;
}
.lg-outer.lg-show-after-load .lg-item .lg-object,
.lg-outer.lg-show-after-load .lg-item .lg-video-play-button {
opacity: 0;
will-change: opacity;
-webkit-transition: opacity 0.15s ease 0s;
-o-transition: opacity 0.15s ease 0s;
transition: opacity 0.15s ease 0s;
}
.lg-outer.lg-show-after-load .lg-item.lg-zoom-from-image .lg-object,
.lg-outer.lg-show-after-load .lg-item.lg-zoom-from-image .lg-video-play-button {
opacity: 1;
}
.lg-outer.lg-show-after-load .lg-item.lg-complete .lg-object,
.lg-outer.lg-show-after-load .lg-item.lg-complete .lg-video-play-button {
opacity: 1;
}
.lg-outer .lg-empty-html.lg-sub-html,
.lg-outer .lg-empty-html .lg-sub-html {
display: none;
}
.lg-outer.lg-hide-download .lg-download {
opacity: 0.75;
pointer-events: none;
}
.lg-outer .lg-first-slide .lg-dummy-img {
position: absolute;
top: 50%;
left: 50%;
}
.lg-outer.lg-components-open:not(.lg-zoomed) .lg-components {
-webkit-transform: translate3d(0, 0%, 0);
transform: translate3d(0, 0%, 0);
opacity: 1;
}
.lg-outer.lg-components-open:not(.lg-zoomed) .lg-sub-html {
opacity: 1;
transition: opacity 0.2s ease-out 0.15s;
}
.lg-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1040;
background-color: #000;
opacity: 0;
will-change: auto;
-webkit-transition: opacity 333ms ease-in 0s;
-o-transition: opacity 333ms ease-in 0s;
transition: opacity 333ms ease-in 0s;
}
.lg-backdrop.in {
opacity: 1;
}
.lg-css3.lg-no-trans .lg-prev-slide,
.lg-css3.lg-no-trans .lg-next-slide,
.lg-css3.lg-no-trans .lg-current {
-webkit-transition: none 0s ease 0s !important;
-moz-transition: none 0s ease 0s !important;
-o-transition: none 0s ease 0s !important;
transition: none 0s ease 0s !important;
}
.lg-css3.lg-use-css3 .lg-item {
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
}
.lg-css3.lg-fade .lg-item {
opacity: 0;
}
.lg-css3.lg-fade .lg-item.lg-current {
opacity: 1;
}
.lg-css3.lg-fade .lg-item.lg-prev-slide, .lg-css3.lg-fade .lg-item.lg-next-slide, .lg-css3.lg-fade .lg-item.lg-current {
-webkit-transition: opacity 0.1s ease 0s;
-moz-transition: opacity 0.1s ease 0s;
-o-transition: opacity 0.1s ease 0s;
transition: opacity 0.1s ease 0s;
}
.lg-css3.lg-use-css3 .lg-item.lg-start-progress {
-webkit-transition: -webkit-transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
-moz-transition: -moz-transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
-o-transition: -o-transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
transition: transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s;
}
.lg-css3.lg-use-css3 .lg-item.lg-start-end-progress {
-webkit-transition: -webkit-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
-moz-transition: -moz-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
-o-transition: -o-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
transition: transform 1s cubic-bezier(0, 0, 0.25, 1) 0s;
}
.lg-css3.lg-slide.lg-use-css3 .lg-item {
opacity: 0;
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
opacity: 1;
}
.lg-css3.lg-slide.lg-use-css3 .lg-item.lg-prev-slide, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-next-slide, .lg-css3.lg-slide.lg-use-css3 .lg-item.lg-current {
-webkit-transition: -webkit-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
-moz-transition: -moz-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
-o-transition: -o-transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
transition: transform 1s cubic-bezier(0, 0, 0.25, 1) 0s, opacity 0.1s ease 0s;
}
.lg-container {
display: none;
}
.lg-container.lg-show {
display: block;
}
.lg-container.lg-dragging-vertical .lg-backdrop {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
}
.lg-container.lg-dragging-vertical .lg-css3 .lg-item.lg-current {
-webkit-transition-duration: 0s !important;
transition-duration: 0s !important;
opacity: 1;
}
.lg-inline .lg-backdrop,
.lg-inline .lg-outer {
position: absolute;
}
.lg-inline .lg-backdrop {
z-index: 1;
}
.lg-inline .lg-outer {
z-index: 2;
}
.lg-inline .lg-maximize:after {
content: '\e909';
}
.lg-components {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
will-change: transform;
-webkit-transition: -webkit-transform 0.35s ease-out 0s;
-moz-transition: -moz-transform 0.35s ease-out 0s;
-o-transition: -o-transform 0.35s ease-out 0s;
transition: transform 0.35s ease-out 0s;
z-index: 1080;
position: absolute;
bottom: 0;
right: 0;
left: 0;
}

View file

@ -0,0 +1,54 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
<json>
<![CDATA[
{
"fontFamily": "lg",
"majorVersion": 2,
"minorVersion": 0,
"fontURL": "",
"copyright": "",
"license": "",
"licenseURL": "",
"description": "Font generated by IcoMoon.",
"version": "Version 2.0",
"fontId": "lg",
"psName": "lg",
"subFamily": "Regular",
"fullName": "lg"
}
]]>
</json>
</metadata>
<defs>
<font id="lg" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe01a;" glyph-name="pause_circle_outline" data-tags="pause_circle_outline" d="M554 256.667v340h86v-340h-86zM512 84.667q140 0 241 101t101 241-101 241-241 101-241-101-101-241 101-241 241-101zM512 852.667q176 0 301-125t125-301-125-301-301-125-301 125-125 301 125 301 301 125zM384 256.667v340h86v-340h-86z" />
<glyph unicode="&#xe01d;" glyph-name="play_circle_outline" data-tags="play_circle_outline" d="M512 84.667q140 0 241 101t101 241-101 241-241 101-241-101-101-241 101-241 241-101zM512 852.667q176 0 301-125t125-301-125-301-301-125-301 125-125 301 125 301 301 125zM426 234.667v384l256-192z" />
<glyph unicode="&#xe033;" glyph-name="stack-2" data-tags="stack-2" d="M384 853.334h426.667q53 0 90.5-37.5t37.5-90.5v-426.667q0-53-37.5-90.5t-90.5-37.5h-426.667q-53 0-90.5 37.5t-37.5 90.5v426.667q0 53 37.5 90.5t90.5 37.5zM170.667 675.334v-547.333q0-17.667 12.5-30.167t30.167-12.5h547.333q-13.333-37.667-46.333-61.5t-74.333-23.833h-426.667q-53 0-90.5 37.5t-37.5 90.5v426.667q0 41.333 23.833 74.333t61.5 46.333zM810.667 768h-426.667q-17.667 0-30.167-12.5t-12.5-30.167v-426.667q0-17.667 12.5-30.167t30.167-12.5h426.667q17.667 0 30.167 12.5t12.5 30.167v426.667q0 17.667-12.5 30.167t-30.167 12.5z" />
<glyph unicode="&#xe070;" glyph-name="clear" data-tags="clear" d="M810 664.667l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
<glyph unicode="&#xe094;" glyph-name="arrow-left" data-tags="arrow-left" d="M426.667 768q17.667 0 30.167-12.5t12.5-30.167q0-18-12.667-30.333l-225.667-225.667h665q17.667 0 30.167-12.5t12.5-30.167-12.5-30.167-30.167-12.5h-665l225.667-225.667q12.667-12.333 12.667-30.333 0-17.667-12.5-30.167t-30.167-12.5q-18 0-30.333 12.333l-298.667 298.667q-12.333 13-12.333 30.333t12.333 30.333l298.667 298.667q12.667 12.333 30.333 12.333z" />
<glyph unicode="&#xe095;" glyph-name="arrow-right" data-tags="arrow-right" d="M597.333 768q18 0 30.333-12.333l298.667-298.667q12.333-12.333 12.333-30.333t-12.333-30.333l-298.667-298.667q-12.333-12.333-30.333-12.333-18.333 0-30.5 12.167t-12.167 30.5q0 18 12.333 30.333l226 225.667h-665q-17.667 0-30.167 12.5t-12.5 30.167 12.5 30.167 30.167 12.5h665l-226 225.667q-12.333 12.333-12.333 30.333 0 18.333 12.167 30.5t30.5 12.167z" />
<glyph unicode="&#xe0f2;" glyph-name="vertical_align_bottom" data-tags="vertical_align_bottom" d="M170 128.667h684v-86h-684v86zM682 384.667l-170-172-170 172h128v426h84v-426h128z" />
<glyph unicode="&#xe1ff;" glyph-name="apps" data-tags="apps" d="M682 84.667v172h172v-172h-172zM682 340.667v172h172v-172h-172zM426 596.667v172h172v-172h-172zM682 768.667h172v-172h-172v172zM426 340.667v172h172v-172h-172zM170 340.667v172h172v-172h-172zM170 84.667v172h172v-172h-172zM426 84.667v172h172v-172h-172zM170 596.667v172h172v-172h-172z" />
<glyph unicode="&#xe20c;" glyph-name="fullscreen" data-tags="fullscreen" d="M598 724.667h212v-212h-84v128h-128v84zM726 212.667v128h84v-212h-212v84h128zM214 512.667v212h212v-84h-128v-128h-84zM298 340.667v-128h128v-84h-212v212h84z" />
<glyph unicode="&#xe20d;" glyph-name="fullscreen_exit" data-tags="fullscreen_exit" d="M682 596.667h128v-84h-212v212h84v-128zM598 128.667v212h212v-84h-128v-128h-84zM342 596.667v128h84v-212h-212v84h128zM214 256.667v84h212v-212h-84v128h-128z" />
<glyph unicode="&#xe311;" glyph-name="zoom_in" data-tags="zoom_in" d="M512 512.667h-86v-86h-42v86h-86v42h86v86h42v-86h86v-42zM406 340.667q80 0 136 56t56 136-56 136-136 56-136-56-56-136 56-136 136-56zM662 340.667l212-212-64-64-212 212v34l-12 12q-76-66-180-66-116 0-197 80t-81 196 81 197 197 81 196-81 80-197q0-104-66-180l12-12h34z" />
<glyph unicode="&#xe312;" glyph-name="zoom_out" data-tags="zoom_out" d="M298 554.667h214v-42h-214v42zM406 340.667q80 0 136 56t56 136-56 136-136 56-136-56-56-136 56-136 136-56zM662 340.667l212-212-64-64-212 212v34l-12 12q-76-66-180-66-116 0-197 80t-81 196 81 197 197 81 196-81 80-197q0-104-66-180l12-12h34z" />
<glyph unicode="&#xe80d;" glyph-name="share" data-tags="share" d="M768 252.667c68 0 124-56 124-124s-56-126-124-126-124 58-124 126c0 10 0 20 2 28l-302 176c-24-22-54-34-88-34-70 0-128 58-128 128s58 128 128 128c34 0 64-12 88-34l300 174c-2 10-4 20-4 30 0 70 58 128 128 128s128-58 128-128-58-128-128-128c-34 0-64 14-88 36l-300-176c2-10 4-20 4-30s-2-20-4-30l304-176c22 20 52 32 84 32z" />
<glyph unicode="&#xe900;" glyph-name="rotate_left" data-tags="rotate_left" d="M554 764.667q126-16 213-112t87-226-87-226-213-112v86q92 16 153 87t61 165-61 165-153 87v-166l-194 190 194 194v-132zM302 156.667l62 62q46-34 106-44v-86q-96 12-168 68zM260 384.667q10-58 42-106l-60-60q-56 74-68 166h86zM304 574.667q-36-52-44-106h-86q12 90 70 166z" />
<glyph unicode="&#xe901;" glyph-name="rotate_right" data-tags="rotate_right" d="M720 278.667q34 46 44 106h86q-12-92-68-166zM554 174.667q60 10 106 44l62-62q-72-56-168-68v86zM850 468.667h-86q-10 60-44 106l62 60q58-72 68-166zM664 702.667l-194-190v166q-92-16-153-87t-61-165 61-165 153-87v-86q-126 16-213 112t-87 226 87 226 213 112v132z" />
<glyph unicode="&#xe902;" glyph-name="swap_horiz" data-tags="swap_horiz" d="M896 554.667l-170-170v128h-300v84h300v128zM298 468.667v-128h300v-84h-300v-128l-170 170z" />
<glyph unicode="&#xe903;" glyph-name="swap_vert" data-tags="swap_vert" d="M384 810.667l170-170h-128v-300h-84v300h-128zM682 212.667h128l-170-170-170 170h128v300h84v-300z" />
<glyph unicode="&#xe904;" glyph-name="facebook-with-circle" data-tags="facebook-with-circle" d="M512 952.32c-271.462 0-491.52-220.058-491.52-491.52s220.058-491.52 491.52-491.52 491.52 220.058 491.52 491.52-220.058 491.52-491.52 491.52zM628.429 612.659h-73.882c-8.755 0-18.483-11.52-18.483-26.829v-53.35h92.416l-13.978-76.083h-78.438v-228.403h-87.194v228.403h-79.104v76.083h79.104v44.749c0 64.205 44.544 116.378 105.677 116.378h73.882v-80.947z" />
<glyph unicode="&#xe905;" glyph-name="google-with-circle" data-tags="google+-with-circle" d="M512 952.32c-271.462 0-491.52-220.058-491.52-491.52s220.058-491.52 491.52-491.52 491.52 220.058 491.52 491.52-220.058 491.52-491.52 491.52zM483.686 249.805c-30.874-15.002-64.102-16.589-76.954-16.589-2.458 0-3.84 0-3.84 0s-1.178 0-2.765 0c-20.070 0-119.962 4.608-119.962 95.59 0 89.395 108.8 96.41 142.131 96.41h0.87c-19.251 25.702-15.258 51.61-15.258 51.61-1.69-0.102-4.147-0.205-7.168-0.205-12.544 0-36.762 1.997-57.549 15.411-25.498 16.384-38.4 44.288-38.4 82.893 0 109.107 119.142 113.51 120.32 113.613h118.989v-2.611c0-13.312-23.91-15.923-40.192-18.125-5.53-0.819-16.64-1.894-19.763-3.482 30.157-16.128 35.021-41.421 35.021-79.104 0-42.906-16.794-65.587-34.611-81.51-11.059-9.882-19.712-17.613-19.712-28.006 0-10.189 11.878-20.582 25.702-32.717 22.579-19.917 53.555-47.002 53.555-92.723 0-47.258-20.326-81.050-60.416-100.454zM742.4 460.8h-76.8v-76.8h-51.2v76.8h-76.8v51.2h76.8v76.8h51.2v-76.8h76.8v-51.2zM421.018 401.92c-2.662 0-5.325-0.102-8.038-0.307-22.733-1.69-43.725-10.189-58.88-24.013-15.053-13.619-22.733-30.822-21.658-48.179 2.304-36.403 41.37-57.702 88.832-54.323 46.694 3.379 77.824 30.31 75.571 66.714-2.15 34.202-31.898 60.109-75.827 60.109zM465.766 599.808c-12.39 43.52-32.358 56.422-63.386 56.422-3.328 0-6.707-0.512-9.933-1.382-13.466-3.84-24.166-15.053-30.106-31.744-6.093-16.896-6.451-34.509-1.229-54.579 9.472-35.891 34.97-61.901 60.672-61.901 3.379 0 6.758 0.41 9.933 1.382 28.109 7.885 45.722 50.79 34.048 91.802z" />
<glyph unicode="&#xe906;" glyph-name="pinterest-with-circle" data-tags="pinterest-with-circle" d="M512 952.32c-271.462 0-491.52-220.058-491.52-491.52s220.058-491.52 491.52-491.52 491.52 220.058 491.52 491.52-220.058 491.52-491.52 491.52zM545.638 344.32c-31.539 2.406-44.749 18.022-69.427 32.973-13.568-71.219-30.157-139.52-79.309-175.206-15.206 107.725 22.221 188.518 39.629 274.381-29.645 49.92 3.533 150.323 66.099 125.645 76.954-30.515-66.662-185.6 29.747-205.005 100.659-20.173 141.773 174.694 79.36 237.978-90.214 91.494-262.502 2.099-241.306-128.87 5.12-32 38.246-41.728 13.21-85.914-57.702 12.8-74.957 58.317-72.704 118.989 3.533 99.328 89.242 168.909 175.155 178.483 108.698 12.083 210.688-39.885 224.819-142.182 15.821-115.405-49.101-240.282-165.274-231.27z" />
<glyph unicode="&#xe907;" glyph-name="twitter-with-circle" data-tags="twitter-with-circle" d="M512 952.32c-271.462 0-491.52-220.058-491.52-491.52s220.058-491.52 491.52-491.52 491.52 220.058 491.52 491.52-220.058 491.52-491.52 491.52zM711.936 549.683c0.205-4.198 0.256-8.397 0.256-12.493 0-128-97.331-275.507-275.405-275.507-54.682 0-105.574 15.974-148.378 43.52 7.526-0.922 15.258-1.28 23.091-1.28 45.363 0 87.091 15.411 120.218 41.421-42.342 0.819-78.080 28.774-90.419 67.174 5.888-1.075 11.93-1.69 18.176-1.69 8.806 0 17.408 1.178 25.498 3.379-44.288 8.909-77.67 48.026-77.67 94.925v1.178c13.056-7.219 28.006-11.622 43.878-12.134-26.010 17.408-43.059 47.002-43.059 80.64 0 17.715 4.762 34.406 13.107 48.691 47.77-58.573 119.040-97.075 199.526-101.222-1.69 7.117-2.509 14.49-2.509 22.118 0 53.402 43.315 96.819 96.819 96.819 27.802 0 52.992-11.776 70.656-30.618 22.067 4.403 42.752 12.39 61.44 23.501-7.219-22.579-22.528-41.574-42.547-53.606 19.61 2.406 38.246 7.578 55.603 15.309-12.954-19.405-29.389-36.506-48.282-50.125z" />
<glyph unicode="&#xe908;" glyph-name="message-circle" data-tags="message-circle" d="M938.667 448.128v21.205c0 0.725-0.043 1.621-0.085 2.475-5.803 99.755-47.488 190.336-112.725 258.176-68.352 71.125-162.731 117.419-268.843 123.264-0.683 0.043-1.536 0.085-2.347 0.085h-20.864c-59.947 0.683-122.965-13.227-181.931-43.008-52.181-26.496-97.749-63.488-133.931-108.16-56.405-69.717-89.899-158.080-89.941-253.696-0.597-54.4 10.795-111.36 35.157-165.419l-75.605-226.859c-2.816-8.363-3.072-17.835 0-26.965 7.467-22.357 31.616-34.432 53.973-26.965l226.731 75.563c49.493-22.485 105.984-35.243 165.376-35.115 58.539 0.384 115.797 13.141 168.149 36.949 81.579 37.163 151.040 101.248 193.749 186.667 27.477 53.291 43.307 115.84 43.136 181.803zM853.333 447.872c0.128-52.267-12.459-101.333-33.664-142.464-34.176-68.352-88.832-118.827-153.259-148.139-41.387-18.859-86.827-28.971-133.376-29.269-52.096-0.128-101.163 12.459-142.293 33.664-10.624 5.504-22.528 6.059-33.067 2.56l-162.261-54.101 54.101 162.261c3.755 11.221 2.56 22.912-2.389 32.725-23.552 46.677-34.304 96.171-33.792 142.421 0.043 76.331 26.411 145.92 70.955 200.917 28.629 35.371 64.768 64.725 106.24 85.76 46.592 23.552 96.085 34.304 142.336 33.792h19.456c83.712-4.565 158.037-41.003 212.011-97.152 51.285-53.376 84.139-124.416 89.003-202.795z" />
<glyph unicode="&#xe909;" glyph-name="maximize-2" data-tags="maximize-2" d="M793.003 768l-225.835-225.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l225.835 225.835v-153.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 5.803-1.152 11.307-3.243 16.341s-5.163 9.728-9.216 13.781c-0.043 0.043-0.043 0.043-0.085 0.085-3.925 3.925-8.619 7.083-13.781 9.216-5.035 2.091-10.539 3.243-16.341 3.243h-256c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667zM230.997 85.334l225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835v153.003c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-256c0-23.552 19.115-42.667 42.667-42.667h256c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667z" />
<glyph unicode="&#xe90a;" glyph-name="minimize-2" data-tags="minimize-2" d="M700.331 554.667l225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835v153.003c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-256c0-5.803 1.152-11.307 3.243-16.341s5.163-9.728 9.216-13.781c0.043-0.043 0.043-0.043 0.085-0.085 3.925-3.925 8.619-7.083 13.781-9.216 5.035-2.091 10.539-3.243 16.341-3.243h256c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM158.165 12.502l225.835 225.835v-153.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 5.803-1.152 11.307-3.243 16.341s-5.163 9.728-9.216 13.781c-0.043 0.043-0.043 0.043-0.085 0.085-4.096 4.053-8.789 7.125-13.781 9.216-5.035 2.091-10.539 3.243-16.341 3.243h-256c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h153.003l-225.835-225.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,26 @@
/*
* Custom LightGallery plugin to add some buttons for administration
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
class lgAdmin {
constructor(instance, $LG) {
this.core = instance;
this.$LG = $LG;
return this;
}
init() {
this.core.$toolbar.append("<a href=\"#\" id=\"lg-admin\" class=\"lg-icon\">\uE033</a>");
this.core.LGel.on("lgAfterSlide.admin", this.onAfterSlide.bind(this));
}
onAfterSlide(event) {
const photoId = this.core.galleryItems[event.detail.index].slideName;
document.getElementById("lg-admin").href = `https://photos.crans.org/admin/photologue/photo/${photoId}/change/`;
}
// Admin must have destroy prototype
destroy() { }
}

View file

@ -0,0 +1,8 @@
/**
* lightgallery | 2.2.1 | September 4th 2021
* http://www.lightgalleryjs.com/
* Copyright (c) 2020 Sachin Neravath;
* @license GPLv3
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).lgHash=e()}(this,(function(){"use strict";var t=function(){return(t=Object.assign||function(t){for(var e,i=1,o=arguments.length;i<o;i++)for(var s in e=arguments[i])Object.prototype.hasOwnProperty.call(e,s)&&(t[s]=e[s]);return t}).apply(this,arguments)},e="lgAfterSlide",i="lgAfterClose",o={hash:!0,galleryId:"1",customSlideName:!1};return function(){function s(e,i){return this.core=e,this.$LG=i,this.settings=t(t({},o),this.core.settings),this}return s.prototype.init=function(){var t=this;this.settings.hash&&(this.oldHash=window.location.hash,setTimeout((function(){t.buildFromHash()}),100),this.core.LGel.on(e+".hash",this.onAfterSlide.bind(this)),this.core.LGel.on(i+".hash",this.onCloseAfter.bind(this)),this.$LG(window).on("hashchange.lg.hash.global"+this.core.lgId,this.onHashchange.bind(this)))},s.prototype.onAfterSlide=function(t){var e=this.core.galleryItems[t.detail.index].slideName;e=this.settings.customSlideName&&e||t.detail.index,history.replaceState?history.replaceState(null,"",window.location.pathname+window.location.search+"#lg="+this.settings.galleryId+"&slide="+e):window.location.hash="lg="+this.settings.galleryId+"&slide="+e},s.prototype.getIndexFromUrl=function(t){void 0===t&&(t=window.location.hash);var e=t.split("&slide=")[1],i=0;if(this.settings.customSlideName)for(var o=0;o<this.core.galleryItems.length;o++){if(this.core.galleryItems[o].slideName===e){i=o;break}}else i=parseInt(e,10);return isNaN(i)?0:i},s.prototype.buildFromHash=function(){var t=window.location.hash;if(t.indexOf("lg="+this.settings.galleryId)>0){this.$LG(document.body).addClass("lg-from-hash");var e=this.getIndexFromUrl(t);return this.core.openGallery(e),!0}},s.prototype.onCloseAfter=function(){this.oldHash&&this.oldHash.indexOf("lg="+this.settings.galleryId)<0?history.replaceState?history.replaceState(null,"",this.oldHash):window.location.hash=this.oldHash:history.replaceState?history.replaceState(null,document.title,window.location.pathname+window.location.search):window.location.hash=""},s.prototype.onHashchange=function(){if(this.core.lgOpened){var t=window.location.hash,e=this.getIndexFromUrl(t);t.indexOf("lg="+this.settings.galleryId)>-1?this.core.slide(e,!1,!1):this.core.lGalleryOn&&this.core.closeGallery()}},s.prototype.closeGallery=function(){this.settings.hash&&this.$LG(document.body).removeClass("lg-from-hash")},s.prototype.destroy=function(){this.core.LGel.off(".lg.hash"),this.core.LGel.off(".hash"),this.$LG(window).off("hashchange.lg.hash.global"+this.core.lgId)},s}()}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

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

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block title %}{% trans "Latest photo galleries" %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<h1>{% trans "Latest photo galleries" %}</h1>
</div>
</div>
<div class="row">
<aside class="col-md-2">
<h5>{% trans "Filter by year" %}</h5>
<div class="list-group">
{% for date in date_list %}
<a class="list-group-item list-group-item-action" href="{% url 'photologue:pl-gallery-archive-year' date.year %}">{{ date|date:"Y" }}</a>
{% endfor %}
</div>
</aside>
<main class="col-md-10">
{% if latest %}
<div class="row mb-2">
{% for gallery in latest|slice:":32" %}
<div class="col-6 col-md-3 mb-2">
{% include "photologue/includes/gallery_sample.html" %}
</div>
{% endfor %}
</div>
{% else %}
<p>{% trans "No galleries were found" %}.</p>
{% endif %}
</main>
</div>
{% endblock %}

View file

@ -0,0 +1,33 @@
{% extends "base.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 %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<h1>{% blocktrans with show_year=year|date:"Y" %}Galleries for {{ show_year }}{% endblocktrans %}</h1>
</div>
</div>
<div class="row">
<aside class="col-md-2">
<a href="{% url 'photologue:pl-gallery-archive' %}">⬅ {% trans "View all galleries" %}</a>
</aside>
<main class="col-md-10">
{% if object_list %}
<div class="row mb-2">
{% for gallery in object_list %}
<div class="col-6 col-md-3 mb-2">
{% include "photologue/includes/gallery_sample.html" %}
</div>
{% endfor %}
</div>
{% else %}
<p>{% trans "No galleries were found." %}</p>
{% endif %}
</main>
</div>
{% endblock %}

View file

@ -0,0 +1,81 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load static i18n %}
{% block title %}{{ gallery.title }}{% endblock %}
{% block extracss %}
<link rel="stylesheet" href="{% static 'lightgallery/css/lightgallery.css' %}" />
<link rel="stylesheet" href="{% static 'lightgallery/css/lg-zoom.css' %}" />
<link rel="stylesheet" href="{% static 'lightgallery/css/lg-thumbnail.css' %}" />
{% endblock %}
{% block extrajs %}
<script src="{% static 'lightgallery/lightgallery.min.js' %}"></script>
<script src="{% static 'lightgallery/plugins/admin/lg-admin.js' %}"></script>
<script src="{% static 'lightgallery/plugins/hash/lg-hash.min.js' %}"></script>
<script src="{% static 'lightgallery/plugins/thumbnail/lg-thumbnail.min.js' %}"></script>
<script src="{% static 'lightgallery/plugins/zoom/lg-zoom.min.js' %}"></script>
<script>
lightGallery(document.getElementById('lightgallery'), {
plugins: [{% if request.user.is_staff %}lgAdmin, {% endif %}lgHash, lgThumbnail, lgZoom],
customSlideName: true,
});
</script>
{% endblock %}
{% block content %}
<h1>
{{ gallery.title }}
{% if request.user.is_staff and perms.photologue.change_gallery %}
<a href="{% url 'admin:photologue_gallery_change' gallery.id %}">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#555" class="bi bi-gear" viewBox="0 0 16 18">
<path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/>
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/>
</svg>
</a>
{% endif %}
</h1>
{% if gallery.date_start %}<p class="text-muted small">{{ gallery.date_start }}{% if gallery.date_end and gallery.date_end != gallery.date_start %} {% trans "to" %} {{ gallery.date_end }}{% endif %}</p>{% endif %}
{% if gallery.tags.all %}
<p class="text-muted">
Tags : {% for tag in gallery.tags.all %}
<a class="badge rounded-pill bg-dark text-decoration-none" href="{% url 'photologue:tag-detail' tag.slug %}">{{ tag }}</a>
{% endfor %}
</p>
{% endif %}
{% if gallery.description %}<p>{{ gallery.description|safe }}</p>{% endif %}
<div class="card">
<div class="card-header pb-0 border-bottom-0">
<ul class="nav nav-tabs">
<li class="nav-item">
{% url 'photologue:pl-gallery' gallery.slug as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
{% trans "All pictures" %}
</a>
</li>
{% for owner in owners %}
<li class="nav-item">
{% url 'photologue:pl-gallery-owner' slug=gallery.slug owner=owner.id as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}">
{% if owner.get_full_name %}{{ owner.get_full_name }}{% else %}{{ owner.username }}{% endif %}
</a>
</li>
{% endfor %}
</ul>
</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 }}" data-slide-name="{{ photo.id }}">
<img src="{{ photo.get_thumbnail_url }}" loading="lazy" class="img-thumbnail p-0" alt="{{ photo.title }}{% if photo.date_taken %} - {{ photo.date_taken|date }} {{ photo.date_taken|time }}{% endif %} - {{ photo.owner.get_full_name }}{% if photo.license %} - {{ photo.license }}{% endif %}">
</a>
{% endfor %}
</div>
<div class="card-footer">
<a href="{% url 'photologue:pl-gallery-download' gallery.slug %}" class="btn btn-secondary btn-sm">{% trans 'Download all gallery' %}</a>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,12 @@
{% load i18n %}
<div class="card text-white bg-dark">
{% for photo in gallery.sample %}
<img src="{{ photo.get_thumbnail_url }}" class="card-img-top" alt="{{ photo.title }}">
{% endfor %}
<div class="card-body">
<h5 class="card-title">{{ gallery.title }}</h5>
{% if gallery.date_start %}<p class="card-text text-muted small mb-0">{{ gallery.date_start }}{% if gallery.date_end and gallery.date_end != gallery.date_start %} - {{ gallery.date_end }}{% endif %}</p>{% endif %}
<a href="{{ gallery.get_absolute_url }}" class="stretched-link"></a>
</div>
</div>

View file

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load photologue_tags i18n %}
{% block title %}{{ object.title }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-12">
<h1>{{ object.title }}</h1>
<p class="text-muted small">{% trans "Published" %} {{ object.date_added }}</p>
</div>
</div>
<div class="row">
<div class="col-md-8">
{% if object.caption %}<p>{{ object.caption|safe }}</p>{% endif %}
<a href="{{ object.image.url }}">
<img src="{{ object.get_display_url }}" class="img-thumbnail" alt="{{ object.title }}">
</a>
</div>
<div class="col-md-4">
{% if object.public_galleries %}
<p>{% trans "This photo is found in the following galleries" %}:</p>
<ul>
{% for gallery in object.public_galleries %}
<li><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{{ object.name }}{% endblock %}
{% block content %}
<h1>Tag : {{ object.name }}</h1>
<div class="row mb-2">
{% for gallery in galleries %}
<div class="col-6 col-md-3 mb-2">
{% include "photologue/includes/gallery_sample.html" %}
</div>
{% endfor %}
</div>
{% endblock %}

View file

@ -0,0 +1,87 @@
{% 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>
// When user drags files, register them in the file field
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;
}
// When user selects an existing gallery, disable new gallery fields
const gallerySelect = document.getElementById('id_gallery')
gallerySelectUpdate = () => {
const useGallery = (gallerySelect.value !== "");
document.getElementById('id_new_gallery_title').disabled = useGallery;
document.getElementById('id_new_gallery_date_start').disabled = useGallery;
document.getElementById('id_new_gallery_date_end').disabled = useGallery;
document.getElementById('id_new_gallery_tags').disabled = useGallery;
}
gallerySelect.addEventListener('change', gallerySelectUpdate);
gallerySelectUpdate();
// On submit, show a message to make user wait
document.getElementById('upload_form').addEventListener('submit', (e) => {
document.getElementById('submit-id-submit').disabled = true;
document.getElementById('submit-id-submit').value = "Please be patient";
})
</script>
{% endblock %}
{% block content %}
<h1>{% trans "Upload" %}</h1>
<div class="card">
<div class="card-body">
<form method="post" enctype="multipart/form-data" id="upload_form">{% csrf_token %}
<div class="upload-drop-zone" id="drop-zone">
{% trans "Drag and drop photos here" %}
</div>
{% crispy form %}
<p class="mt-3">
{% trans "Owner will be" %} <code>{{ request.user.get_full_name }} ({{ request.user.username}})</code>.
</p>
</form>
</div>
</div>
{% endblock %}

View file

@ -1,7 +1,8 @@
from django.urls import path, re_path
from photologue_custom.views import CustomGalleryDetailView, GalleryDownload, GalleryUpload, TagDetail
from .views import GalleryArchiveIndexView, GalleryYearArchiveView, PhotoDetailView
from .views import (CustomGalleryDetailView, GalleryArchiveIndexView,
GalleryDownload, GalleryUpload, GalleryYearArchiveView,
PhotoDetailView, TagDetail)
app_name = 'photologue'
urlpatterns = [

View file

@ -1,8 +1,26 @@
from django.contrib.auth.mixins import LoginRequiredMixin
# Copyright (C) 2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import zipfile
from io import BytesIO
from pathlib import Path
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.urls import reverse_lazy
from django.utils.text import slugify
from django.views.generic.dates import ArchiveIndexView, YearArchiveView
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormView
from PIL import Image
from .models import Gallery, Photo
from .forms import UploadForm
from .models import Gallery, Photo, Tag
class GalleryDateView(LoginRequiredMixin):
@ -22,3 +40,125 @@ class GalleryYearArchiveView(GalleryDateView, YearArchiveView):
class PhotoDetailView(LoginRequiredMixin, DetailView):
queryset = Photo.objects.filter(is_public=True)
class TagDetail(LoginRequiredMixin, DetailView):
model = Tag
def get_context_data(self, **kwargs):
"""
Insert the single object into the context dict.
"""
current_tag = self.get_object().slug
context = super().get_context_data(**kwargs)
context['galleries'] = Gallery.objects.filter(is_public=True) \
.filter(tags__slug=current_tag) \
.order_by('-date_start')
return context
class CustomGalleryDetailView(LoginRequiredMixin, DetailView):
"""
Custom gallery detail view to filter on photo owner
"""
queryset = Gallery.objects.filter(is_public=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Query with owner to reduce database lag
context['photos'] = self.object.public().select_related('owner')
# List owners
context['owners'] = []
for photo in context['photos']:
if photo.owner not in context['owners']:
context['owners'].append(photo.owner)
# Filter on owner
if 'owner' in self.kwargs:
context['photos'] = context['photos'].filter(owner__id=self.kwargs['owner'])
return context
class GalleryDownload(LoginRequiredMixin, DetailView):
model = Gallery
def get(self, request, *args, **kwargs):
"""
Download a zip file of the gallery on GET request.
"""
# Create zip file with pictures
gallery = self.get_object()
byte_data = BytesIO()
zip_file = zipfile.ZipFile(byte_data, "w")
for photo in gallery.public():
filename = os.path.basename(os.path.normpath(photo.image.path))
zip_file.write(photo.image.path, filename)
zip_file.close()
# Return zip file
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("photologue:pl-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()
gallery_year = Path(str(gallery.date_start.year))
gallery_dir = gallery_year / gallery.slug
failed_upload = 0
for photo_file in files:
# Check that we have a valid image
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),
owner=self.request.user,
)
photo_name = str(gallery_dir / photo_file.name)
photo.image.save(photo_name, photo_file)
photo.save()
photo.galleries.set([gallery])
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)