diff --git a/README.md b/README.md
index 52dccb321f9648dc948c920cbfc9eb10ba164120..9edddbcab8c29a392309fb056b9d92c7e8374b7e 100644
--- a/README.md
+++ b/README.md
@@ -79,8 +79,7 @@ production néccessite **une installation de Debian Bullseye ou plus récent**.
     ```
     $ sudo apt install nginx git gettext uwsgi uwsgi-plugin-python3 python3-venv \
         python3-certbot-nginx python3-django python3-django-crispy-forms \
-        python3-django-taggit python3-pil python3-exifread python3-django-allauth \
-        python3-psycopg2 python3-docutils
+        python3-pil python3-exifread python3-django-allauth python3-docutils
     ```
 
 2.  **Clonage du dépot dans `/var/www/photos/photo21`**
diff --git a/photo21/locale/de/LC_MESSAGES/django.po b/photo21/locale/de/LC_MESSAGES/django.po
index 1cff8066dd069ed9d677916be71597ecfcb4f8b1..d45572b3e0e3c4f13a3abfb1bce6c7cb9aae540e 100644
--- a/photo21/locale/de/LC_MESSAGES/django.po
+++ b/photo21/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-01-29 21:58+0000\n"
+"POT-Creation-Date: 2022-01-30 09:55+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -40,19 +40,19 @@ msgstr ""
 msgid "hash"
 msgstr ""
 
-#: photo21/settings.py:162
+#: photo21/settings.py:161
 msgid "German"
 msgstr ""
 
-#: photo21/settings.py:163
+#: photo21/settings.py:162
 msgid "English"
 msgstr ""
 
-#: photo21/settings.py:164
+#: photo21/settings.py:163
 msgid "Spanish"
 msgstr ""
 
-#: photo21/settings.py:165
+#: photo21/settings.py:164
 msgid "French"
 msgstr ""
 
@@ -199,6 +199,16 @@ msgstr ""
 msgid "If any problem, please contact the server owners at"
 msgstr ""
 
+#: photo21/templates/account/logout.html:6
+#: photo21/templates/account/logout.html:11
+#: photo21/templates/account/logout.html:20
+msgid "Sign Out"
+msgstr ""
+
+#: photo21/templates/account/logout.html:14
+msgid "Are you sure you want to sign out?"
+msgstr ""
+
 #: photo21/templates/account/signup.html:6
 msgid "Signup"
 msgstr ""
diff --git a/photo21/locale/es/LC_MESSAGES/django.po b/photo21/locale/es/LC_MESSAGES/django.po
index a9413d9859ec28fab5b5de5874fe696285a4db44..f05fed8c053dbbeabee18116fd2c04e269e5becc 100644
--- a/photo21/locale/es/LC_MESSAGES/django.po
+++ b/photo21/locale/es/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-01-29 21:58+0000\n"
+"POT-Creation-Date: 2022-01-30 09:55+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -39,19 +39,19 @@ msgstr ""
 msgid "hash"
 msgstr ""
 
-#: photo21/settings.py:162
+#: photo21/settings.py:161
 msgid "German"
 msgstr ""
 
-#: photo21/settings.py:163
+#: photo21/settings.py:162
 msgid "English"
 msgstr ""
 
-#: photo21/settings.py:164
+#: photo21/settings.py:163
 msgid "Spanish"
 msgstr ""
 
-#: photo21/settings.py:165
+#: photo21/settings.py:164
 msgid "French"
 msgstr ""
 
@@ -198,6 +198,16 @@ msgstr ""
 msgid "If any problem, please contact the server owners at"
 msgstr ""
 
+#: photo21/templates/account/logout.html:6
+#: photo21/templates/account/logout.html:11
+#: photo21/templates/account/logout.html:20
+msgid "Sign Out"
+msgstr ""
+
+#: photo21/templates/account/logout.html:14
+msgid "Are you sure you want to sign out?"
+msgstr ""
+
 #: photo21/templates/account/signup.html:6
 msgid "Signup"
 msgstr ""
diff --git a/photo21/locale/fr/LC_MESSAGES/django.po b/photo21/locale/fr/LC_MESSAGES/django.po
index ac7970b1c2438380699a5a99f4296d0ca3b30525..7d7d518810da9d55adaccf41112fb6bda2d1a204 100644
--- a/photo21/locale/fr/LC_MESSAGES/django.po
+++ b/photo21/locale/fr/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\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: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -42,19 +42,19 @@ msgstr ""
 msgid "hash"
 msgstr ""
 
-#: photo21/settings.py:162
+#: photo21/settings.py:160
 msgid "German"
 msgstr ""
 
-#: photo21/settings.py:163
+#: photo21/settings.py:161
 msgid "English"
 msgstr ""
 
-#: photo21/settings.py:164
+#: photo21/settings.py:162
 msgid "Spanish"
 msgstr ""
 
-#: photo21/settings.py:165
+#: photo21/settings.py:163
 msgid "French"
 msgstr ""
 
@@ -206,6 +206,18 @@ msgstr ""
 msgid "If any problem, please contact the server owners at"
 msgstr "En cas de problème, contactez les administrateurs à"
 
+#: photo21/templates/account/logout.html:6
+#: photo21/templates/account/logout.html:11
+#: photo21/templates/account/logout.html:20
+#, fuzzy
+#| msgid "Sign up"
+msgid "Sign Out"
+msgstr "Inscription"
+
+#: photo21/templates/account/logout.html:14
+msgid "Are you sure you want to sign out?"
+msgstr ""
+
 #: photo21/templates/account/signup.html:6
 msgid "Signup"
 msgstr ""
@@ -333,48 +345,3 @@ msgstr ""
 #: photo21/templates/socialaccount/connections.html:54
 msgid "Add a 3rd Party Account"
 msgstr ""
-
-#~ msgid "owner"
-#~ msgstr "propriétaire"
-
-#~ msgid "Gallery"
-#~ msgstr "Galerie"
-
-#~ msgid "-- Create a new gallery --"
-#~ msgstr "-- Créer une nouvelle galerie --"
-
-#~ msgid "New gallery title"
-#~ msgstr "Titre de la nouvelle galerie"
-
-#~ msgid "New gallery event start date"
-#~ msgstr "Date de début de l'évènement de la nouvelle galerie"
-
-#~ msgid "New gallery event end date"
-#~ msgstr "Date de fin de l'évènement de la nouvelle galerie"
-
-#~ msgid "New gallery tags"
-#~ msgstr "Tags de la nouvelle galerie"
-
-#~ msgid "start date"
-#~ msgstr "date de début"
-
-#~ msgid "end date"
-#~ msgstr "date de fin"
-
-#~ msgid "license"
-#~ msgstr "licence"
-
-#~ msgid "to"
-#~ msgstr "au"
-
-#~ msgid "All pictures"
-#~ msgstr "Toutes les photos"
-
-#~ msgid "Download all gallery"
-#~ msgstr "Télécharger toute la galerie"
-
-#~ msgid "Drag and drop photos here"
-#~ msgstr "Glissez et déposez les photos ici"
-
-#~ msgid "Owner will be"
-#~ msgstr "Le propriétaire sera"
diff --git a/photo21/settings.py b/photo21/settings.py
index 5c61f4242143544482c5abf870dfc3246731b2d8..e6773ec95394e18d1cba5a9883f5170601f10738 100644
--- a/photo21/settings.py
+++ b/photo21/settings.py
@@ -62,9 +62,7 @@ INSTALLED_APPS = [
     'allauth.socialaccount',
     'allauth_note_kfet',
     'crispy_forms',
-    'photologue_custom',
     'photologue',
-    'taggit',
 ]
 
 MIDDLEWARE = [
@@ -156,8 +154,6 @@ USE_L10N = True
 
 USE_TZ = True
 
-DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-
 # Limit available languages to this subset
 LANGUAGES = [
     ('de', _('German')),
diff --git a/photo21/templates/account/logout.html b/photo21/templates/account/logout.html
new file mode 100644
index 0000000000000000000000000000000000000000..7db6ac3dc1cf88dee30baecca8b8460911a3cb08
--- /dev/null
+++ b/photo21/templates/account/logout.html
@@ -0,0 +1,24 @@
+{% extends "account/base.html" %}
+{% comment %}
+SPDX-License-Identifier: GPL-2.0-or-later
+{% endcomment %}
+{% load i18n %}
+{% block head_title %}{% trans "Sign Out" %}{% endblock %}
+
+{% block content %}
+<div class="card mx-auto">
+    <h3 class="card-header text-center">
+        {% trans "Sign Out" %}
+    </h3>
+    <div class="card-body">        
+            <p>{% trans 'Are you sure you want to sign out?' %}</p>
+            <form method="post" action="{% url 'account_logout' %}">
+                {% csrf_token %}
+                {% if redirect_field_value %}
+                <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
+                {% endif %}
+                <button type="submit" class="btn btn-primary">{% trans 'Sign Out' %}</button>
+            </form>
+    </div>
+</div>
+{% endblock %}
diff --git a/photo21/urls.py b/photo21/urls.py
index 530068a2596497104708a8f9b873324fe467bed8..66d4912bc362e86f27123e710f33221a18757bde 100644
--- a/photo21/urls.py
+++ b/photo21/urls.py
@@ -22,11 +22,11 @@ from .views import IndexView, MediaAccess
 
 urlpatterns = [
     path('', IndexView.as_view(), name='index'),
-    path('', include('photologue_custom.urls', namespace='photologue')),
+    path('', include('photologue.urls', namespace='photologue')),
     path('accounts/', include('allauth.urls')),
     path('i18n/', include('django.conf.urls.i18n')),
-    path('admin/', admin.site.urls),
     path('admin/doc/', include('django.contrib.admindocs.urls')),
+    path('admin/', admin.site.urls),
 ]
 
 # In production media are served through NGINX with X-Accel-Redirect
diff --git a/photologue/admin.py b/photologue/admin.py
index 0c04abf5fe91a0d87f9e5ba01d5d98d19d9a0e0f..774ed8e89af2d4abf6c1b8d319499cc1925362c7 100644
--- a/photologue/admin.py
+++ b/photologue/admin.py
@@ -1,24 +1,41 @@
 from django.contrib import admin
+from django.utils.translation import gettext_lazy as _
 
-from .models import Gallery, Photo
+from .models import Gallery, Photo, Tag
 
 
 class GalleryAdmin(admin.ModelAdmin):
-    list_display = ('title', 'date_added', 'photo_count', 'is_public')
-    list_filter = ['date_added', 'is_public']
-    date_hierarchy = 'date_added'
+    list_display = ('title', 'date_start', 'photo_count', 'is_public')
+    list_filter = ['date_start', 'is_public']
+    date_hierarchy = 'date_start'
     prepopulated_fields = {'slug': ('title',)}
     model = Gallery
-    autocomplete_fields = ['photos', ]
+    autocomplete_fields = ['photos', 'tags']
     search_fields = ['title', ]
 
 
 class PhotoAdmin(admin.ModelAdmin):
     list_display = ('title', 'date_taken', 'date_added',
-                    'is_public', 'view_count', 'admin_thumbnail')
-    list_filter = ['date_added', 'is_public']
+                    'is_public', 'view_count', 'admin_thumbnail', 'get_owner')
+    list_filter = ['date_added', 'is_public', 'owner']
     search_fields = ['title', 'slug', 'caption']
     list_per_page = 10
     prepopulated_fields = {'slug': ('title',)}
     readonly_fields = ('date_taken',)
     model = Photo
+
+    def get_owner(self, obj):
+        return obj.owner.username
+    get_owner.admin_order_field = 'owner'
+    get_owner.short_description = _('owner')
+
+
+class TagAdmin(admin.ModelAdmin):
+    list_display = ('name',)
+    search_fields = ('name',)
+    model = Tag
+
+
+admin.site.register(Gallery, GalleryAdmin)
+admin.site.register(Photo, PhotoAdmin)
+admin.site.register(Tag, TagAdmin)
diff --git a/photologue_custom/forms.py b/photologue/forms.py
similarity index 84%
rename from photologue_custom/forms.py
rename to photologue/forms.py
index 11a146d7db404ca1a9ed355e7de67f299406ada9..8ef1f1cc3ce7a03ac2cbd001a99d7a10026b226f 100644
--- a/photologue_custom/forms.py
+++ b/photologue/forms.py
@@ -5,19 +5,8 @@ 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 photologue.models import Gallery
-from taggit.models import Tag
 
-from .models import GalleryExtended
-
-
-class GalleryChoiceField(forms.ModelChoiceField):
-    def label_from_instance(self, obj):
-        """Show gallery event date."""
-        if hasattr(obj, 'extended'):
-            return f"{ obj.title } ({obj.extended.date_start})"
-        else:
-            return obj.title
+from .models import Gallery, Tag
 
 
 class UploadForm(forms.Form):
@@ -29,7 +18,7 @@ class UploadForm(forms.Form):
             'class': 'mb-3',
         }),
     )
-    gallery = GalleryChoiceField(
+    gallery = forms.ModelChoiceField(
         Gallery.objects.all(),
         label=_('Gallery'),
         required=False,
@@ -100,12 +89,12 @@ class UploadForm(forms.Form):
         if not gallery:
             # Create new gallery
             title = self.cleaned_data.get('new_gallery_title')
-            gallery = Gallery.objects.create(title=title, slug=slugify(title))
-            ext = GalleryExtended.objects.create(
-                gallery=gallery,
+            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']:
-                ext.tags.add(tag)
+                gallery.tags.add(tag)
         return gallery
diff --git a/photologue/locale/de/LC_MESSAGES/django.po b/photologue/locale/de/LC_MESSAGES/django.po
index f9a29ed88c4ab83f70d57587781d76cc7724cc37..50c106d605e892e454abbb92c381076ff9b8b898 100644
--- a/photologue/locale/de/LC_MESSAGES/django.po
+++ b/photologue/locale/de/LC_MESSAGES/django.po
@@ -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"
diff --git a/photologue/locale/es/LC_MESSAGES/django.po b/photologue/locale/es/LC_MESSAGES/django.po
index 8a9ca7147505a44b03393bd3f6bd9bfbadf5c39a..e8f687fe479329dd697276e2dffbe1f1422342f0 100644
--- a/photologue/locale/es/LC_MESSAGES/django.po
+++ b/photologue/locale/es/LC_MESSAGES/django.po
@@ -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"
diff --git a/photologue/locale/fr/LC_MESSAGES/django.po b/photologue/locale/fr/LC_MESSAGES/django.po
index f3b69fc0137eac88fcd81b6ab6ac1ecf095ebb5f..a9966278cad75453690e4423ccef3202da42a92d 100644
--- a/photologue/locale/fr/LC_MESSAGES/django.po
+++ b/photologue/locale/fr/LC_MESSAGES/django.po
@@ -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"
-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."
+#: photologue/models.py:702
+msgid "tag"
 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"
diff --git a/photologue_custom/management/commands/duplicate.py b/photologue/management/commands/duplicate.py
similarity index 100%
rename from photologue_custom/management/commands/duplicate.py
rename to photologue/management/commands/duplicate.py
index 97a902af895c47f6caa9b16e0433ca7607621800..56ce18558d40437523d72a91958c9c628495ffc5 100644
--- a/photologue_custom/management/commands/duplicate.py
+++ b/photologue/management/commands/duplicate.py
@@ -1,8 +1,8 @@
+import hashlib
+
 from django.core.management.base import BaseCommand, CommandError
 from photologue.models import Gallery
 
-import hashlib
-
 
 class Command(BaseCommand):
     help = 'List all duplicate for chosen galleries'
diff --git a/photologue_custom/management/commands/rename_media.py b/photologue/management/commands/rename_media.py
similarity index 95%
rename from photologue_custom/management/commands/rename_media.py
rename to photologue/management/commands/rename_media.py
index 62615e9269d337debb295e00b6e8f653dc804b72..1f8767e10870889364da0a87972f70e6f94340fe 100644
--- a/photologue_custom/management/commands/rename_media.py
+++ b/photologue/management/commands/rename_media.py
@@ -1,9 +1,9 @@
-from pathlib import Path
 import os
+from pathlib import Path
 
+from django.conf import settings
 from django.core.management.base import BaseCommand
 from photologue.models import Gallery
-from django.conf import settings
 
 
 class Command(BaseCommand):
@@ -16,7 +16,7 @@ class Command(BaseCommand):
         media_dir = Path(settings.MEDIA_ROOT)
         for gallery in Gallery.objects.all():
             # Create gallery directory
-            gallery_year = str(gallery.extended.date_start.year)
+            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():
diff --git a/photologue/migrations/0001_initial.py b/photologue/migrations/0001_initial.py
index 35d3b3d8f35f00493cece7a116fe4ce22b6b30e7..47bca5b7627ea5e561ac2372c5e3a526e498ada3 100644
--- a/photologue/migrations/0001_initial.py
+++ b/photologue/migrations/0001_initial.py
@@ -1,158 +1,96 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
+# Generated by Django 3.2.11 on 2022-01-30 10:14
 
+from django.conf import settings
 import django.core.validators
-import django.utils.timezone
-import sortedm2m.fields
 from django.db import migrations, models
-
+import django.db.models.deletion
+import django.utils.timezone
 import photologue.models
 
 
 class Migration(migrations.Migration):
 
+    initial = True
+
     dependencies = [
-        ('sites', '0001_initial'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Gallery',
+            name='PhotoSize',
             fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
-                ('date_added', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date published')),
-                ('title', models.CharField(max_length=50, verbose_name='title', unique=True)),
-                ('slug', models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', verbose_name='title slug', unique=True)),
-                ('description', models.TextField(blank=True, verbose_name='description')),
-                ('is_public', models.BooleanField(help_text='Public galleries will be displayed in the default views.', verbose_name='is public', default=True)),
-                ('tags', photologue.models.TagField(max_length=255, help_text='Django-tagging was not found, tags will be treated as plain text.', blank=True, verbose_name='tags')),
-                ('sites', models.ManyToManyField(blank=True, verbose_name='sites', null=True, to='sites.Site')),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(help_text='Photo size name should contain only letters, numbers and underscores. Examples: "thumbnail", "display", "small", "main_page_widget".', max_length=40, unique=True, validators=[django.core.validators.RegexValidator(message='Use only plain lowercase letters (ASCII), numbers and underscores.', regex='^[a-z0-9_]+$')], verbose_name='name')),
+                ('width', models.PositiveIntegerField(default=0, help_text='If width is set to "0" the image will be scaled to the supplied height.', verbose_name='width')),
+                ('height', models.PositiveIntegerField(default=0, help_text='If height is set to "0" the image will be scaled to the supplied width', verbose_name='height')),
+                ('quality', models.PositiveIntegerField(choices=[(30, 'Very Low'), (40, 'Low'), (50, 'Medium-Low'), (60, 'Medium'), (70, 'Medium-High'), (80, 'High'), (90, 'Very High')], default=70, help_text='JPEG image quality.', verbose_name='quality')),
+                ('upscale', models.BooleanField(default=False, help_text='If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting.', verbose_name='upscale images?')),
+                ('crop', models.BooleanField(default=False, help_text='If selected the image will be scaled and cropped to fit the supplied dimensions.', verbose_name='crop to fit?')),
+                ('pre_cache', models.BooleanField(default=False, help_text='If selected this photo size will be pre-cached as photos are added.', verbose_name='pre-cache?')),
+                ('increment_count', models.BooleanField(default=False, help_text='If selected the image\'s "view_count" will be incremented when this photo size is displayed.', verbose_name='increment view count?')),
             ],
             options={
-                'get_latest_by': 'date_added',
-                'verbose_name': 'gallery',
-                'ordering': ['-date_added'],
-                'verbose_name_plural': 'galleries',
+                'verbose_name': 'photo size',
+                'verbose_name_plural': 'photo sizes',
+                'ordering': ['width', 'height'],
             },
-            bases=(models.Model,),
         ),
         migrations.CreateModel(
-            name='GalleryUpload',
+            name='Tag',
             fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
-                ('zip_file', models.FileField(help_text='Select a .zip file of images to upload into a new Gallery.', verbose_name='images file (.zip)', upload_to='photologue/temp')),
-                ('title', models.CharField(max_length=50, help_text='All uploaded photos will be given a title made up of this title + a sequential number.', verbose_name='title')),
-                ('caption', models.TextField(help_text='Caption will be added to all photos.', blank=True, verbose_name='caption')),
-                ('description', models.TextField(help_text='A description of this Gallery.', blank=True, verbose_name='description')),
-                ('is_public', models.BooleanField(help_text='Uncheck this to make the uploaded gallery and included photographs private.', verbose_name='is public', default=True)),
-                ('tags', models.CharField(max_length=255, help_text='Django-tagging was not found, tags will be treated as plain text.', blank=True, verbose_name='tags')),
-                ('gallery', models.ForeignKey(blank=True, verbose_name='gallery', null=True, help_text='Select a gallery to add these images to. Leave this empty to create a new gallery from the supplied title.', to='photologue.Gallery', on_delete=models.CASCADE)),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=250, unique=True, verbose_name='name')),
+                ('slug', models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', max_length=250, unique=True, verbose_name='slug')),
             ],
             options={
-                'verbose_name': 'gallery upload',
-                'verbose_name_plural': 'gallery uploads',
+                'verbose_name': 'tag',
+                'verbose_name_plural': 'tags',
+                'ordering': ['name'],
             },
-            bases=(models.Model,),
         ),
         migrations.CreateModel(
             name='Photo',
             fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('image', models.ImageField(upload_to=photologue.models.get_storage_path, verbose_name='image')),
-                ('date_taken', models.DateTimeField(verbose_name='date taken', blank=True, editable=False, null=True)),
-                ('view_count', models.PositiveIntegerField(verbose_name='view count', default=0, editable=False)),
-                ('crop_from', models.CharField(max_length=10, default='center', blank=True, verbose_name='crop from', choices=[('top', 'Top'), ('right', 'Right'), ('bottom', 'Bottom'), ('left', 'Left'), ('center', 'Center (Default)')])),
-                ('title', models.CharField(max_length=50, verbose_name='title', unique=True)),
-                ('slug', models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', verbose_name='slug', unique=True)),
+                ('date_taken', models.DateTimeField(blank=True, help_text='Date image was taken; is obtained from the image EXIF data.', null=True, verbose_name='date taken')),
+                ('view_count', models.PositiveIntegerField(default=0, editable=False, verbose_name='view count')),
+                ('crop_from', models.CharField(blank=True, choices=[('top', 'Top'), ('right', 'Right'), ('bottom', 'Bottom'), ('left', 'Left'), ('center', 'Center (Default)')], default='center', max_length=10, verbose_name='crop from')),
+                ('title', models.CharField(max_length=250, unique=True, verbose_name='title')),
+                ('slug', models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', max_length=250, unique=True, verbose_name='slug')),
                 ('caption', models.TextField(blank=True, verbose_name='caption')),
                 ('date_added', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date added')),
-                ('is_public', models.BooleanField(help_text='Public photographs will be displayed in the default views.', verbose_name='is public', default=True)),
-                ('tags', photologue.models.TagField(max_length=255, help_text='Django-tagging was not found, tags will be treated as plain text.', blank=True, verbose_name='tags')),
-                ('sites', models.ManyToManyField(blank=True, verbose_name='sites', null=True, to='sites.Site')),
+                ('license', models.CharField(blank=True, max_length=255, verbose_name='license')),
+                ('is_public', models.BooleanField(default=True, help_text='Public photographs will be displayed in the default views.', verbose_name='is public')),
+                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='owner')),
             ],
             options={
-                'get_latest_by': 'date_added',
                 'verbose_name': 'photo',
-                'ordering': ['-date_added'],
                 'verbose_name_plural': 'photos',
+                'ordering': ['-date_added'],
+                'get_latest_by': 'date_added',
             },
-            bases=(models.Model,),
-        ),
-        migrations.AddField(
-            model_name='gallery',
-            name='photos',
-            field=sortedm2m.fields.SortedManyToManyField(blank=True, verbose_name='photos', null=True, to='photologue.Photo'),
-            preserve_default=True,
-        ),
-        migrations.CreateModel(
-            name='PhotoEffect',
-            fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
-                ('name', models.CharField(max_length=30, verbose_name='name', unique=True)),
-                ('description', models.TextField(blank=True, verbose_name='description')),
-                ('transpose_method', models.CharField(max_length=15, blank=True, verbose_name='rotate or flip', choices=[('FLIP_LEFT_RIGHT', 'Flip left to right'), ('FLIP_TOP_BOTTOM', 'Flip top to bottom'), ('ROTATE_90', 'Rotate 90 degrees counter-clockwise'), ('ROTATE_270', 'Rotate 90 degrees clockwise'), ('ROTATE_180', 'Rotate 180 degrees')])),
-                ('color', models.FloatField(help_text='A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image.', verbose_name='color', default=1.0)),
-                ('brightness', models.FloatField(help_text='A factor of 0.0 gives a black image, a factor of 1.0 gives the original image.', verbose_name='brightness', default=1.0)),
-                ('contrast', models.FloatField(help_text='A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image.', verbose_name='contrast', default=1.0)),
-                ('sharpness', models.FloatField(help_text='A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image.', verbose_name='sharpness', default=1.0)),
-                ('filters', models.CharField(max_length=200, help_text='Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE". Image filters will be applied in order. The following filters are available: BLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, SHARPEN, SMOOTH, SMOOTH_MORE.', blank=True, verbose_name='filters')),
-                ('reflection_size', models.FloatField(help_text='The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image.', verbose_name='size', default=0)),
-                ('reflection_strength', models.FloatField(help_text='The initial opacity of the reflection gradient.', verbose_name='strength', default=0.6)),
-                ('background_color', models.CharField(max_length=7, help_text='The background color of the reflection gradient. Set this to match the background color of your page.', verbose_name='color', default='#FFFFFF')),
-            ],
-            options={
-                'verbose_name': 'photo effect',
-                'verbose_name_plural': 'photo effects',
-            },
-            bases=(models.Model,),
-        ),
-        migrations.AddField(
-            model_name='photo',
-            name='effect',
-            field=models.ForeignKey(blank=True, verbose_name='effect', null=True, to='photologue.PhotoEffect', on_delete=models.CASCADE),
-            preserve_default=True,
-        ),
-        migrations.CreateModel(
-            name='PhotoSize',
-            fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
-                ('name', models.CharField(max_length=40, help_text='Photo size name should contain only letters, numbers and underscores. Examples: "thumbnail", "display", "small", "main_page_widget".', verbose_name='name', unique=True, validators=[django.core.validators.RegexValidator(regex='^[a-z0-9_]+$', message='Use only plain lowercase letters (ASCII), numbers and underscores.')])),
-                ('width', models.PositiveIntegerField(help_text='If width is set to "0" the image will be scaled to the supplied height.', verbose_name='width', default=0)),
-                ('height', models.PositiveIntegerField(help_text='If height is set to "0" the image will be scaled to the supplied width', verbose_name='height', default=0)),
-                ('quality', models.PositiveIntegerField(help_text='JPEG image quality.', verbose_name='quality', choices=[(30, 'Very Low'), (40, 'Low'), (50, 'Medium-Low'), (60, 'Medium'), (70, 'Medium-High'), (80, 'High'), (90, 'Very High')], default=70)),
-                ('upscale', models.BooleanField(help_text='If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting.', verbose_name='upscale images?', default=False)),
-                ('crop', models.BooleanField(help_text='If selected the image will be scaled and cropped to fit the supplied dimensions.', verbose_name='crop to fit?', default=False)),
-                ('pre_cache', models.BooleanField(help_text='If selected this photo size will be pre-cached as photos are added.', verbose_name='pre-cache?', default=False)),
-                ('increment_count', models.BooleanField(help_text='If selected the image\'s "view_count" will be incremented when this photo size is displayed.', verbose_name='increment view count?', default=False)),
-                ('effect', models.ForeignKey(blank=True, verbose_name='photo effect', null=True, to='photologue.PhotoEffect', on_delete=models.CASCADE)),
-            ],
-            options={
-                'verbose_name': 'photo size',
-                'ordering': ['width', 'height'],
-                'verbose_name_plural': 'photo sizes',
-            },
-            bases=(models.Model,),
         ),
         migrations.CreateModel(
-            name='Watermark',
+            name='Gallery',
             fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
-                ('name', models.CharField(max_length=30, verbose_name='name', unique=True)),
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date_added', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date published')),
+                ('title', models.CharField(max_length=250, unique=True, verbose_name='title')),
+                ('slug', models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', max_length=250, unique=True, verbose_name='title slug')),
+                ('date_start', models.DateField(default=django.utils.timezone.now, verbose_name='start date')),
+                ('date_end', models.DateField(blank=True, null=True, verbose_name='end date')),
                 ('description', models.TextField(blank=True, verbose_name='description')),
-                ('image', models.ImageField(upload_to='photologue/watermarks', verbose_name='image')),
-                ('style', models.CharField(max_length=5, default='scale', verbose_name='style', choices=[('tile', 'Tile'), ('scale', 'Scale')])),
-                ('opacity', models.FloatField(help_text='The opacity of the overlay.', verbose_name='opacity', default=1)),
+                ('is_public', models.BooleanField(default=True, help_text='Public galleries will be displayed in the default views.', verbose_name='is public')),
+                ('photos', models.ManyToManyField(blank=True, related_name='galleries', to='photologue.Photo', verbose_name='photos')),
+                ('tags', models.ManyToManyField(blank=True, related_name='galleries', to='photologue.Tag', verbose_name='tags')),
             ],
             options={
-                'verbose_name': 'watermark',
-                'verbose_name_plural': 'watermarks',
+                'verbose_name': 'gallery',
+                'verbose_name_plural': 'galleries',
+                'ordering': ['-date_added'],
+                'get_latest_by': 'date_added',
             },
-            bases=(models.Model,),
-        ),
-        migrations.AddField(
-            model_name='photosize',
-            name='watermark',
-            field=models.ForeignKey(blank=True, verbose_name='watermark image', null=True, to='photologue.Watermark', on_delete=models.CASCADE),
-            preserve_default=True,
         ),
     ]
diff --git a/photologue/migrations/0002_auto_20220130_1020.py b/photologue/migrations/0002_auto_20220130_1020.py
new file mode 100644
index 0000000000000000000000000000000000000000..21338b08206126b6756efacc913e7d76315032ab
--- /dev/null
+++ b/photologue/migrations/0002_auto_20220130_1020.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.2.11 on 2022-01-30 10:20
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('photologue', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='gallery',
+            options={'get_latest_by': 'date_start', 'ordering': ['-date_start'], 'verbose_name': 'gallery', 'verbose_name_plural': 'galleries'},
+        ),
+        migrations.RemoveField(
+            model_name='gallery',
+            name='date_added',
+        ),
+    ]
diff --git a/photologue/migrations/0002_photosize_data.py b/photologue/migrations/0002_photosize_data.py
deleted file mode 100644
index 7bb9229197ba5264aec75e5f7aab738aef7af20e..0000000000000000000000000000000000000000
--- a/photologue/migrations/0002_photosize_data.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# encoding: utf8
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-def initial_photosizes(apps, schema_editor):
-
-    PhotoSize = apps.get_model('photologue', 'PhotoSize')
-
-    # If there are already Photosizes, then we are upgrading an existing
-    # installation, we don't want to auto-create some PhotoSizes.
-    if PhotoSize.objects.all().count() > 0:
-        return
-    PhotoSize.objects.create(name='admin_thumbnail',
-                             width=100,
-                             height=75,
-                             crop=True,
-                             pre_cache=True,
-                             increment_count=False)
-    PhotoSize.objects.create(name='thumbnail',
-                             width=100,
-                             height=75,
-                             crop=True,
-                             pre_cache=True,
-                             increment_count=False)
-    PhotoSize.objects.create(name='display',
-                             width=400,
-                             crop=False,
-                             pre_cache=True,
-                             increment_count=True)
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0001_initial'),
-        ('contenttypes', '0002_remove_content_type_name'),
-    ]
-
-    operations = [
-        migrations.RunPython(initial_photosizes),
-    ]
diff --git a/photologue/migrations/0003_auto_20140822_1716.py b/photologue/migrations/0003_auto_20140822_1716.py
deleted file mode 100644
index 16d1942b04893b762bf25e6b99e58e9b8e5d50f1..0000000000000000000000000000000000000000
--- a/photologue/migrations/0003_auto_20140822_1716.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0002_photosize_data'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='galleryupload',
-            name='title',
-            field=models.CharField(null=True, help_text='All uploaded photos will be given a title made up of this title + a sequential number.', max_length=50, verbose_name='title', blank=True),
-        ),
-    ]
diff --git a/photologue/migrations/0004_auto_20140915_1259.py b/photologue/migrations/0004_auto_20140915_1259.py
deleted file mode 100644
index 0202044113a2eeab893ec3ee6399a5b9f5a9021b..0000000000000000000000000000000000000000
--- a/photologue/migrations/0004_auto_20140915_1259.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import sortedm2m.fields
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0003_auto_20140822_1716'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='gallery',
-            name='photos',
-            field=sortedm2m.fields.SortedManyToManyField(to='photologue.Photo', related_name='galleries', null=True, verbose_name='photos', blank=True, help_text=None),
-        ),
-        migrations.AlterField(
-            model_name='photo',
-            name='effect',
-            field=models.ForeignKey(to='photologue.PhotoEffect', blank=True, related_name='photo_related', verbose_name='effect', null=True, on_delete=models.CASCADE),
-        ),
-        migrations.AlterField(
-            model_name='photosize',
-            name='effect',
-            field=models.ForeignKey(to='photologue.PhotoEffect', blank=True, related_name='photo_sizes', verbose_name='photo effect', null=True, on_delete=models.CASCADE),
-        ),
-        migrations.AlterField(
-            model_name='photosize',
-            name='watermark',
-            field=models.ForeignKey(to='photologue.Watermark', blank=True, related_name='photo_sizes', verbose_name='watermark image', null=True, on_delete=models.CASCADE),
-        ),
-    ]
diff --git a/photologue/migrations/0005_auto_20141027_1552.py b/photologue/migrations/0005_auto_20141027_1552.py
deleted file mode 100644
index 9f3d862340caf49c9bc5c1bd8764429fb9361877..0000000000000000000000000000000000000000
--- a/photologue/migrations/0005_auto_20141027_1552.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0004_auto_20140915_1259'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='photo',
-            name='title',
-            field=models.CharField(unique=True, max_length=60, verbose_name='title'),
-            preserve_default=True,
-        ),
-    ]
diff --git a/photologue/migrations/0006_auto_20141028_2005.py b/photologue/migrations/0006_auto_20141028_2005.py
deleted file mode 100644
index 583c3b800d38522f3b26b48c447b373836b43dc3..0000000000000000000000000000000000000000
--- a/photologue/migrations/0006_auto_20141028_2005.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0005_auto_20141027_1552'),
-    ]
-
-    operations = [
-        migrations.RemoveField(
-            model_name='galleryupload',
-            name='gallery',
-        ),
-        migrations.DeleteModel(
-            name='GalleryUpload',
-        ),
-    ]
diff --git a/photologue/migrations/0007_auto_20150404_1737.py b/photologue/migrations/0007_auto_20150404_1737.py
deleted file mode 100644
index b41490c28bac4d9ef60bf3601ea2cb050bff54ac..0000000000000000000000000000000000000000
--- a/photologue/migrations/0007_auto_20150404_1737.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import sortedm2m.fields
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0006_auto_20141028_2005'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='gallery',
-            name='photos',
-            field=sortedm2m.fields.SortedManyToManyField(help_text=None, related_name='galleries', verbose_name='photos', to='photologue.Photo', blank=True),
-        ),
-        migrations.AlterField(
-            model_name='gallery',
-            name='sites',
-            field=models.ManyToManyField(to='sites.Site', verbose_name='sites', blank=True),
-        ),
-        migrations.AlterField(
-            model_name='photo',
-            name='sites',
-            field=models.ManyToManyField(to='sites.Site', verbose_name='sites', blank=True),
-        ),
-    ]
diff --git a/photologue/migrations/0008_auto_20150509_1557.py b/photologue/migrations/0008_auto_20150509_1557.py
deleted file mode 100644
index 0baafd2f63d02aadd582d7b3b23e77cee695ff64..0000000000000000000000000000000000000000
--- a/photologue/migrations/0008_auto_20150509_1557.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0007_auto_20150404_1737'),
-    ]
-
-    operations = [
-        migrations.RemoveField(
-            model_name='gallery',
-            name='tags',
-        ),
-        migrations.RemoveField(
-            model_name='photo',
-            name='tags',
-        ),
-    ]
diff --git a/photologue/migrations/0009_auto_20160102_0904.py b/photologue/migrations/0009_auto_20160102_0904.py
deleted file mode 100644
index 4c64f11f1c95606cbd8da2285c03296ba68537ae..0000000000000000000000000000000000000000
--- a/photologue/migrations/0009_auto_20160102_0904.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.9 on 2016-01-02 09:04
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0008_auto_20150509_1557'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='photo',
-            name='date_taken',
-            field=models.DateTimeField(blank=True, help_text='Date image was taken; is obtained from the image EXIF data.', null=True, verbose_name='date taken'),
-        ),
-    ]
diff --git a/photologue/migrations/0010_auto_20160105_1307.py b/photologue/migrations/0010_auto_20160105_1307.py
deleted file mode 100644
index d466cae53fcfd255d7331e5ebef5ff49dacaf7e5..0000000000000000000000000000000000000000
--- a/photologue/migrations/0010_auto_20160105_1307.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.9 on 2016-01-05 13:07
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0009_auto_20160102_0904'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='gallery',
-            name='slug',
-            field=models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', max_length=250, unique=True, verbose_name='title slug'),
-        ),
-        migrations.AlterField(
-            model_name='gallery',
-            name='title',
-            field=models.CharField(max_length=250, unique=True, verbose_name='title'),
-        ),
-        migrations.AlterField(
-            model_name='photo',
-            name='slug',
-            field=models.SlugField(help_text='A "slug" is a unique URL-friendly title for an object.', max_length=250, unique=True, verbose_name='slug'),
-        ),
-        migrations.AlterField(
-            model_name='photo',
-            name='title',
-            field=models.CharField(max_length=250, unique=True, verbose_name='title'),
-        ),
-    ]
diff --git a/photologue/migrations/0011_auto_20190223_2138.py b/photologue/migrations/0011_auto_20190223_2138.py
deleted file mode 100644
index 7bee4ebc31355b9c47c9ddd3248da3997fea57cc..0000000000000000000000000000000000000000
--- a/photologue/migrations/0011_auto_20190223_2138.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.1.7 on 2019-02-23 21:38
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0010_auto_20160105_1307'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='photoeffect',
-            name='filters',
-            field=models.CharField(blank=True, help_text='Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE". Image filters will be applied in order. The following filters are available: BLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, Kernel, SHARPEN, SMOOTH, SMOOTH_MORE.', max_length=200, verbose_name='filters'),
-        ),
-    ]
diff --git a/photologue/migrations/0012_auto_20220129_2207.py b/photologue/migrations/0012_auto_20220129_2207.py
deleted file mode 100644
index 7ed2b9ab1e621274ec9d89d2c1433ebf465c1f01..0000000000000000000000000000000000000000
--- a/photologue/migrations/0012_auto_20220129_2207.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Generated by Django 3.2.11 on 2022-01-29 22:07
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0011_auto_20190223_2138'),
-    ]
-
-    operations = [
-        migrations.RemoveField(
-            model_name='gallery',
-            name='sites',
-        ),
-        migrations.RemoveField(
-            model_name='photo',
-            name='effect',
-        ),
-        migrations.RemoveField(
-            model_name='photo',
-            name='sites',
-        ),
-        migrations.RemoveField(
-            model_name='photosize',
-            name='effect',
-        ),
-        migrations.RemoveField(
-            model_name='photosize',
-            name='watermark',
-        ),
-        migrations.DeleteModel(
-            name='PhotoEffect',
-        ),
-        migrations.DeleteModel(
-            name='Watermark',
-        ),
-    ]
diff --git a/photologue/migrations/0013_alter_gallery_photos.py b/photologue/migrations/0013_alter_gallery_photos.py
deleted file mode 100644
index ab5ecbc65114e5b707404a26d9e50c7189a79858..0000000000000000000000000000000000000000
--- a/photologue/migrations/0013_alter_gallery_photos.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 3.2.11 on 2022-01-30 07:09
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue', '0012_auto_20220129_2207'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='gallery',
-            name='photos',
-            field=models.ManyToManyField(blank=True, related_name='galleries', to='photologue.Photo', verbose_name='photos'),
-        ),
-    ]
diff --git a/photologue/models.py b/photologue/models.py
index f38fbc556102ad38e7f961fd94ae75e761304752..b7e975c634cab0baf08a232c72fb7b253617295e 100644
--- a/photologue/models.py
+++ b/photologue/models.py
@@ -136,8 +136,6 @@ class TagField(models.CharField):
 
 
 class Gallery(models.Model):
