Added model for special galleries.

This commit is contained in:
Miguel Astor
2026-03-24 15:06:48 -04:00
parent 77d83c58d1
commit 5a6632491a
5 changed files with 122 additions and 3 deletions

View File

@@ -8,13 +8,16 @@ import datetime
# Django imports. # Django imports.
from django.http import HttpResponseNotFound from django.http import HttpResponseNotFound
from django.shortcuts import render from django.shortcuts import render, redirect
from django.conf import settings from django.conf import settings
from django.utils import timezone
# Third-party # Third-party
from PIL import Image from PIL import Image
# Project imports. # Project imports.
from .models import Image as Im
from .common import ( from .common import (
normalize_sort, normalize_sort,
normalize_theme, normalize_theme,
@@ -34,6 +37,20 @@ def render_image(request, path_text, full_path):
Renders the view corresponding to an image file. Renders the view corresponding to an image file.
""" """
try:
img = Im.objects.get(path=full_path, user=request.user)
if request.method == 'POST':
img.favorite = not img.favorite
except Im.DoesNotExist:
img = Im(path=full_path, user=request.user)
finally:
img.last_visited = timezone.now()
img.visits = img.visits + 1
img.save()
# Preserve query state (sort, search, theme) similar to gallery view # Preserve query state (sort, search, theme) similar to gallery view
search_text = request.GET.get("search", "").strip() search_text = request.GET.get("search", "").strip()
sort_key = normalize_sort(request.GET.get("sort", "abc")) sort_key = normalize_sort(request.GET.get("sort", "abc"))
@@ -177,6 +194,9 @@ def render_image(request, path_text, full_path):
"filesize": human_size(filesize), "filesize": human_size(filesize),
"created": fmt_ts(created_ts), "created": fmt_ts(created_ts),
"modified": fmt_ts(modified_ts), "modified": fmt_ts(modified_ts),
"visits": img.visits,
"visited": fmt_ts(img.last_visited.timestamp()),
"favorite": img.favorite
}, },
"breadcrumbs": breadcrumbs, "breadcrumbs": breadcrumbs,
"theme": theme, "theme": theme,
@@ -186,6 +206,7 @@ def render_image(request, path_text, full_path):
"theme_toggle_url": gallery_url( "theme_toggle_url": gallery_url(
Path(dir_path_text) if dir_path_text != "" else None, True, theme_query Path(dir_path_text) if dir_path_text != "" else None, True, theme_query
), ),
"path": path_text,
} }
from .common import SORT_LABELS from .common import SORT_LABELS

View File

@@ -0,0 +1,52 @@
# Generated by Django 6.0.3 on 2026-03-24 18:56
import django.db.models.deletion
import django.utils.timezone
import pathlib
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="Image",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"path",
models.FilePathField(
path=pathlib.PurePosixPath("/home/miky/Imágenes")
),
),
("favorite", models.BooleanField(default=False)),
(
"last_visited",
models.DateTimeField(default=django.utils.timezone.now),
),
("visits", models.IntegerField(default=0)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

27
viewer/models.py Normal file
View File

@@ -0,0 +1,27 @@
from django.utils import timezone
from django.conf import settings
from django.db.models import (
Model,
BooleanField,
DateTimeField,
IntegerField,
FilePathField,
ForeignKey,
CASCADE
)
class Image(Model):
"""
User relations to a specific image file by path.
"""
user = ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, on_delete=CASCADE)
path = FilePathField(path=settings.GALLERY_ROOT, blank=False, null=False)
favorite = BooleanField(blank=False, null=False, default=False)
last_visited = DateTimeField(blank=False, null=False, default=timezone.now)
visits = IntegerField(blank=False, null=False, default=0)
class meta:
ordering = ["pk"]
get_latest_by = "-last_visited"

View File

@@ -240,7 +240,7 @@ body {
} }
.info-menu { .info-menu {
min-width: 220px; min-width: 275px;
border-radius: 8px; border-radius: 8px;
} }

View File

@@ -117,12 +117,23 @@
{% endfor %} {% endfor %}
</div> </div>
<form method="post" action="{% url 'gallery_view_path' path=path %}">
{% csrf_token %}
<button class="btn btn-sm btn-plain" type="submit" title="Favorite" aria-label="Fav image" name="favorite">
{% if image_meta.favorite %}
<i class="fa-solid fa-star"></i>
{% else %}
<i class="fa-regular fa-star"></i>
{% endif %}
</button>
</form>
<div class="dropdown ms-auto"> <div class="dropdown ms-auto">
<button class="btn btn-sm btn-plain" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Info" aria-label="Image info"> <button class="btn btn-sm btn-plain" type="button" data-bs-toggle="dropdown" aria-expanded="false" title="Info" aria-label="Image info">
<i class="fa-solid fa-circle-info"></i> <i class="fa-solid fa-circle-info"></i>
</button> </button>
<div class="dropdown-menu dropdown-menu-end info-menu p-2"> <div class="dropdown-menu dropdown-menu-end info-menu p-2">
<div style="max-height:220px; overflow:auto;"> <div style="max-height:300px; overflow:auto;">
<div class="small text-muted">{{ image_meta.filename|truncatechars:40 }}</div> <div class="small text-muted">{{ image_meta.filename|truncatechars:40 }}</div>
{% if image_meta.width and image_meta.height %} {% if image_meta.width and image_meta.height %}
<div>{{ image_meta.width }} x {{ image_meta.height }} px</div> <div>{{ image_meta.width }} x {{ image_meta.height }} px</div>
@@ -138,6 +149,14 @@
<div class="small text-muted">Modification date</div> <div class="small text-muted">Modification date</div>
<div>{{ image_meta.modified }}</div> <div>{{ image_meta.modified }}</div>
{% endif %} {% endif %}
{% if image_meta.visits %}
<div class="small text-muted">Visits:</div>
<div>{{ image_meta.visits }}</div>
{% endif %}
{% if image_meta.visited %}
<div class="small text-muted">Last visit:</div>
<div>{{ image_meta.visited }}</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>