Add Photo model & templates
This commit is contained in:
parent
9a483b2a7f
commit
f04beb3aaa
9 changed files with 123 additions and 11 deletions
|
@ -37,6 +37,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'imagekit',
|
||||
'gallery'
|
||||
]
|
||||
|
||||
|
@ -121,6 +122,12 @@ STATICFILES_DIRS = [
|
|||
BASE_DIR / 'static',
|
||||
]
|
||||
|
||||
|
||||
# Media files
|
||||
|
||||
MEDIA_URL = 'media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||
|
||||
|
|
|
@ -26,3 +26,4 @@ urlpatterns = [
|
|||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from imagekit.admin import AdminThumbnail
|
||||
|
||||
from gallery.models import Album, City, Location
|
||||
|
||||
# Register your models here.
|
||||
from gallery.models import Album, City, Location, Photo
|
||||
|
||||
|
||||
class CityAdmin(admin.ModelAdmin):
|
||||
|
@ -19,6 +18,7 @@ class LocationAdmin(admin.ModelAdmin):
|
|||
search_fields = ('place', 'city__name',)
|
||||
list_per_page = 30
|
||||
|
||||
|
||||
class AlbumAdmin(admin.ModelAdmin):
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
list_display = ('__str__',)
|
||||
|
@ -27,7 +27,15 @@ class AlbumAdmin(admin.ModelAdmin):
|
|||
list_per_page = 30
|
||||
|
||||
|
||||
class PhotoAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'album', 'admin_thumbnail',)
|
||||
list_display_links = ('__str__',)
|
||||
list_editable = ('album',)
|
||||
readonly_fields = ['slug', 'taken_at', 'height', 'width', 'exif', ]
|
||||
admin_thumbnail = AdminThumbnail(image_field='photo_thumbnail',)
|
||||
|
||||
|
||||
admin.site.register(City, CityAdmin)
|
||||
admin.site.register(Location, LocationAdmin)
|
||||
admin.site.register(Album, AlbumAdmin)
|
||||
|
||||
admin.site.register(Photo, PhotoAdmin)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Generated by Django 5.1.4 on 2024-12-29 21:23
|
||||
# Generated by Django 5.1.4 on 2025-01-05 15:24
|
||||
|
||||
import datetime
|
||||
import django.db.models.deletion
|
||||
import gallery.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@ -49,4 +50,17 @@ class Migration(migrations.Migration):
|
|||
'verbose_name_plural': 'Albums',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Photo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('slug', models.CharField(editable=False, max_length=15, verbose_name='Photo Slug')),
|
||||
('photo', models.ImageField(height_field='height', upload_to=gallery.models.Photo._get_upload_path, verbose_name='Photo', width_field='width')),
|
||||
('width', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Width')),
|
||||
('height', models.PositiveIntegerField(default=0, editable=False, verbose_name='Photo Height')),
|
||||
('taken_at', models.DateTimeField(blank=True, editable=False, null=True, verbose_name='Taken at')),
|
||||
('exif', models.JSONField(blank=True, editable=False, null=True, verbose_name='Exif Metadata')),
|
||||
('album', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='gallery.album', verbose_name='Album')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
from datetime import datetime
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from imagekit.models import ImageSpecField
|
||||
from imagekit.processors import ResizeToFill
|
||||
from PIL import Image
|
||||
|
||||
from gallery.exif import Exif
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
@ -53,3 +59,40 @@ class Album(models.Model):
|
|||
def __str__(self):
|
||||
return '{}'.format(self.name)
|
||||
|
||||
|
||||
class Photo(models.Model):
|
||||
|
||||
def _get_upload_path(instance, filename):
|
||||
return os.path.join('albums', str(instance.album.slug), filename)
|
||||
|
||||
def _generate_unique_slug(self, datetime_taken=None):
|
||||
datetime_taken = datetime_taken or datetime.now()
|
||||
slug = datetime_taken.strftime('%y%m%d%H%M%S')
|
||||
counter = 0
|
||||
while Photo.objects.filter(album=self.album, slug=slug).exists():
|
||||
counter += 1
|
||||
slug = (datetime_taken + timedelta(seconds=counter)).strftime('%y%m%d%H%M%S')
|
||||
return slug
|
||||
|
||||
album = models.ForeignKey(Album, on_delete=models.CASCADE, related_name='photos', verbose_name="Album")
|
||||
slug = models.CharField(max_length=15, editable=False, verbose_name="Photo Slug")
|
||||
photo = models.ImageField(upload_to=_get_upload_path, height_field='height', width_field='width', verbose_name="Photo")
|
||||
photo_thumbnail = ImageSpecField(source='photo', processors=[ResizeToFill(100, 100)], format='JPEG', options={'quality': 70})
|
||||
width = models.PositiveIntegerField(default=0, editable=False, verbose_name="Photo Width")
|
||||
height = models.PositiveIntegerField(default=0, editable=False, verbose_name="Photo Height")
|
||||
taken_at = models.DateTimeField(blank=True, null=True, editable=False, verbose_name="Taken at")
|
||||
exif = models.JSONField(blank=True, null=True, editable=False, verbose_name="Exif Metadata")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.exif_data = Exif(self.photo.file)
|
||||
datetime_taken = getattr(self.exif_data, 'datetimeoriginal', datetime.now)()
|
||||
self.slug = self.slug or self._generate_unique_slug(datetime_taken)
|
||||
self.taken_at = self.taken_at or datetime_taken
|
||||
self.exif = getattr(self.exif_data, 'data', None)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('gallery:photo_url', kwargs={'album_path': self.album.slug, 'photo_slug': self.slug})
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.slug} ({self.album.name})'
|
||||
|
|
|
@ -13,5 +13,8 @@
|
|||
{% block content %}
|
||||
|
||||
<h1>{{ album.name }}</h1>
|
||||
{% for photo in photos %}
|
||||
<a href="{{ photo.get_absolute_url }}"> {{ photo.slug }} </a>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
19
gallery/templates/gallery/photo_detail.html
Normal file
19
gallery/templates/gallery/photo_detail.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
<!-- Title -->
|
||||
{% block title %} Gallery : Photo {% endblock %}
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
{% block breadcrumb %}
|
||||
<li><a href="{% url 'gallery:albums_url' %}">Albums</a></li>
|
||||
<li><a href="{{ photo.album.get_absolute_url }}">{{ photo.album.name }}</a></li>
|
||||
<li>{{ photo.slug }}</li>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<!-- Content -->
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ photo.slug }}</h1>
|
||||
|
||||
{% endblock %}
|
|
@ -9,6 +9,9 @@ urlpatterns = [
|
|||
# Main page
|
||||
path('', views.Main.as_view(), name='main_url'),
|
||||
|
||||
# Yksittäinen kuva albumissa:
|
||||
path('albums/<path:album_path>/<int:photo_slug>/', views.PhotoDetail.as_view(), name="photo_url"),
|
||||
|
||||
# Albumin yksityiskohdat:
|
||||
path('albums/<path:album_slug>/', views.AlbumDetail.as_view(), name="album_url"),
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.views.generic import DetailView, ListView, TemplateView
|
||||
|
||||
from .models import Album
|
||||
from .models import Album, Photo
|
||||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
|
||||
class Main(TemplateView):
|
||||
template_name = "gallery/main.html"
|
||||
|
||||
|
@ -24,3 +23,18 @@ class AlbumDetail(DetailView):
|
|||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Album, slug=self.kwargs.get('album_slug'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['photos'] = self.object.photos.all()
|
||||
return context
|
||||
|
||||
|
||||
class PhotoDetail(DetailView):
|
||||
model = Photo
|
||||
template_name = 'gallery/photo_detail.html'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Photo, slug=self.kwargs.get('photo_slug'))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue