updated gallery meta

This commit is contained in:
Alexander Karpov 2023-05-09 22:48:35 +03:00
parent 4bd36ab159
commit 66aad318b3
12 changed files with 256 additions and 5 deletions

View File

@ -21,7 +21,9 @@ def view(file: File):
""" """
content = ( content = (
""" f"""
<a href="{file.file.url}">View in system pdf viewer</a>"""
+ """
<div id="pdf" class="col-auto"> <div id="pdf" class="col-auto">
</div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.5.141/pdf.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.5.141/pdf.min.js"></script>

View File

@ -0,0 +1,66 @@
# Generated by Django 4.2 on 2023-05-09 07:10
from django.db import migrations, models
import django.db.models.deletion
import location_field.models.plain
class Migration(migrations.Migration):
dependencies = [
("shortener", "0001_initial"),
("gallery", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="image",
name="extra_data",
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name="image",
name="image_city",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name="image",
name="image_location",
field=location_field.models.plain.PlainLocationField(
blank=True, max_length=63, null=True
),
),
migrations.CreateModel(
name="Tag",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("slug", models.SlugField(blank=True, max_length=20, unique=True)),
("name", models.CharField(blank=True, max_length=255, null=True)),
(
"short_link",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shortener.link",
),
),
],
options={
"abstract": False,
},
),
migrations.AddField(
model_name="image",
name="tags",
field=models.ManyToManyField(related_name="images", to="gallery.tag"),
),
]

View File

@ -1,6 +1,7 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from location_field.models.plain import PlainLocationField
from akarpov.common.models import BaseImageModel from akarpov.common.models import BaseImageModel
from akarpov.tools.shortener.models import ShortLink from akarpov.tools.shortener.models import ShortLink
@ -30,9 +31,27 @@ class Image(TimeStampedModel, ShortLink, BaseImageModel):
"users.User", related_name="images", on_delete=models.CASCADE "users.User", related_name="images", on_delete=models.CASCADE
) )
image = models.ImageField(upload_to=user_file_upload_mixin, blank=False, null=False) image = models.ImageField(upload_to=user_file_upload_mixin, blank=False, null=False)
tags = models.ManyToManyField("Tag", related_name="images")
# image meta
extra_data = models.JSONField(default=dict)
image_city = models.CharField(max_length=255, null=True, blank=True)
image_location = PlainLocationField(
based_fields=["image_city"], zoom=7, null=True, blank=True
)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("gallery:view", kwargs={"slug": self.slug}) return reverse("gallery:view", kwargs={"slug": self.slug})
def __str__(self): def __str__(self):
return self.image.name return self.image.name
class Tag(ShortLink):
name = models.CharField(max_length=255, null=True, blank=True)
def get_absolute_url(self):
return reverse("gallery:tag", kwargs={"slug": self.slug})
def __str__(self):
return self.name

View File

@ -0,0 +1,87 @@
from PIL import ExifTags, Image
def _get_if_exist(data, key):
if key in data:
return data[key]
return None
def _convert_to_dec_degrees(value):
"""Helper function to convert the GPS coordinates stored in the EXIF to decimal degress"""
d0 = value[0][0]
d1 = value[0][1]
d = float(d0) / float(d1)
m0 = value[1][0]
m1 = value[1][1]
m = float(m0) / float(m1)
s0 = value[2][0]
s1 = value[2][1]
s = float(s0) / float(s1)
return d + (m / 60.0) + (s / 3600.0)
def get_image_coordinate(path):
try:
exif = load_image_meta(path)
gps_info = exif["GPSInfo"]
except Exception:
return {}
try:
gps_latitude = _get_if_exist(gps_info, "GPSLatitude")
gps_latitude_ref = _get_if_exist(gps_info, "GPSLatitudeRef")
gps_longitude = _get_if_exist(gps_info, "GPSLongitude")
gps_longitude_ref = _get_if_exist(gps_info, "GPSLongitudeRef")
if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
lat = _convert_to_dec_degrees(gps_latitude)
if gps_latitude_ref != "N":
lat = 0 - lat
lon = _convert_to_dec_degrees(gps_longitude)
if gps_longitude_ref != "E":
lon = 0 - lon
# check if coordinates are sane and if not....
if lat > 90 or lat < -90 or lon > 180 or lon < -180:
raise Exception("No usable coordinates stored in photo")
gps_altitude = _get_if_exist(gps_info, "GPSAltitude")
try:
z = [float(x) / float(y) for x, y in gps_altitude]
except Exception:
z = None
gps_mapdatum = _get_if_exist(gps_info, "GPSMapDatum")
if gps_mapdatum:
gps_mapdatum = gps_mapdatum.rstrip()
gps_imgdirection = _get_if_exist(gps_info, "GPSImgDirection")
try:
bearing = [float(x) / float(y) for x, y in gps_imgdirection]
except Exception:
bearing = None
coordinates = {
"mapdatum": gps_mapdatum,
"lon": lon,
"lat": lat,
"bearing": bearing,
"z": z,
}
return coordinates
except Exception:
return {}
def load_image_meta(image_path: str) -> dict:
img = Image.open(image_path)
img_exif = img.getexif()
data = {}
if img_exif is not None:
for key, val in img_exif.items():
if key in ExifTags.TAGS:
data[key] = val
return data

