From 69f9848da8eb0ab34c9f7a404f403449ddd49b7e Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Sun, 25 May 2025 16:04:07 -0500 Subject: [PATCH 01/16] Add missing migration dependency file --- .gitignore | 4 +-- apps/gallery/migrations/0001_initial.py | 41 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 apps/gallery/migrations/0001_initial.py diff --git a/.gitignore b/.gitignore index d926c6e..34e0a4f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,8 @@ venv/ *.JPEG /static/lightbox/images/ /static/ckeditor/ckeditor/skins/ -apps/*/migrations/*.py -!**/migrations/__init__.py +# apps/*/migrations/*.py +# !**/migrations/__init__.py .DS_Store db.sqlite3 .idea/ diff --git a/apps/gallery/migrations/0001_initial.py b/apps/gallery/migrations/0001_initial.py new file mode 100644 index 0000000..4acbd36 --- /dev/null +++ b/apps/gallery/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# Generated by Django 5.2 on 2025-05-22 20:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("photologue", "0013_alter_watermark_image"), + ] + + operations = [ + migrations.CreateModel( + name="PhotoGallery", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("country", models.CharField(max_length=199)), + ("content", models.TextField()), + ( + "slug", + models.SlugField(allow_unicode=True, blank=True, max_length=250), + ), + ("galleries", models.ManyToManyField(to="photologue.gallery")), + ], + options={ + "verbose_name": "Gallery", + "verbose_name_plural": "Galleries", + "ordering": ["country"], + }, + ), + ] From 5fa1cb512e6d5240528b6f0eb4a0981cd56cb8d7 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Sun, 25 May 2025 16:10:47 -0500 Subject: [PATCH 02/16] Fix debug not being set by environment variables. --- prokope/settings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prokope/settings.py b/prokope/settings.py index 31efe93..9717424 100755 --- a/prokope/settings.py +++ b/prokope/settings.py @@ -19,8 +19,7 @@ SECRET_KEY = os.environ["SECRET_KEY"] # SECURITY WARNING: don't run with debug turned on in production! -# DEBUG = os.environ.get("ENVIRONMENT", "development") == "development" -DEBUG = True +DEBUG = os.environ.get("ENVIRONMENT", "development") == "development" # The `DYNO` env var is set on Heroku CI, but it's not a real Heroku app, so we have to # also explicitly exclude CI: From ea912326dcda2c22350d9e7865d8069eb67d6645 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Sun, 25 May 2025 17:06:37 -0500 Subject: [PATCH 03/16] Update migrations --- apps/blog/migrations/0001_initial.py | 89 +++++++++++++++++++ .../0002_rename_photogallery_countryalbum.py | 18 ---- .../0003_rename_countryalbum_photogallery.py | 18 ---- apps/index/migrations/0001_initial.py | 85 ++++++++++++++++++ 4 files changed, 174 insertions(+), 36 deletions(-) create mode 100644 apps/blog/migrations/0001_initial.py delete mode 100644 apps/gallery/migrations/0002_rename_photogallery_countryalbum.py delete mode 100644 apps/gallery/migrations/0003_rename_countryalbum_photogallery.py create mode 100644 apps/index/migrations/0001_initial.py diff --git a/apps/blog/migrations/0001_initial.py b/apps/blog/migrations/0001_initial.py new file mode 100644 index 0000000..5af108f --- /dev/null +++ b/apps/blog/migrations/0001_initial.py @@ -0,0 +1,89 @@ +# Generated by Django 5.2 on 2025-05-25 21:49 + +import django.db.models.deletion +import django.utils.timezone +import taggit.managers +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ( + "taggit", + "0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx", + ), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Contact", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(default="", max_length=255)), + ("email", models.EmailField(max_length=254)), + ("subject", models.CharField(max_length=255)), + ("message", models.TextField()), + ], + ), + migrations.CreateModel( + name="Post", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=50, unique=True)), + ("slug", models.SlugField(unique=True)), + ("updated_on", models.DateField(default=django.utils.timezone.now)), + ("created_on", models.DateField(default=django.utils.timezone.now)), + ("content", models.TextField()), + ( + "status", + models.IntegerField( + choices=[(0, "Draft"), (1, "Publish")], default=0 + ), + ), + ("thumb", models.ImageField(blank=True, upload_to="")), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="blog_posts", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "tag", + taggit.managers.TaggableManager( + help_text="A comma-separated list of tags.", + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="Tags", + ), + ), + ], + options={ + "verbose_name": "Blog", + "verbose_name_plural": "Blogs", + "ordering": ["-created_on"], + }, + ), + ] diff --git a/apps/gallery/migrations/0002_rename_photogallery_countryalbum.py b/apps/gallery/migrations/0002_rename_photogallery_countryalbum.py deleted file mode 100644 index 57181cc..0000000 --- a/apps/gallery/migrations/0002_rename_photogallery_countryalbum.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2 on 2025-05-25 20:40 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("gallery", "0001_initial"), - ("photologue", "0013_alter_watermark_image"), - ] - - operations = [ - migrations.RenameModel( - old_name="PhotoGallery", - new_name="CountryAlbum", - ), - ] diff --git a/apps/gallery/migrations/0003_rename_countryalbum_photogallery.py b/apps/gallery/migrations/0003_rename_countryalbum_photogallery.py deleted file mode 100644 index 532995f..0000000 --- a/apps/gallery/migrations/0003_rename_countryalbum_photogallery.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2 on 2025-05-25 21:27 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("gallery", "0002_rename_photogallery_countryalbum"), - ("photologue", "0013_alter_watermark_image"), - ] - - operations = [ - migrations.RenameModel( - old_name="CountryAlbum", - new_name="PhotoGallery", - ), - ] diff --git a/apps/index/migrations/0001_initial.py b/apps/index/migrations/0001_initial.py new file mode 100644 index 0000000..2d884af --- /dev/null +++ b/apps/index/migrations/0001_initial.py @@ -0,0 +1,85 @@ +# Generated by Django 5.2 on 2025-05-25 21:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Index", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "hero_banner", + models.ImageField( + default="/images/default/default_hero_banner.png", + upload_to="images", + ), + ), + ( + "hero_image", + models.ImageField( + default="/images/default/default_hero_image.png", + upload_to="images", + ), + ), + ( + "greeting_title", + models.CharField( + default="Welcome to Prokope.io!", max_length=200, null=True + ), + ), + ( + "greeting_description", + models.TextField( + default="My personal portfolio for my thoughts and achievements.", + null=True, + ), + ), + ( + "about_me_title", + models.CharField(default="About Jay", max_length=200, null=True), + ), + ( + "about_me_description", + models.TextField( + default=( + "I'm from Kansas, served in the Marines, and work as a software ", + "developer intern while studying at Kansas State University", + ), + null=True, + ), + ), + ( + "about_prokope_title", + models.CharField( + default="What is 'Prokope'", max_length=200, null=True + ), + ), + ( + "about_prokope_description", + models.TextField( + default="Prokope means 'to chop down what gets in the way'", + null=True, + ), + ), + ], + options={ + "verbose_name": "Index", + "verbose_name_plural": "Index", + }, + ), + ] From f4d345919e14c39a75823d3f13570f4852dcbfb8 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Sun, 25 May 2025 17:13:07 -0500 Subject: [PATCH 04/16] Update migrations --- apps/gallery/admin.py | 3 +- .../0002_rename_photogallery_countryalbum.py | 18 ++ .../0003_rename_countryalbum_photogallery.py | 18 ++ ...hoto_city_country_countryalbum_and_more.py | 196 ++++++++++++++++++ .../migrations/0005_delete_photogallery.py | 16 ++ apps/gallery/models.py | 21 -- apps/index/views.py | 1 - 7 files changed, 249 insertions(+), 24 deletions(-) create mode 100644 apps/gallery/migrations/0002_rename_photogallery_countryalbum.py create mode 100644 apps/gallery/migrations/0003_rename_countryalbum_photogallery.py create mode 100644 apps/gallery/migrations/0004_city_country_cityphoto_city_country_countryalbum_and_more.py create mode 100644 apps/gallery/migrations/0005_delete_photogallery.py diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 00d4014..6529357 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -9,8 +9,7 @@ CityGallery, CityPhoto, City, - Country, - PhotoGallery + Country ) from photologue.admin import ( PhotoAdmin as BasePhotoAdmin diff --git a/apps/gallery/migrations/0002_rename_photogallery_countryalbum.py b/apps/gallery/migrations/0002_rename_photogallery_countryalbum.py new file mode 100644 index 0000000..57181cc --- /dev/null +++ b/apps/gallery/migrations/0002_rename_photogallery_countryalbum.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-05-25 20:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("gallery", "0001_initial"), + ("photologue", "0013_alter_watermark_image"), + ] + + operations = [ + migrations.RenameModel( + old_name="PhotoGallery", + new_name="CountryAlbum", + ), + ] diff --git a/apps/gallery/migrations/0003_rename_countryalbum_photogallery.py b/apps/gallery/migrations/0003_rename_countryalbum_photogallery.py new file mode 100644 index 0000000..532995f --- /dev/null +++ b/apps/gallery/migrations/0003_rename_countryalbum_photogallery.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2 on 2025-05-25 21:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("gallery", "0002_rename_photogallery_countryalbum"), + ("photologue", "0013_alter_watermark_image"), + ] + + operations = [ + migrations.RenameModel( + old_name="CountryAlbum", + new_name="PhotoGallery", + ), + ] diff --git a/apps/gallery/migrations/0004_city_country_cityphoto_city_country_countryalbum_and_more.py b/apps/gallery/migrations/0004_city_country_cityphoto_city_country_countryalbum_and_more.py new file mode 100644 index 0000000..bb1169e --- /dev/null +++ b/apps/gallery/migrations/0004_city_country_cityphoto_city_country_countryalbum_and_more.py @@ -0,0 +1,196 @@ +# Generated by Django 5.2 on 2025-05-25 21:49 + +import django.db.models.deletion +import sortedm2m.fields +import taggit.managers +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gallery", "0003_rename_countryalbum_photogallery"), + ("photologue", "0013_alter_watermark_image"), + ( + "taggit", + "0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx", + ), + ] + + operations = [ + migrations.CreateModel( + name="City", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ], + options={ + "verbose_name_plural": "Cities", + "ordering": ["name"], + }, + ), + migrations.CreateModel( + name="Country", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100, unique=True)), + ], + options={ + "verbose_name_plural": "Countries", + "ordering": ["name"], + }, + ), + migrations.CreateModel( + name="CityPhoto", + fields=[ + ( + "photo_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="photologue.photo", + ), + ), + ( + "city", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="gallery.city", + ), + ), + ( + "country", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="gallery.country", + ), + ), + ], + options={ + "verbose_name": "City Photo", + "verbose_name_plural": "City Photos", + }, + bases=("photologue.photo",), + ), + migrations.AddField( + model_name="city", + name="country", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="cities", + to="gallery.country", + ), + ), + migrations.CreateModel( + name="CountryAlbum", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=50, unique=True)), + ("slug", models.SlugField(unique=True)), + ( + "country", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="album", + to="gallery.country", + ), + ), + ( + "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": "Country Album", + "verbose_name_plural": "Country Albums", + }, + ), + migrations.CreateModel( + name="CityGallery", + fields=[ + ( + "gallery_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="photologue.gallery", + ), + ), + ( + "city", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="gallery", + to="gallery.city", + ), + ), + ( + "city_photos", + sortedm2m.fields.SortedManyToManyField( + blank=True, + help_text=None, + related_name="city_gallery", + to="gallery.cityphoto", + verbose_name="photos", + ), + ), + ( + "album", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="city_galleries", + to="gallery.countryalbum", + ), + ), + ], + options={ + "verbose_name": "City Gallery", + "verbose_name_plural": "City Galleries", + }, + bases=("photologue.gallery",), + ), + migrations.AlterUniqueTogether( + name="city", + unique_together={("name", "country")}, + ), + ] diff --git a/apps/gallery/migrations/0005_delete_photogallery.py b/apps/gallery/migrations/0005_delete_photogallery.py new file mode 100644 index 0000000..98ec2f6 --- /dev/null +++ b/apps/gallery/migrations/0005_delete_photogallery.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2 on 2025-05-25 22:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("gallery", "0004_city_country_cityphoto_city_country_countryalbum_and_more"), + ] + + operations = [ + migrations.DeleteModel( + name="PhotoGallery", + ), + ] diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 7888a2e..c341229 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -10,27 +10,6 @@ from sortedm2m.fields import SortedManyToManyField -class PhotoGallery(models.Model): - """Photo gallery model.""" - - # TODO: Change format to get Photos directly from - # Photologue, then generate new to enforce pagination - galleries = models.ManyToManyField(Gallery) - country = models.CharField(max_length=199) - content = models.TextField() - slug = models.SlugField(max_length=250, allow_unicode=True, blank=True) - - class Meta: - """Metadata for PhotoGallery class.""" - ordering = ["country"] - verbose_name = "Gallery" - verbose_name_plural = "Galleries" - - def __str__(self): - """String representation of name.""" - return f"{self.country}" - - class Country(models.Model): """A specified country.""" name = models.CharField(max_length=100, unique=True) diff --git a/apps/index/views.py b/apps/index/views.py index 82146fb..f144cc9 100644 --- a/apps/index/views.py +++ b/apps/index/views.py @@ -9,7 +9,6 @@ from apps.index.models import Index from apps.blog.models import Post -from apps.gallery.models import PhotoGallery def index_view(request: HttpRequest) -> HttpResponse: From e07a09ac032c3509c0beb8579ffde1b2c57b4a44 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 09:59:44 -0500 Subject: [PATCH 05/16] Temporarily comment out CityPhotoAdmin visibility in admin --- apps/gallery/admin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 6529357..d962077 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -61,8 +61,8 @@ def get_model_perms(self, request): class CityPhotoAdmin(BasePhotoAdmin): autocomplete_fields = ['country', 'city'] - def get_model_perms(self, request): - """ - Return empty perms dict, hiding the model from admin index. - """ - return {} + # def get_model_perms(self, request): + # """ + # Return empty perms dict, hiding the model from admin index. + # """ + # return {} From ab140689d359822f801597241469da68c462a769 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 10:12:02 -0500 Subject: [PATCH 06/16] Add upload_zip functionality to admin for CityPhoto --- apps/gallery/admin.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index d962077..47816c1 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -3,6 +3,12 @@ Author: Jared Paubel Version: 0.1 """ +from django.urls import path +from django.shortcuts import render +from django.contrib import helpers +from photologue.forms import UploadZipForm +from django.http import HttpResponseRedirect +from django.utils.translation import gettext_lazy as _ from django.contrib import admin from apps.gallery.models import ( CountryAlbum, @@ -66,3 +72,34 @@ class CityPhotoAdmin(BasePhotoAdmin): # Return empty perms dict, hiding the model from admin index. # """ # return {} + + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path('upload_zip/', + self.admin_site.admin_view(self.upload_zip), + name='photologue_upload_zip') + ] + return custom_urls + urls + + def upload_zip(self, request): + context = { + 'title': _('Upload a zip archive of photos'), + 'app_label': self.model._meta.app_label, + 'opts': self.model._meta, + 'has_change_permission': self.has_change_permission(request) + } + + # Handle form request + if request.method == 'POST': + form = UploadZipForm(request.POST, request.FILES) + if form.is_valid(): + form.save(request=request) + return HttpResponseRedirect('..') + else: + form = UploadZipForm() + context['form'] = form + context['adminform'] = helpers.AdminForm(form, + list([(None, {'fields': form.base_fields})]), + {}) + return render(request, 'admin/photologue/photo/upload_zip.html', context) From e8966aa37c5dac9f420abb9b7e450c603afc6223 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 10:13:23 -0500 Subject: [PATCH 07/16] Minor change to import statement --- apps/gallery/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 47816c1..19696db 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -5,7 +5,6 @@ """ from django.urls import path from django.shortcuts import render -from django.contrib import helpers from photologue.forms import UploadZipForm from django.http import HttpResponseRedirect from django.utils.translation import gettext_lazy as _ @@ -83,6 +82,7 @@ def get_urls(self): return custom_urls + urls def upload_zip(self, request): + from django.contrib import helpers # Lazy load context = { 'title': _('Upload a zip archive of photos'), 'app_label': self.model._meta.app_label, From b8a674de670e256803c8cb8bb485358fef79cb76 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 10:20:10 -0500 Subject: [PATCH 08/16] Remove overridden methods --- apps/gallery/admin.py | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 19696db..59d2e97 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -71,35 +71,3 @@ class CityPhotoAdmin(BasePhotoAdmin): # Return empty perms dict, hiding the model from admin index. # """ # return {} - - def get_urls(self): - urls = super().get_urls() - custom_urls = [ - path('upload_zip/', - self.admin_site.admin_view(self.upload_zip), - name='photologue_upload_zip') - ] - return custom_urls + urls - - def upload_zip(self, request): - from django.contrib import helpers # Lazy load - context = { - 'title': _('Upload a zip archive of photos'), - 'app_label': self.model._meta.app_label, - 'opts': self.model._meta, - 'has_change_permission': self.has_change_permission(request) - } - - # Handle form request - if request.method == 'POST': - form = UploadZipForm(request.POST, request.FILES) - if form.is_valid(): - form.save(request=request) - return HttpResponseRedirect('..') - else: - form = UploadZipForm() - context['form'] = form - context['adminform'] = helpers.AdminForm(form, - list([(None, {'fields': form.base_fields})]), - {}) - return render(request, 'admin/photologue/photo/upload_zip.html', context) From d8cd5d8436399f031f3ee23973afddfec9d8916d Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 10:49:16 -0500 Subject: [PATCH 09/16] Override photologue photo zip upload for CityPhoto --- apps/gallery/admin.py | 71 ++++++++++++++++--- .../admin/gallery/cityphoto/change_list.html | 7 ++ templates/admin/gallery/upload_zip.html | 55 ++++++++++++++ 3 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 templates/admin/gallery/cityphoto/change_list.html create mode 100644 templates/admin/gallery/upload_zip.html diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 59d2e97..9ae6df1 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -3,11 +3,14 @@ Author: Jared Paubel Version: 0.1 """ -from django.urls import path -from django.shortcuts import render -from photologue.forms import UploadZipForm -from django.http import HttpResponseRedirect +from django.urls import path, reverse +from django.shortcuts import render, redirect +from django.contrib import messages +from django import forms from django.utils.translation import gettext_lazy as _ +from photologue.forms import UploadZipForm +from zipfile import ZipFile +from io import BytesIO from django.contrib import admin from apps.gallery.models import ( CountryAlbum, @@ -16,9 +19,7 @@ City, Country ) -from photologue.admin import ( - PhotoAdmin as BasePhotoAdmin -) +from photologue.admin import PhotoAdmin class CityGalleryInline(admin.StackedInline): @@ -62,10 +63,64 @@ def get_model_perms(self, request): return {} +class CityPhotoZipUploadForm(UploadZipForm): + """Form for uploading a zip file as CityPhoto objects.""" + city = forms.ModelChoiceField( + queryset=City.objects.all(), + label=_("City"), + required=False + ) + country = forms.ModelChoiceField( + queryset=Country.objects.all(), + label=_("Country"), + required=False + ) + + @admin.register(CityPhoto) -class CityPhotoAdmin(BasePhotoAdmin): +class CityPhotoAdmin(PhotoAdmin): autocomplete_fields = ['country', 'city'] + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path( + 'upload_zip/', + self.admin_site.admin_view(self.upload_zip), + name='gallery_cityphoto_upload_zip', + ), + ] + return custom_urls + urls + + def changelist_view(self, request, extra_context=None): + if extra_context is None: + extra_context = {} + extra_context['upload_zip_url'] = reverse('admin:gallery_cityphoto_upload_zip') + return super().changelist_view(request, extra_context=extra_context) + + def upload_zip(self, request): + if request.method == 'POST': + form = CityPhotoZipUploadForm(request.POST, request.FILES) + if form.is_valid(): + city = form.cleaned_data.get('city') + country = form.cleaned_data.get('country') + zip_file = request.FILES['zip_file'] + with ZipFile(zip_file) as archive: + for filename in archive.namelist(): + if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): + data = archive.read(filename) + photo = CityPhoto() + photo.title = filename + photo.image.save(filename, BytesIO(data)) + photo.city = city + photo.country = country + photo.save() + messages.success(request, _("Photos uploaded successfully.")) + return redirect('admin:gallery_cityphoto_changelist') + else: + form = CityPhotoZipUploadForm() + return render(request, 'admin/gallery/upload_zip.html', {'form': form}) + # def get_model_perms(self, request): # """ # Return empty perms dict, hiding the model from admin index. diff --git a/templates/admin/gallery/cityphoto/change_list.html b/templates/admin/gallery/cityphoto/change_list.html new file mode 100644 index 0000000..c9e9418 --- /dev/null +++ b/templates/admin/gallery/cityphoto/change_list.html @@ -0,0 +1,7 @@ +{% extends "admin/change_list.html" %} +{% block object-tools-items %} + {{ block.super }} +
On this page you can upload many photos at once, as long as you have + put them all in a zip archive. The photos can be either:
++ {% if form.errors|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} +
+ {{ form.non_field_errors }} + {% endif %} + + + +{% endblock %} From 9f188e017d1fbfb31ed9d19747d91aa3d1f26ae6 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 10:54:08 -0500 Subject: [PATCH 10/16] Add request context --- apps/gallery/admin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 9ae6df1..cbcfc9f 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -99,6 +99,9 @@ def changelist_view(self, request, extra_context=None): return super().changelist_view(request, extra_context=extra_context) def upload_zip(self, request): + opts = self.model._meta + app_label = opts.app_label + if request.method == 'POST': form = CityPhotoZipUploadForm(request.POST, request.FILES) if form.is_valid(): @@ -119,7 +122,14 @@ def upload_zip(self, request): return redirect('admin:gallery_cityphoto_changelist') else: form = CityPhotoZipUploadForm() - return render(request, 'admin/gallery/upload_zip.html', {'form': form}) + + context = { + 'form': form, + 'opts': opts, + 'app_label': app_label, + 'has_change_permission': self.has_change_permission(request), + } + return render(request, 'admin/gallery/upload_zip.html', context) # def get_model_perms(self, request): # """ From 4357c6c237fd429a60aa9a93c4b5239cc58f6ffc Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Mon, 26 May 2025 11:00:00 -0500 Subject: [PATCH 11/16] Add city gallery selection field for zip upload form --- apps/gallery/admin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index cbcfc9f..d1ed075 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -65,6 +65,11 @@ def get_model_perms(self, request): class CityPhotoZipUploadForm(UploadZipForm): """Form for uploading a zip file as CityPhoto objects.""" + gallery = forms.ModelChoiceField( + queryset=CityGallery.objects.select_related('city', 'album__country').all(), + label=_("City Gallery"), + required=True + ) city = forms.ModelChoiceField( queryset=City.objects.all(), label=_("City"), From 1afa1e1e7193a0e9df88fd42f469ad665ec5ce75 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Tue, 27 May 2025 10:09:27 -0500 Subject: [PATCH 12/16] Update overridden save method in CityPhoto to prevent further IntegrityErrors when uploading photos in bulk. --- apps/gallery/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/gallery/models.py b/apps/gallery/models.py index c341229..8b6a183 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -65,7 +65,13 @@ def save(self, *args, **kwargs): if not self.title: self.title = "{}, {}".format(self.city, self.country) if not self.slug: - self.slug = "{} {}".format(self.city, self.country) + base_slug = slugify("{}-{}".format(self.city, self.country)) + slug = base_slug + counter = 1 + while CityPhoto.objects.filter(slug=slug).exists(): + slug = "{}-{}".format(base_slug, counter) + counter += 1 + self.slug = slug super().save(*args, **kwargs) From d65b9ad34cf19a95a68eb7f4f1880833c82b7983 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Tue, 27 May 2025 10:23:27 -0500 Subject: [PATCH 13/16] Change upload zip automation of saving photos to set the title as the city name with an incrementor --- apps/gallery/admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index d1ed075..1ff9df3 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -114,11 +114,12 @@ def upload_zip(self, request): country = form.cleaned_data.get('country') zip_file = request.FILES['zip_file'] with ZipFile(zip_file) as archive: - for filename in archive.namelist(): + for idx, filename in enumerate(archive.namelist()): + idx += 1 # Humanize index. if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): data = archive.read(filename) photo = CityPhoto() - photo.title = filename + photo.title = "{} {}".format(city.name, idx) photo.image.save(filename, BytesIO(data)) photo.city = city photo.country = country From 40dbf6f84996ac3c3332c6bf9c1efbc4640c55cb Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Tue, 27 May 2025 11:06:00 -0500 Subject: [PATCH 14/16] Add setting of photo slug in upload_zip to keep indices consistent during saves. --- apps/gallery/admin.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 1ff9df3..698e3bf 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -3,6 +3,7 @@ Author: Jared Paubel Version: 0.1 """ +from django.utils.text import slugify from django.urls import path, reverse from django.shortcuts import render, redirect from django.contrib import messages @@ -100,7 +101,9 @@ def get_urls(self): def changelist_view(self, request, extra_context=None): if extra_context is None: extra_context = {} - extra_context['upload_zip_url'] = reverse('admin:gallery_cityphoto_upload_zip') + extra_context['upload_zip_url'] = reverse( + 'admin:gallery_cityphoto_upload_zip' + ) return super().changelist_view(request, extra_context=extra_context) def upload_zip(self, request): @@ -114,12 +117,21 @@ def upload_zip(self, request): country = form.cleaned_data.get('country') zip_file = request.FILES['zip_file'] with ZipFile(zip_file) as archive: - for idx, filename in enumerate(archive.namelist()): - idx += 1 # Humanize index. - if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): + for idx, filename in enumerate( + archive.namelist(), + start=1 + ): + if filename.lower().endswith( + ('.jpg', '.jpeg', '.png', '.gif') + ): data = archive.read(filename) photo = CityPhoto() photo.title = "{} {}".format(city.name, idx) + photo.slug = slugify("{}-{}-{}".format( + self.city, + self.country, + idx + )) photo.image.save(filename, BytesIO(data)) photo.city = city photo.country = country From ade7a2c314d35305e96d209d2a3226105ef3dd19 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Tue, 27 May 2025 11:08:49 -0500 Subject: [PATCH 15/16] Minor fix --- apps/gallery/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index 698e3bf..c0c266a 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -128,8 +128,8 @@ def upload_zip(self, request): photo = CityPhoto() photo.title = "{} {}".format(city.name, idx) photo.slug = slugify("{}-{}-{}".format( - self.city, - self.country, + city, + country, idx )) photo.image.save(filename, BytesIO(data)) From 68f6e061d0eb183dbfddfc68dfc13f12c1ad5187 Mon Sep 17 00:00:00 2001 From: Jay <52184578+Prokope45@users.noreply.github.com> Date: Tue, 27 May 2025 11:12:47 -0500 Subject: [PATCH 16/16] Remove setting of photo slug during upload zip --- apps/gallery/admin.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index c0c266a..eeadf30 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -127,11 +127,6 @@ def upload_zip(self, request): data = archive.read(filename) photo = CityPhoto() photo.title = "{} {}".format(city.name, idx) - photo.slug = slugify("{}-{}-{}".format( - city, - country, - idx - )) photo.image.save(filename, BytesIO(data)) photo.city = city photo.country = country