-    date_added = models.DateTimeField(_('date published'),
-                                      default=now)
     title = models.CharField(_('title'),
                              max_length=250,
                              unique=True)
@@ -145,8 +143,23 @@ class Gallery(models.Model):
                             unique=True,
                             max_length=250,
                             help_text=_('A "slug" is a unique URL-friendly title for an object.'))
+    date_start = models.DateField(
+        default=now,
+        verbose_name=_("start date"),
+    )
+    date_end = models.DateField(
+        blank=True,
+        null=True,
+        verbose_name=_("end date"),
+    )
     description = models.TextField(_('description'),
                                    blank=True)
+    tags = models.ManyToManyField(
+        'photologue.Tag',
+        related_name='galleries',
+        verbose_name=_('tags'),
+        blank=True,
+    )
     is_public = models.BooleanField(_('is public'),
                                     default=True,
                                     help_text=_('Public galleries will be displayed '
@@ -157,13 +170,13 @@ class Gallery(models.Model):
                                     blank=True)
 
     class Meta:
-        ordering = ['-date_added']
-        get_latest_by = 'date_added'
+        ordering = ['-date_start']
+        get_latest_by = 'date_start'
         verbose_name = _('gallery')
         verbose_name_plural = _('galleries')
 
     def __str__(self):
-        return self.title
+        return f"{ self.title } ({self.date_start})"
 
     def get_absolute_url(self):
         return reverse('photologue:pl-gallery', args=[self.slug])
@@ -478,6 +491,16 @@ class Photo(ImageModel):
                                blank=True)
     date_added = models.DateTimeField(_('date added'),
                                       default=now)
+    owner = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.CASCADE,
+        verbose_name=_("owner"),
+    )
+    license = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name=_("license"),
+    )
     is_public = models.BooleanField(_('is public'),
                                     default=True,
                                     help_text=_('Public photographs will be displayed in the default views.'))
@@ -657,3 +680,25 @@ def init_size_method_map():
             {'base_name': '_get_size_url', 'size': size}
         size_method_map['get_%s_filename' % size] = \
             {'base_name': '_get_size_filename', 'size': size}
+
+
+class Tag(models.Model):
+    name = models.CharField(
+        max_length=250,
+        unique=True,
+        verbose_name=_('name'),
+    )
+    slug = models.SlugField(
+        unique=True,
+        max_length=250,
+        verbose_name=_('slug'),
+        help_text=_('A "slug" is a unique URL-friendly title for an object.'),
+    )
+
+    class Meta:
+        ordering = ['name']
+        verbose_name = _('tag')
+        verbose_name_plural = _('tags')
+
+    def __str__(self):
+        return self.name
diff --git a/photologue_custom/static/lightgallery/css/lg-thumbnail.css b/photologue/static/lightgallery/css/lg-thumbnail.css
similarity index 100%
rename from photologue_custom/static/lightgallery/css/lg-thumbnail.css
rename to photologue/static/lightgallery/css/lg-thumbnail.css
diff --git a/photologue_custom/static/lightgallery/css/lg-zoom.css b/photologue/static/lightgallery/css/lg-zoom.css
similarity index 100%
rename from photologue_custom/static/lightgallery/css/lg-zoom.css
rename to photologue/static/lightgallery/css/lg-zoom.css
diff --git a/photologue_custom/static/lightgallery/css/lightgallery.css b/photologue/static/lightgallery/css/lightgallery.css
similarity index 100%
rename from photologue_custom/static/lightgallery/css/lightgallery.css
rename to photologue/static/lightgallery/css/lightgallery.css
diff --git a/photologue_custom/static/lightgallery/fonts/lg.svg b/photologue/static/lightgallery/fonts/lg.svg
similarity index 100%
rename from photologue_custom/static/lightgallery/fonts/lg.svg
rename to photologue/static/lightgallery/fonts/lg.svg
diff --git a/photologue_custom/static/lightgallery/fonts/lg.ttf b/photologue/static/lightgallery/fonts/lg.ttf
similarity index 100%
rename from photologue_custom/static/lightgallery/fonts/lg.ttf
rename to photologue/static/lightgallery/fonts/lg.ttf
diff --git a/photologue_custom/static/lightgallery/fonts/lg.woff b/photologue/static/lightgallery/fonts/lg.woff
similarity index 100%
rename from photologue_custom/static/lightgallery/fonts/lg.woff
rename to photologue/static/lightgallery/fonts/lg.woff
diff --git a/photologue_custom/static/lightgallery/fonts/lg.woff2 b/photologue/static/lightgallery/fonts/lg.woff2
similarity index 100%
rename from photologue_custom/static/lightgallery/fonts/lg.woff2
rename to photologue/static/lightgallery/fonts/lg.woff2
diff --git a/photologue_custom/static/lightgallery/images/loading.gif b/photologue/static/lightgallery/images/loading.gif
similarity index 100%
rename from photologue_custom/static/lightgallery/images/loading.gif
rename to photologue/static/lightgallery/images/loading.gif
diff --git a/photologue_custom/static/lightgallery/lightgallery.min.js b/photologue/static/lightgallery/lightgallery.min.js
similarity index 100%
rename from photologue_custom/static/lightgallery/lightgallery.min.js
rename to photologue/static/lightgallery/lightgallery.min.js
diff --git a/photologue_custom/static/lightgallery/plugins/admin/lg-admin.js b/photologue/static/lightgallery/plugins/admin/lg-admin.js
similarity index 100%
rename from photologue_custom/static/lightgallery/plugins/admin/lg-admin.js
rename to photologue/static/lightgallery/plugins/admin/lg-admin.js
diff --git a/photologue_custom/static/lightgallery/plugins/hash/lg-hash.min.js b/photologue/static/lightgallery/plugins/hash/lg-hash.min.js
similarity index 100%
rename from photologue_custom/static/lightgallery/plugins/hash/lg-hash.min.js
rename to photologue/static/lightgallery/plugins/hash/lg-hash.min.js
diff --git a/photologue_custom/static/lightgallery/plugins/thumbnail/lg-thumbnail.min.js b/photologue/static/lightgallery/plugins/thumbnail/lg-thumbnail.min.js
similarity index 100%
rename from photologue_custom/static/lightgallery/plugins/thumbnail/lg-thumbnail.min.js
rename to photologue/static/lightgallery/plugins/thumbnail/lg-thumbnail.min.js
diff --git a/photologue_custom/static/lightgallery/plugins/zoom/lg-zoom.min.js b/photologue/static/lightgallery/plugins/zoom/lg-zoom.min.js
similarity index 100%
rename from photologue_custom/static/lightgallery/plugins/zoom/lg-zoom.min.js
rename to photologue/static/lightgallery/plugins/zoom/lg-zoom.min.js
diff --git a/photologue_custom/templates/admin/photologue/photo/change_list.html b/photologue/templates/admin/photologue/photo/change_list.html
similarity index 100%
rename from photologue_custom/templates/admin/photologue/photo/change_list.html
rename to photologue/templates/admin/photologue/photo/change_list.html
diff --git a/photologue_custom/templates/photologue/gallery_archive.html b/photologue/templates/photologue/gallery_archive.html
similarity index 100%
rename from photologue_custom/templates/photologue/gallery_archive.html
rename to photologue/templates/photologue/gallery_archive.html
diff --git a/photologue_custom/templates/photologue/gallery_archive_year.html b/photologue/templates/photologue/gallery_archive_year.html
similarity index 100%
rename from photologue_custom/templates/photologue/gallery_archive_year.html
rename to photologue/templates/photologue/gallery_archive_year.html
diff --git a/photologue_custom/templates/photologue/gallery_detail.html b/photologue/templates/photologue/gallery_detail.html
similarity index 88%
rename from photologue_custom/templates/photologue/gallery_detail.html
rename to photologue/templates/photologue/gallery_detail.html
index 21bc348c84de84aa8b6b0f82c15f6dd7d0f26d7f..1df68af1c2f7aa7b83380523a8336ea2ed31e49e 100644
--- a/photologue_custom/templates/photologue/gallery_detail.html
+++ b/photologue/templates/photologue/gallery_detail.html
@@ -38,10 +38,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
     </a>
     {% endif %}
 </h1>
