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.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'imagekit',
|
||||||
'gallery'
|
'gallery'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -121,6 +122,12 @@ STATICFILES_DIRS = [
|
||||||
BASE_DIR / 'static',
|
BASE_DIR / 'static',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Media files
|
||||||
|
|
||||||
|
MEDIA_URL = 'media/'
|
||||||
|
MEDIA_ROOT = BASE_DIR / 'media'
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
|
|
@ -25,4 +25,5 @@ urlpatterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
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 django.contrib import admin
|
||||||
|
from imagekit.admin import AdminThumbnail
|
||||||
|
|
||||||
from gallery.models import Album, City, Location
|
from gallery.models import Album, City, Location, Photo
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
|
||||||
|
|
||||||
class CityAdmin(admin.ModelAdmin):
|
class CityAdmin(admin.ModelAdmin):
|
||||||
|
@ -19,6 +18,7 @@ class LocationAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('place', 'city__name',)
|
search_fields = ('place', 'city__name',)
|
||||||
list_per_page = 30
|
list_per_page = 30
|
||||||
|
|
||||||
|
|
||||||
class AlbumAdmin(admin.ModelAdmin):
|
class AlbumAdmin(admin.ModelAdmin):
|
||||||
prepopulated_fields = {'slug': ('name',)}
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
list_display = ('__str__',)
|
list_display = ('__str__',)
|
||||||
|
@ -27,7 +27,15 @@ class AlbumAdmin(admin.ModelAdmin):
|
||||||
list_per_page = 30
|
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(City, CityAdmin)
|
||||||
admin.site.register(Location, LocationAdmin)
|
admin.site.register(Location, LocationAdmin)
|
||||||
admin.site.register(Album, AlbumAdmin)
|
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 datetime
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import gallery.models
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,4 +50,17 @@ class Migration(migrations.Migration):
|
||||||
'verbose_name_plural': 'Albums',
|
'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.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
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.
|
# Create your models here.
|
||||||
|
|
||||||
|
@ -53,3 +59,40 @@ class Album(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}'.format(self.name)
|
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 %}
|
{% block content %}
|
||||||
|
|
||||||
<h1>{{ album.name }}</h1>
|
<h1>{{ album.name }}</h1>
|
||||||
|
{% for photo in photos %}
|
||||||
|
<a href="{{ photo.get_absolute_url }}"> {{ photo.slug }} </a>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock %}
|
{% 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,9 +9,12 @@ urlpatterns = [
|
||||||
# Main page
|
# Main page
|
||||||
path('', views.Main.as_view(), name='main_url'),
|
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:
|
# Albumin yksityiskohdat:
|
||||||
path('albums/<path:album_slug>/', views.AlbumDetail.as_view(), name="album_url"),
|
path('albums/<path:album_slug>/', views.AlbumDetail.as_view(), name="album_url"),
|
||||||
|
|
||||||
# Albumien lista:
|
# Albumien lista:
|
||||||
path('albums/', views.AlbumsList.as_view(), name='albums_url'),
|
path('albums/', views.AlbumsList.as_view(), name='albums_url'),
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.views.generic import DetailView, ListView, TemplateView
|
from django.views.generic import DetailView, ListView, TemplateView
|
||||||
|
|
||||||
from .models import Album
|
from .models import Album, Photo
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Main(TemplateView):
|
class Main(TemplateView):
|
||||||
template_name = "gallery/main.html"
|
template_name = "gallery/main.html"
|
||||||
|
|
||||||
|
@ -23,4 +22,19 @@ class AlbumDetail(DetailView):
|
||||||
template_name = 'gallery/album_detail.html'
|
template_name = 'gallery/album_detail.html'
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
return get_object_or_404(Album, slug=self.kwargs.get('album_slug'))
|
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