mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-22 07:26:33 +03:00
updated gallery meta
This commit is contained in:
parent
4bd36ab159
commit
66aad318b3
|
@ -21,7 +21,9 @@ def view(file: File):
|
|||
"""
|
||||
|
||||
content = (
|
||||
"""
|
||||
f"""
|
||||
<a href="{file.file.url}">View in system pdf viewer</a>"""
|
||||
+ """
|
||||
<div id="pdf" class="col-auto">
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.5.141/pdf.min.js"></script>
|
||||
|
|
|
@ -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"),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django_extensions.db.models import TimeStampedModel
|
||||
from location_field.models.plain import PlainLocationField
|
||||
|
||||
from akarpov.common.models import BaseImageModel
|
||||
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
|
||||
)
|
||||
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):
|
||||
return reverse("gallery:view", kwargs={"slug": self.slug})
|
||||
|
||||
def __str__(self):
|
||||
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
|
||||
|
|
87
akarpov/gallery/services.py
Normal file
87
akarpov/gallery/services.py
Normal 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
|
16
akarpov/gallery/signals.py
Normal file
16
akarpov/gallery/signals.py
Normal 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
16
akarpov/gallery/tasks.py
Normal 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()
|
|
@ -1,10 +1,16 @@
|
|||
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"
|
||||
urlpatterns = [
|
||||
path("", list_collections_view, name="list"),
|
||||
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"),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.views import generic
|
||||
|
||||
from akarpov.common.views import HasPermissions
|
||||
from akarpov.gallery.models import Collection
|
||||
from akarpov.gallery.models import Collection, Image, Tag
|
||||
|
||||
|
||||
class ListCollectionsView(generic.ListView):
|
||||
|
@ -17,6 +18,20 @@ def get_queryset(self):
|
|||
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):
|
||||
model = Collection
|
||||
template_name = "gallery/collection.html"
|
||||
|
|
5
akarpov/templates/gallery/images.html
Normal file
5
akarpov/templates/gallery/images.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
|
@ -117,6 +117,7 @@
|
|||
"robots",
|
||||
"django_filters",
|
||||
"django_tables2",
|
||||
"location_field",
|
||||
# django-cms
|
||||
"cms",
|
||||
"menus",
|
||||
|
@ -548,3 +549,10 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
ROBOTS_USE_SITEMAP = True
|
||||
ROBOTS_USE_SCHEME_IN_HOST = True
|
||||
|
||||
# LOCATION_FIELD
|
||||
# ------------------------------------------------------------------------------
|
||||
LOCATION_FIELD = {
|
||||
"map.provider": "openstreetmap",
|
||||
"search.provider": "nominatim",
|
||||
}
|
||||
|
|
14
poetry.lock
generated
14
poetry.lock
generated
|
@ -1613,6 +1613,17 @@ Django = ">=2.2"
|
|||
[package.extras]
|
||||
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]]
|
||||
name = "django-model-utils"
|
||||
version = "4.3.1"
|
||||
|
@ -5355,7 +5366,6 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
|
||||
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
|
||||
]
|
||||
|
||||
|
@ -5698,4 +5708,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "5837eb6fa52d084699060d6fbf6fa9c599ccf9da936a4e80e418e5636a743c17"
|
||||
content-hash = "2365c910257ce91b09f72d236f19cf15257b5561f7fc746fed7c8e1f6155c0d4"
|
||||
|
|
|
@ -91,6 +91,7 @@ django-robots = "^5.0"
|
|||
django-tables2 = "^2.5.3"
|
||||
django-filter = "^23.2"
|
||||
tablib = "^3.4.0"
|
||||
django-location-field = "^2.7.0"
|
||||
|
||||
|
||||
[build-system]
|
||||
|
|
Loading…
Reference in New Issue
Block a user