-{% if gallery.extended.date_start %}<p class="text-muted small">{{ gallery.extended.date_start }}{% if gallery.extended.date_end and gallery.extended.date_end != gallery.extended.date_start %} {% trans "to" %} {{ gallery.extended.date_end }}{% endif %}</p>{% endif %}
-{% if gallery.extended.tags.all %}
+{% 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.extended.tags.all %}
+    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>
@@ -70,7 +70,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
     <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" alt="{{ photo.title }}{% if photo.date_taken %} - {{ photo.date_taken|date }} {{ photo.date_taken|time }}{% endif %} - {{ photo.extended.owner.get_full_name }}{% if photo.extended.license %} - {{ photo.extended.license }}{% endif %}">
+            <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>
diff --git a/photologue_custom/templates/photologue/includes/gallery_sample.html b/photologue/templates/photologue/includes/gallery_sample.html
similarity index 57%
rename from photologue_custom/templates/photologue/includes/gallery_sample.html
rename to photologue/templates/photologue/includes/gallery_sample.html
index abe2bfdc8fc6e01c2f3a14ef0f2cb45e5263b24b..8282074b6369450db851769ee8b037e32a288744 100644
--- a/photologue_custom/templates/photologue/includes/gallery_sample.html
+++ b/photologue/templates/photologue/includes/gallery_sample.html
@@ -6,7 +6,7 @@
     {% endfor %}
     <div class="card-body">
         <h5 class="card-title">{{ gallery.title }}</h5>
