add phototags

This commit is contained in:
root 2022-06-15 12:05:29 +02:00
parent e91e83df40
commit d160bf3e7b
69 changed files with 138 additions and 343 deletions

4
.gitignore vendored Normal file → Executable file
View File

@ -1,2 +1,6 @@
static/* static/*
./*/*pycache*/* ./*/*pycache*/*
**/__pycache__
*/migrations/*
env/
static_bk/

View File

@ -1,38 +0,0 @@
# Generated by Django 3.2.8 on 2021-10-27 11:22
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='CustomUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
),
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

0
api/photolog_generator.py Normal file → Executable file
View File

0
api/photolog_layout.py Normal file → Executable file
View File

View File

@ -1,5 +1,11 @@
from rest_framework import serializers from rest_framework import serializers
from photo_log.models import PhotoGroup, Photo, PhotoLog from photo_log.models import PhotoGroup, Photo, PhotoLog, PhotoTag
class PhotoTagSerializer(serializers.ModelSerializer):
class Meta:
model = PhotoTag
fields = ('id', 'name', 'color')
class PhotoGroupSerializer(serializers.ModelSerializer): class PhotoGroupSerializer(serializers.ModelSerializer):
@ -15,15 +21,17 @@ class PhotosSerializer(serializers.ModelSerializer):
class PhotoSerializer(serializers.ModelSerializer): class PhotoSerializer(serializers.ModelSerializer):
tag = PhotoTagSerializer()
class Meta: class Meta:
model = Photo model = Photo
fields = ('id', 'legacy_id', 'group', 'bbox_coords', 'rotate', 'intersections', 'original_image', 'cropped_image', 'ocr_text') fields = ('id', 'legacy_id', 'group', 'bbox_coords', 'rotate', 'intersections', 'original_image', 'cropped_image', 'ocr_text', 'tag')
class PhotoUpdateSerializer(serializers.ModelSerializer): class PhotoUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Photo model = Photo
fields = ('id', 'legacy_id', 'group', 'bbox_coords', 'rotate', 'ocr_text') fields = ('id', 'legacy_id', 'group', 'bbox_coords', 'rotate', 'ocr_text', 'tag')
class PhotoLogSerializer(serializers.ModelSerializer): class PhotoLogSerializer(serializers.ModelSerializer):

View File

@ -14,6 +14,9 @@ from .views import (
UpdatePhotoLogAPIView, UpdatePhotoLogAPIView,
UpdatePhotoAPIView, UpdatePhotoAPIView,
GeneratePhotoLogAPIView, GeneratePhotoLogAPIView,
PhotoTagsAPIView,
RetrieveUpdateDestroyPhotoTagAPIView,
CreatePhotoTagAPIView,
) )
@ -33,4 +36,7 @@ urlpatterns = [
path('updatephotolog/<int:pk>/', UpdatePhotoLogAPIView.as_view()), path('updatephotolog/<int:pk>/', UpdatePhotoLogAPIView.as_view()),
path('updatephoto/<int:pk>/', UpdatePhotoAPIView.as_view()), path('updatephoto/<int:pk>/', UpdatePhotoAPIView.as_view()),
path('generatephotolog/<int:pk>/', GeneratePhotoLogAPIView.as_view()), path('generatephotolog/<int:pk>/', GeneratePhotoLogAPIView.as_view()),
path('phototag/', CreatePhotoTagAPIView.as_view()),
path('phototags/', PhotoTagsAPIView.as_view()),
path('phototag/<int:pk>/', RetrieveUpdateDestroyPhotoTagAPIView.as_view()),
] ]

View File

@ -1,7 +1,7 @@
from rest_framework import generics, views, status from rest_framework import generics, views, status
from rest_framework.response import Response from rest_framework.response import Response
from django.shortcuts import get_list_or_404 from django.shortcuts import get_list_or_404
from photo_log.models import PhotoGroup, Photo, PhotoLog from photo_log.models import PhotoGroup, Photo, PhotoLog, PhotoTag
from .serializers import ( from .serializers import (
PhotoGroupSerializer, PhotoGroupSerializer,
PhotosSerializer, PhotosSerializer,
@ -9,6 +9,7 @@ from .serializers import (
PhotoUpdateSerializer, PhotoUpdateSerializer,
PhotoLogSerializer, PhotoLogSerializer,
PhotoLogsSerializer, PhotoLogsSerializer,
PhotoTagSerializer,
) )
from .photolog_generator import generate_photolog_from_latex from .photolog_generator import generate_photolog_from_latex
@ -22,6 +23,21 @@ from io import BytesIO
from PIL import Image, ExifTags from PIL import Image, ExifTags
class PhotoTagsAPIView(generics.ListAPIView):
queryset = PhotoTag.objects.all()
serializer_class = PhotoTagSerializer
class CreatePhotoTagAPIView(generics.CreateAPIView):
queryset = PhotoTag.objects.all()
serializer_class = PhotoTagSerializer
class RetrieveUpdateDestroyPhotoTagAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = PhotoTag.objects.all()
serializer_class = PhotoTagSerializer
class PhotoGroupAPIView(generics.ListAPIView): class PhotoGroupAPIView(generics.ListAPIView):
queryset = PhotoGroup.objects.all() queryset = PhotoGroup.objects.all()
serializer_class = PhotoGroupSerializer serializer_class = PhotoGroupSerializer
@ -135,7 +151,7 @@ class GeneratePhotoLogAPIView(generics.RetrieveAPIView):
if log_bytes: if log_bytes:
photolog.pdf.save('log.pdf', ContentFile(log_bytes)) photolog.pdf.save('log.pdf', ContentFile(log_bytes))
return Response({'pdf': 'https://server.riezel.com%s' % str(photolog.pdf.url)}) return Response({'pdf': 'https://zierle-training.riezel.com%s' % str(photolog.pdf.url)})
return Response({"error": "Not Found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "Not Found"}, status=status.HTTP_404_NOT_FOUND)
@ -191,6 +207,9 @@ def rotateByExif(img):
exif = img._getexif() exif = img._getexif()
if not exif:
return img
if exif[orientation] == 3: if exif[orientation] == 3:
img=img.rotate(180, expand=True) img=img.rotate(180, expand=True)
elif exif[orientation] == 6: elif exif[orientation] == 6:

0
config/package-lock.json generated Normal file → Executable file
View File

View File

@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-z465dl_(vk55hxbm0bj*mp-ok3!*=ssw#!$5s2nrxa!9j+67z+
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ['server.riezel.com', 'localhost', '127.0.0.1', '192.168.1.244'] ALLOWED_HOSTS = ['zierle-training.riezel.com', 'localhost', '127.0.0.1', '192.168.1.244']
# Application definition # Application definition
@ -45,6 +45,7 @@ INSTALLED_APPS = [
'corsheaders', 'corsheaders',
'drf_yasg', 'drf_yasg',
'django_tex', 'django_tex',
'colorfield',
# local # local
'accounts', 'accounts',
@ -110,8 +111,8 @@ ASGI_APPLICATION = 'config.asgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', 'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'www_db', 'NAME': 'zierle_training_db',
'USER': 'www_db_user', 'USER': 'zierle_training_db_user',
'PASSWORD': 'UI&hWG,El7G{A2c0n=qIULv:b', 'PASSWORD': 'UI&hWG,El7G{A2c0n=qIULv:b',
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': '5432', 'PORT': '5432',
@ -157,12 +158,37 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/ # https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
#STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/")]
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
MINIO = False
if MINIO:
# MinIO S3 Object-Storage
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'
AWS_ACCESS_KEY_ID = 'zierle-trainig-minio'
AWS_SECRET_ACCESS_KEY = 'iGWJt7IFlKzufMtVTnl4W4kGmHz0TTBG'
AWS_STORAGE_BUCKET_NAME = 'zierle-training'
AWS_S3_ENDPOINT_URL = 'https://minio.riezel.com'
AWS_DEFAULT_ACL = 'public'
MEDIA_URL = 'https://minio.riezel.com/zierle-training/'
STATIC_URL = 'https://minio.riezel.com/zierle-training/'
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'public, max-age=86400',
}
else:
#MEDIA_URL = 'media/'
#MEDIA_ROOT = BASE_DIR / 'media'
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
#STATICFILES_DIRS = [os.path.join(BASE_DIR, "static/")]

View File

@ -1,8 +1,9 @@
from django.contrib import admin from django.contrib import admin
from .models import PhotoGroup, Photo, PhotoLog from .models import PhotoGroup, Photo, PhotoLog, PhotoTag
admin.site.register(PhotoGroup) admin.site.register(PhotoGroup)
admin.site.register(Photo) admin.site.register(Photo)
admin.site.register(PhotoLog) admin.site.register(PhotoLog)
admin.site.register(PhotoTag)

View File

@ -1,36 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-05 17:12
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import photo_log.models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='PhotoGroup',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)),
('date', models.DateField(null=True)),
],
),
migrations.CreateModel(
name='Photo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('legacy_id', models.IntegerField(blank=True, null=True, unique=True)),
('original_image', models.ImageField(upload_to='original_images/')),
('cropped_image', models.ImageField(blank=True, null=True, upload_to='cropped_images/')),
('ocr_text', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), size=None)),
('group', models.ForeignKey(default=photo_log.models.get_default_photogroup, on_delete=django.db.models.deletion.SET_DEFAULT, to='photo_log.photogroup')),
],
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-05 17:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='photo',
name='cropped_image',
field=models.ImageField(blank=True, null=True, upload_to='static/cropped_images/'),
),
migrations.AlterField(
model_name='photo',
name='original_image',
field=models.ImageField(upload_to='static/original_images/'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-06 08:52
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0002_auto_20220105_1757'),
]
operations = [
migrations.AlterField(
model_name='photo',
name='ocr_text',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), blank=True, null=True, size=None),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-06 12:48
from django.db import migrations, models
import django.db.models.deletion
import photo_log.models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0003_alter_photo_ocr_text'),
]
operations = [
migrations.AlterField(
model_name='photo',
name='group',
field=models.ForeignKey(blank=True, default=photo_log.models.get_default_photogroup, on_delete=django.db.models.deletion.SET_DEFAULT, to='photo_log.photogroup'),
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-13 13:32
import datetime
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0004_alter_photo_group'),
]
operations = [
migrations.CreateModel(
name='PhotoLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=5000)),
('date', models.DateField(default=datetime.date.today)),
('render_date', models.BooleanField(blank=True, default=True)),
('start_slide_image', models.IntegerField(blank=True, default=3, null=True)),
('slides', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(null=True), blank=True, null=True, size=3), size=None)),
],
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-13 15:11
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0005_photolog'),
]
operations = [
migrations.AlterField(
model_name='photolog',
name='slides',
field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(null=True), blank=True, null=True, size=3), blank=True, default=[], size=None),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-13 15:13
import django.contrib.postgres.fields
from django.db import migrations, models
import photo_log.models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0006_alter_photolog_slides'),
]
operations = [
migrations.AlterField(
model_name='photolog',
name='slides',
field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(null=True), blank=True, null=True, size=3), blank=True, default=photo_log.models.get_empty_photolog_default, size=None),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-18 18:47
from django.db import migrations, models
import photo_log.models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0007_alter_photolog_slides'),
]
operations = [
migrations.AlterField(
model_name='photo',
name='cropped_image',
field=models.ImageField(blank=True, null=True, upload_to=photo_log.models.get_cropped_photo_path),
),
migrations.AlterField(
model_name='photo',
name='original_image',
field=models.ImageField(upload_to=photo_log.models.get_original_photo_path),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-18 19:56
from django.db import migrations, models
import photo_log.models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0008_auto_20220118_1847'),
]
operations = [
migrations.AddField(
model_name='photolog',
name='pdf',
field=models.FileField(blank=True, null=True, upload_to=photo_log.models.get_photolog_pdf_path),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-21 11:47
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0009_photolog_pdf'),
]
operations = [
migrations.AddField(
model_name='photo',
name='bbox_coords',
field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(default=None, null=True), size=2), blank=True, default=None, null=True, size=4),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2022-01-22 11:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0010_photo_bbox_coords'),
]
operations = [
migrations.AlterField(
model_name='photo',
name='ocr_text',
field=models.CharField(blank=True, max_length=200, null=True),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.8 on 2022-05-03 14:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0011_alter_photo_ocr_text'),
]
operations = [
migrations.AddField(
model_name='photo',
name='rotate',
field=models.FloatField(blank=True, null=True),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 3.2.8 on 2022-05-04 07:28
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('photo_log', '0012_photo_rotate'),
]
operations = [
migrations.AddField(
model_name='photo',
name='intersections',
field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(default=None, null=True), size=2), blank=True, default=None, null=True, size=None),
),
]

View File

@ -2,12 +2,22 @@ from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from colorfield.fields import ColorField
from datetime import date from datetime import date
import os import os
import uuid import uuid
class PhotoTag(models.Model):
name = models.CharField(unique=True, null=False, blank=False, max_length=100)
color = ColorField(default='#FFE5B4')
def __str__(self):
return self.name
class PhotoGroup(models.Model): class PhotoGroup(models.Model):
name = models.CharField(unique=True, null=False, max_length=200) name = models.CharField(unique=True, null=False, max_length=200)
date = models.DateField(null=True) date = models.DateField(null=True)
@ -63,6 +73,13 @@ class Photo(models.Model):
default=get_default_photogroup default=get_default_photogroup
) )
ocr_text = models.CharField(max_length=200, null=True, blank=True) ocr_text = models.CharField(max_length=200, null=True, blank=True)
tag = models.ForeignKey(
PhotoTag,
null=True,
blank=False,
on_delete=models.SET_NULL,
default=None,
)
def __str__(self): def __str__(self):
return "Photo #" + str(self.id) return "Photo #" + str(self.id)

13
req.txt
View File

@ -1,13 +0,0 @@
imageio==2.13.5
imutils==0.5.4
networkx==2.6.3
numpy==1.22.0
opencv-python==4.5.5.62
packaging==21.3
Pillow==9.0.0
pyparsing==3.0.6
pytesseract==0.3.8
PyWavelets==1.2.0
scikit-image==0.19.1
scipy==1.7.3
tifffile==2021.11.2

View File

@ -1,19 +1,64 @@
asgiref==3.4.1 asgiref==3.4.1
attrs==21.4.0
autobahn==22.4.2
Automat==20.2.0
beautifulsoup4==4.10.0 beautifulsoup4==4.10.0
certifi==2021.10.8 certifi==2021.10.8
cffi==1.15.0
channels==3.0.4
charset-normalizer==2.0.7 charset-normalizer==2.0.7
constantly==15.1.0
coreapi==2.3.3
coreschema==0.0.4
cryptography==37.0.2
daphne==3.0.2
Django==3.2.8 Django==3.2.8
django-cors-headers==3.12.0
django-filter==21.1
django-tex==1.1.10
djangorestframework==3.13.1 djangorestframework==3.13.1
drf-yasg==1.20.0
gunicorn==20.1.0 gunicorn==20.1.0
hyperlink==21.0.0
idna==3.3 idna==3.3
imageio==2.13.5
imutils==0.5.4
incremental==21.3.0
inflection==0.5.1
itypes==1.2.0
Jinja2==3.1.2
lxml==4.6.3 lxml==4.6.3
Markdown==3.3.7
MarkupSafe==2.1.1
networkx==2.6.3
numpy==1.22.0
opencv-python==4.5.5.62
packaging==21.3
Pillow==9.0.0 Pillow==9.0.0
psycopg2==2.9.1 psycopg2==2.9.1
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.21
pyOpenSSL==22.0.0
pyparsing==3.0.6
pytesseract==0.3.8
python-dateutil==2.8.2 python-dateutil==2.8.2
pytz==2021.3 pytz==2021.3
PyWavelets==1.2.0
requests==2.26.0 requests==2.26.0
ruamel.yaml==0.17.21
ruamel.yaml.clib==0.2.6
scikit-image==0.19.1
scipy==1.7.3
service-identity==21.1.0
six==1.16.0 six==1.16.0
soupsieve==2.2.1 soupsieve==2.2.1
sqlparse==0.4.2 sqlparse==0.4.2
tifffile==2021.11.2
tqdm==4.62.3 tqdm==4.62.3
Twisted==22.4.0
txaio==22.2.1
typing_extensions==4.2.0
uritemplate==4.1.1
urllib3==1.26.7 urllib3==1.26.7
zope.interface==5.4.0

0
templates/photolog.tex Normal file → Executable file
View File