View File

@ -0,0 +1,16 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from akarpov.gallery.models import Image
from akarpov.gallery.tasks import process_gallery_image
@receiver(post_save, sender=Image)
def image_create(sender, instance: Image, created, **kwargs):
if created:
process_gallery_image.apply_async(
kwargs={
"pk": instance.pk,
},
countdown=2,
)

16
akarpov/gallery/tasks.py Normal file
View File

@ -0,0 +1,16 @@
from celery import shared_task
from django.contrib.gis.geos import Point
from akarpov.gallery.models import Image
from akarpov.gallery.services import get_image_coordinate, load_image_meta
@shared_task()
def process_gallery_image(pk: int):
image = Image.objects.get(pk=pk)
data = load_image_meta(image.image.path)
image.extra_data = data
coordinates = get_image_coordinate(image.image.path)
if coordinates and "lon" in coordinates and "lat" in coordinates:
image.image_location = Point(coordinates["lon"], coordinates["lat"])
image.save()

View File

@ -1,10 +1,16 @@
from django.urls import path from django.urls import path
from akarpov.gallery.views import collection_view, image_view, list_collections_view from akarpov.gallery.views import (
collection_view,
image_view,
list_collections_view,
list_tag_images_view,
)
app_name = "gallery" app_name = "gallery"
urlpatterns = [ urlpatterns = [
path("", list_collections_view, name="list"), path("", list_collections_view, name="list"),
path("<str:slug>", collection_view, name="collection"), path("<str:slug>", collection_view, name="collection"),
path("tag/<str:slug>", list_tag_images_view, name="tag"),
path("image/<str:slug>", image_view, name="view"), path("image/<str:slug>", image_view, name="view"),
] ]

View File

@ -1,7 +1,8 @@
from django.shortcuts import get_object_or_404
from django.views import generic from django.views import generic
from akarpov.common.views import HasPermissions from akarpov.common.views import HasPermissions
from akarpov.gallery.models import Collection from akarpov.gallery.models import Collection, Image, Tag
class ListCollectionsView(generic.ListView): class ListCollectionsView(generic.ListView):
@ -17,6 +18,20 @@ def get_queryset(self):
list_collections_view = ListCollectionsView.as_view() list_collections_view = ListCollectionsView.as_view()
class ListTagImagesView(generic.ListView):
model = Image
template_name = "gallery/images.html"
def get_queryset(self):
tag = get_object_or_404(Tag, slug=self.kwargs["slug"])
if self.request.user.is_authenticated:
return Image.objects.filter(tags__contain=tag, user=self.request.user)
return Image.objects.filter(tags__contain=tag, collection__public=True)
list_tag_images_view = ListTagImagesView.as_view()
class CollectionView(generic.DetailView, HasPermissions): class CollectionView(generic.DetailView, HasPermissions):
model = Collection model = Collection
template_name = "gallery/collection.html" template_name = "gallery/collection.html"

View File

@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block content %}
{% endblock %}

View File

@ -117,6 +117,7 @@
"robots", "robots",
"django_filters", "django_filters",
"django_tables2", "django_tables2",
"location_field",
# django-cms # django-cms
"cms", "cms",
"menus", "menus",
@ -548,3 +549,10 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
ROBOTS_USE_SITEMAP = True ROBOTS_USE_SITEMAP = True
ROBOTS_USE_SCHEME_IN_HOST = True ROBOTS_USE_SCHEME_IN_HOST = True
# LOCATION_FIELD
# ------------------------------------------------------------------------------
LOCATION_FIELD = {
"map.provider": "openstreetmap",
"search.provider": "nominatim",
}

14
poetry.lock generated
View File

@ -1613,6 +1613,17 @@ Django = ">=2.2"
[package.extras] [package.extras]
tests = ["coverage"] tests = ["coverage"]
[[package]]
name = "django-location-field"
version = "2.7.0"
description = "Location field for Django"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "django_location_field-2.7.0-py2.py3-none-any.whl", hash = "sha256:25745b3f409c0c7a1dfb5773f4f7505acbe7d0eaba93c2b9ce24ef368b71a24c"},
]
[[package]] [[package]]
name = "django-model-utils" name = "django-model-utils"
version = "4.3.1" version = "4.3.1"
@ -5355,7 +5366,6 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
] ]
@ -5698,4 +5708,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.11"
content-hash = "5837eb6fa52d084699060d6fbf6fa9c599ccf9da936a4e80e418e5636a743c17" content-hash = "2365c910257ce91b09f72d236f19cf15257b5561f7fc746fed7c8e1f6155c0d4"

View File

@ -91,6 +91,7 @@ django-robots = "^5.0"
django-tables2 = "^2.5.3" django-tables2 = "^2.5.3"
django-filter = "^23.2" django-filter = "^23.2"
tablib = "^3.4.0" tablib = "^3.4.0"
django-location-field = "^2.7.0"
[build-system] [build-system]