-        {% if gallery.extended.date_start %}<p class="card-text text-muted small mb-0">{{ gallery.extended.date_start }}{% if gallery.extended.date_end and gallery.extended.date_end != gallery.extended.date_start %} - {{ gallery.extended.date_end }}{% endif %}</p>{% endif %}
+        {% if gallery.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>
diff --git a/photologue_custom/templates/photologue/photo_detail.html b/photologue/templates/photologue/photo_detail.html
similarity index 100%
rename from photologue_custom/templates/photologue/photo_detail.html
rename to photologue/templates/photologue/photo_detail.html
diff --git a/photologue_custom/templates/taggit/tag_detail.html b/photologue/templates/photologue/tag_detail.html
similarity index 100%
rename from photologue_custom/templates/taggit/tag_detail.html
rename to photologue/templates/photologue/tag_detail.html
diff --git a/photologue_custom/templates/photologue/upload.html b/photologue/templates/photologue/upload.html
similarity index 100%
rename from photologue_custom/templates/photologue/upload.html
rename to photologue/templates/photologue/upload.html
diff --git a/photologue_custom/urls.py b/photologue/urls.py
similarity index 73%
rename from photologue_custom/urls.py
rename to photologue/urls.py
index 037a354eb39f0afab3c7cd1b0ee00f0c915dd0ff..e4949f870cef5046e2bcd83aff8d9a30dd07f44e 100644
--- a/photologue_custom/urls.py
+++ b/photologue/urls.py
@@ -1,10 +1,9 @@
 from django.urls import path, re_path
-from photologue.views import GalleryArchiveIndexView, GalleryYearArchiveView, PhotoDetailView
 
-from .views import CustomGalleryDetailView, GalleryDownload, GalleryUpload, TagDetail
+from .views import (CustomGalleryDetailView, GalleryArchiveIndexView,
+                    GalleryDownload, GalleryUpload, GalleryYearArchiveView,
+                    PhotoDetailView, TagDetail)
 
-# Rather than using photologue default router, we redefine our own router
-# with login and permission checks.
 app_name = 'photologue'
 urlpatterns = [
     path('tag/<slug:slug>/', TagDetail.as_view(), name='tag-detail'),
diff --git a/photologue/views.py b/photologue/views.py
index 93fc8b5233a5a400130ac6e9d383b79b725500d8..77953a89f51899e1a10f680b98611c6a3d7e11a0 100644
--- a/photologue/views.py
+++ b/photologue/views.py
@@ -1,13 +1,31 @@
-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):
     queryset = Gallery.objects.filter(is_public=True)
-    date_field = 'extended__date_start'
+    date_field = 'date_start'
     uses_datetime_field = False  # Fix related object access
     allow_empty = True
 
@@ -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)
diff --git a/photologue_custom/__init__.py b/photologue_custom/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/photologue_custom/admin.py b/photologue_custom/admin.py
deleted file mode 100644
index e8f0130b42b2b67fe7bbda360c6360c91638f1a3..0000000000000000000000000000000000000000
--- a/photologue_custom/admin.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from django.contrib import admin
-from django.utils.translation import gettext_lazy as _
-from photologue.admin import GalleryAdmin as GalleryAdminDefault
-from photologue.admin import PhotoAdmin as PhotoAdminDefault
-from photologue.models import Gallery, Photo
-
-from .models import GalleryExtended, PhotoExtended
-
-
-class GalleryExtendedInline(admin.StackedInline):
-    model = GalleryExtended
-    can_delete = False
-
-
-class GalleryAdmin(GalleryAdminDefault):
-    """
-    Define our new one-to-one model as an inline of Photologue's Gallery
-    model.
-    """
-    inlines = [GalleryExtendedInline, ]
-
-
-class PhotoExtendedInline(admin.StackedInline):
-    model = PhotoExtended
-    can_delete = True
-
-
-class PhotoAdmin(PhotoAdminDefault):
-    """
-    Define our new one-to-one model as an inline of Photologue's Photo
-    model.
-    """
-    inlines = [PhotoExtendedInline, ]
-    list_display = ('title', 'date_taken', 'date_added',
-                    'is_public', 'view_count', 'admin_thumbnail', 'get_owner')
-    list_filter = ['date_added', 'is_public', 'extended__owner']
-
-    def get_owner(self, obj):
-        if not hasattr(obj, 'extended'):
-            return "No owner"
-        return obj.extended.owner.username
-    get_owner.admin_order_field = 'owner'
-    get_owner.short_description = _('owner')
-
-
-admin.site.register(Gallery, GalleryAdmin)
-admin.site.register(Photo, PhotoAdmin)
diff --git a/photologue_custom/apps.py b/photologue_custom/apps.py
deleted file mode 100644
index 55acba8b2213ed038c85d8fcd566e942475ce156..0000000000000000000000000000000000000000
--- a/photologue_custom/apps.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.apps import AppConfig
-
-
-class PhotologueCustomConfig(AppConfig):
-    default_auto_field = 'django.db.models.AutoField'
-    name = 'photologue_custom'
diff --git a/photologue_custom/migrations/0001_initial.py b/photologue_custom/migrations/0001_initial.py
deleted file mode 100644
index 9fb5a1f0d5907794686422ebfdc20022e18e1c45..0000000000000000000000000000000000000000
--- a/photologue_custom/migrations/0001_initial.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Generated by Django 2.2.24 on 2021-10-11 19:12
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import taggit.managers
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-        ('photologue', '0011_auto_20190223_2138'),
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ('taggit', '0002_auto_20150616_2121'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='PhotoExtended',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='owner')),
-                ('photo', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='extented', to='photologue.Photo')),
-            ],
-            options={
-                'verbose_name': 'Extra fields',
-                'verbose_name_plural': 'Extra fields',
-            },
-        ),
-        migrations.CreateModel(
-            name='GalleryExtended',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('gallery', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='extended', to='photologue.Gallery')),
-                ('tags', taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
-            ],
-            options={
-                'verbose_name': 'Extra fields',
-                'verbose_name_plural': 'Extra fields',
-            },
-        ),
-    ]
diff --git a/photologue_custom/migrations/0002_auto_20211011_1956.py b/photologue_custom/migrations/0002_auto_20211011_1956.py
deleted file mode 100644
index d03ea2cc7b5bfbe8e24f5badf698a790efeea4e8..0000000000000000000000000000000000000000
--- a/photologue_custom/migrations/0002_auto_20211011_1956.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 2.2.24 on 2021-10-11 19:56
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue_custom', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='galleryextended',
-            name='date_end',
-            field=models.DateField(blank=True, null=True, verbose_name='end date'),
-        ),
-        migrations.AddField(
-            model_name='galleryextended',
-            name='date_start',
-            field=models.DateField(blank=True, null=True, verbose_name='start date'),
-        ),
-    ]
diff --git a/photologue_custom/migrations/0003_auto_20211013_1507.py b/photologue_custom/migrations/0003_auto_20211013_1507.py
deleted file mode 100644
index a99c067e94e3fc62063c5d92b8901aaf921c6bd9..0000000000000000000000000000000000000000
--- a/photologue_custom/migrations/0003_auto_20211013_1507.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Generated by Django 2.2.24 on 2021-10-13 15:07
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue_custom', '0002_auto_20211011_1956'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='galleryextended',
-            name='gallery',
-            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='extended', to='photologue.Gallery'),
-        ),
-        migrations.AlterField(
-            model_name='photoextended',
-            name='photo',
-            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='extended', to='photologue.Photo'),
-        ),
-    ]
\ No newline at end of file
diff --git a/photologue_custom/migrations/0004_photoextended_license.py b/photologue_custom/migrations/0004_photoextended_license.py
deleted file mode 100644
index 87c68873a5104edac477b0d1e8c12ab107bb2010..0000000000000000000000000000000000000000
--- a/photologue_custom/migrations/0004_photoextended_license.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 2.2.24 on 2021-10-22 16:04
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('photologue_custom', '0003_auto_20211013_1507'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='photoextended',
-            name='license',
-            field=models.CharField(blank=True, max_length=255, verbose_name='license'),
-        ),
-    ]
diff --git a/photologue_custom/migrations/__init__.py b/photologue_custom/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/photologue_custom/models.py b/photologue_custom/models.py
deleted file mode 100644
index 6275e647cc89c66cd7a4d6dec85ffaae688d4dd5..0000000000000000000000000000000000000000
--- a/photologue_custom/models.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from django.db import models
-from django.conf import settings
-from taggit.managers import TaggableManager
-from photologue.models import Gallery, Photo
-from django.utils.translation import gettext_lazy as _
-
-
-class GalleryExtended(models.Model):
-    # Extend Photologue Gallery model.
-    gallery = models.OneToOneField(
-        Gallery,
-        related_name='extended',
-        on_delete=models.CASCADE,
-    )
-
-    # Add tags
-    tags = TaggableManager(blank=True)
-
-    # Add start and end dates fields to GalleryExtend
-    date_start = models.DateField(
-        blank=True,
-        null=True,
-        verbose_name=_("start date"),
-    )
-    date_end = models.DateField(
-        blank=True,
-        null=True,
-        verbose_name=_("end date"),
-    )
-
-    class Meta:
-        verbose_name = 'Extra fields'
-        verbose_name_plural = 'Extra fields'
-
-    def __str__(self):
-        return self.gallery.title
-
-
-class PhotoExtended(models.Model):
-    # Extend Photologue Photo model.
-    photo = models.OneToOneField(
-        Photo,
-        related_name='extended',
-        on_delete=models.CASCADE,
-    )
-
-    # Add a owner field to PhotoExtended
-    owner = models.ForeignKey(
-        settings.AUTH_USER_MODEL,
-        on_delete=models.CASCADE,
-        verbose_name=_("owner"),
-    )
-    license = models.CharField(
-        max_length=255,
-        blank=True,
-        verbose_name=_("license"),
-    )
-
-    class Meta:
-        verbose_name = 'Extra fields'
-        verbose_name_plural = 'Extra fields'
-
-    def __str__(self):
-        return str(self.photo)
diff --git a/photologue_custom/views.py b/photologue_custom/views.py
deleted file mode 100644
index 8a683f0a016883a05c6913eefaf11aeff1315c5a..0000000000000000000000000000000000000000
--- a/photologue_custom/views.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# 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.detail import DetailView
-from django.views.generic.edit import FormView
-from photologue.models import Gallery, Photo
-from PIL import Image
-from taggit.models import Tag
-
-from .forms import UploadForm
-from .models import PhotoExtended
-
-
-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(extended__tags__slug=current_tag) \
-            .order_by('-extended__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 extended and owner to reduce database lag
-        context['photos'] = self.object.public().select_related('extended__owner')
-
-        # List owners
-        context['owners'] = []
-        for photo in context['photos']:
-            if hasattr(photo, 'extended') and photo.extended.owner not in context['owners']:
-                context['owners'].append(photo.extended.owner)
-
-        # Filter on owner
-        if 'owner' in self.kwargs:
-            context['photos'] = context['photos'].filter(extended__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.extended.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))
-                photo_name = str(gallery_dir / photo_file.name)
-                photo.image.save(photo_name, photo_file)
-                photo.save()
-                photo.galleries.set([gallery])
-                PhotoExtended.objects.create(photo=photo, owner=self.request.user)
-            except IntegrityError:
-                messages.error(self.request, f"{photo_file.name} was not uploaded. Maybe the photo was already uploaded.")
-                failed_upload += 1
-
-        # Notify user then managers
-        if not failed_upload:
-            messages.success(self.request, "All photos has been successfully uploaded.")
-        else:
-            n_success = len(files) - failed_upload
-            messages.warning(self.request, f"Only {n_success} photos were successfully uploaded !")
-
-        gallery_title = form.cleaned_data['gallery'] or form.cleaned_data.get('new_gallery_title', '')
-        photos = ", ".join(f.name for f in files)
-        mail_managers(
-            subject="New photos upload",
-            message=f"{self.request.user.username} has uploaded in `{gallery_title}`: {photos}",
-        )
-
-        return super().form_valid(form)
diff --git a/requirements.txt b/requirements.txt
index 9f86e8dea72f2a9565a5589f46bc47c9102ce0f7..cf9dcafa010edc6a312df6b9e5827eb3934d41ac 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,5 @@
 django-allauth>=0.44
 django-crispy-forms~=1.7
-django-taggit>=1.5.0
 Django>=2.2.20
 ExifRead>=2.1.2
 git+https://gitlab.crans.org/bde/allauth-note-kfet.git
diff --git a/tox.ini b/tox.ini
index f69e975e890d04d797059dbee807486c2a8d04e8..885d2da62fc27ec7a6de1eecefa3dc3015369f5f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,7 +12,7 @@ deps =
     -r{toxinidir}/requirements.txt
     coverage
 commands =
-    coverage run --omit='photo21/wsgi.py' --source=photo21,photologue,photologue_custom ./manage.py test
+    coverage run --omit='photo21/wsgi.py' --source=photo21,photologue ./manage.py test
     coverage report -m
 
 [testenv:linters]
@@ -26,7 +26,7 @@ deps =
     pep8-naming
     pyflakes
 commands =
-    flake8 photo21 photologue photologue_custom
+    flake8 photo21 photologue
 
 [flake8]
 ignore = W503, I100, I101