diff --git a/photologue/migrations/0005_add_can_resolve_censorship_permission.py b/photologue/migrations/0005_add_can_resolve_censorship_permission.py new file mode 100644 index 0000000..4393519 --- /dev/null +++ b/photologue/migrations/0005_add_can_resolve_censorship_permission.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.13 on 2026-04-21 09:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('photologue', '0004_alter_photo_options_gallery_public_token'), + ] + + operations = [ + migrations.AlterModelOptions( + name='photo', + options={'get_latest_by': 'date_added', 'ordering': ['title'], 'permissions': [('can_resolve_censorship', 'Can resolve censorship (restore private photos)')], 'verbose_name': 'photo', 'verbose_name_plural': 'photos'}, + ), + ] diff --git a/photologue/models.py b/photologue/models.py index 3ca67b5..a466c92 100644 --- a/photologue/models.py +++ b/photologue/models.py @@ -553,6 +553,9 @@ class Photo(ImageModel): get_latest_by = "date_added" verbose_name = _("photo") verbose_name_plural = _("photos") + permissions = [ + ("can_resolve_censorship", "Can resolve censorship (restore private photos)"), + ] def __str__(self): return self.title diff --git a/photologue/static/lightgallery/plugins/admin/lg-admin.js b/photologue/static/lightgallery/plugins/admin/lg-admin.js index a6748e1..5b198c9 100644 --- a/photologue/static/lightgallery/plugins/admin/lg-admin.js +++ b/photologue/static/lightgallery/plugins/admin/lg-admin.js @@ -12,6 +12,7 @@ class lgAdmin { this.$LG = $LG; this.isStaff = document.querySelector('[name=is_staff]').value === "true"; this.userId = document.querySelector('[name=user_id]').value; + this.canResolveCensorship = document.querySelector('[name=can_resolve_censorship]').value === "true"; this.csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; this.photoId = 0; return this; @@ -35,6 +36,12 @@ class lgAdmin { this.core.$toolbar.append(`${reportIcon}`); document.getElementById("lg-report").addEventListener('click', this.onReport.bind(this)); + // Add button to restore a censored photo + const restoreIcon = ""; + this.core.$toolbar.append(`${restoreIcon}`); + document.getElementById("lg-restore").style.display = 'none'; + document.getElementById("lg-restore").addEventListener('click', this.onRestore.bind(this)); + this.core.LGel.on("lgAfterSlide.admin", this.onAfterSlide.bind(this)); } @@ -46,6 +53,31 @@ class lgAdmin { const ownerId = el ? el.dataset.ownerId : null; const canDelete = this.isStaff || (ownerId && ownerId === this.userId); document.getElementById("lg-delete").style.display = canDelete ? 'block' : 'none'; + const isCensored = el ? el.dataset.isPublic === 'false' : false; + document.getElementById("lg-restore").style.display = (this.canResolveCensorship && isCensored) ? 'block' : 'none'; + } + + // Event called when user clicks the restore (uncensor) button + onRestore(event) { + event.preventDefault(); + if(confirm("Restore this photo and make it public again?")) { + const photoId = this.photoId; + let data = new FormData(); + data.append('csrfmiddlewaretoken', this.csrfToken); + fetch(`/photo/${photoId}/uncensor/`, { + method: "POST", + body: data, + credentials: 'same-origin', + }).then(() => { + const el = document.querySelector(`[data-slide-name='${photoId}']`); + if (el) { + el.dataset.isPublic = 'true'; + const img = el.querySelector('img'); + if (img) img.classList.remove('border-danger', 'border-5'); + } + document.getElementById("lg-restore").style.display = 'none'; + }); + } } // Navigate away from a photo that was just deleted/hidden. diff --git a/photologue/templates/photologue/gallery_detail.html b/photologue/templates/photologue/gallery_detail.html index 9771b23..a8b9d18 100644 --- a/photologue/templates/photologue/gallery_detail.html +++ b/photologue/templates/photologue/gallery_detail.html @@ -19,6 +19,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% csrf_token %} + @@ -88,7 +89,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %}