Initial
This commit is contained in:
4
backend/app/models/__init__.py
Normal file
4
backend/app/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .audiofile import Audiofile # noqa: F401
|
||||
from .category import Category # noqa: F401
|
||||
from .content import Content # noqa: F401
|
||||
from .place import Place # noqa: F401
|
||||
45
backend/app/models/audiofile.py
Normal file
45
backend/app/models/audiofile.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from common.form.audio_file import AudioFileField
|
||||
|
||||
|
||||
def overwrite_audio(instance: 'Audiofile', filename: str):
|
||||
if instance.path.is_file():
|
||||
os.remove(instance.path)
|
||||
return instance.url
|
||||
|
||||
|
||||
class Audiofile(models.Model):
|
||||
key = models.UUIDField('ID', primary_key=True, default=uuid.uuid4,
|
||||
editable=False)
|
||||
audio = AudioFileField('Audio', upload_to=overwrite_audio)
|
||||
desc = models.CharField('Beschreibung', max_length=200)
|
||||
created = models.DateTimeField('Erstellt', auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Audiodatei'
|
||||
verbose_name_plural = 'Audiodateien'
|
||||
ordering = ('-created',)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.desc
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
return settings.MEDIA_ROOT / 'audio' / f'{self.pk}.mp3'
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return f'audio/{self.pk}.mp3'
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Audiofile)
|
||||
def on_delete_Audiofile(sender, instance: 'Audiofile', using, **kwargs):
|
||||
os.remove(instance.path)
|
||||
55
backend/app/models/category.py
Normal file
55
backend/app/models/category.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
import json
|
||||
from colorfield.fields import ColorField
|
||||
|
||||
import typing
|
||||
if typing.TYPE_CHECKING:
|
||||
from app.models.place import Place
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField('Name', max_length=100)
|
||||
color = ColorField('Farbe', default='#3388ff', max_length=7)
|
||||
fg_color_white = models.BooleanField('Textfarbe Weiß', default=False)
|
||||
sort = models.IntegerField('Sortierung', default=0)
|
||||
|
||||
places: 'models.QuerySet[Place]'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Kategorie'
|
||||
verbose_name_plural = 'Kategorien'
|
||||
ordering = ('sort',)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
rv = super().save(*args, **kwargs)
|
||||
Category.update_json()
|
||||
return rv
|
||||
|
||||
@staticmethod
|
||||
def update_json():
|
||||
with open(settings.MEDIA_ROOT / 'categories.json', 'w') as fp:
|
||||
json.dump(Category.asJson(), fp)
|
||||
|
||||
@staticmethod
|
||||
def asJson() -> 'list[dict[str, str]]':
|
||||
rv = []
|
||||
for x in Category.objects.all():
|
||||
rv.append({
|
||||
'id': x.pk,
|
||||
'name': x.name,
|
||||
'color': x.color,
|
||||
'inv': x.fg_color_white,
|
||||
})
|
||||
return rv
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Category)
|
||||
def on_delete_Audiofile(sender, instance: 'Category', using, **kwargs):
|
||||
Category.update_json()
|
||||
40
backend/app/models/content.py
Normal file
40
backend/app/models/content.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
import json
|
||||
from tinymce.models import HTMLField
|
||||
|
||||
|
||||
class Content(models.Model):
|
||||
key = models.SlugField('ID', primary_key=True, unique=True)
|
||||
title = models.CharField('Titel', max_length=100)
|
||||
body = HTMLField('Inhalt')
|
||||
wide = models.BooleanField('Breiteres Fenster')
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Text'
|
||||
verbose_name_plural = 'Texte'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
rv = super().save(*args, **kwargs)
|
||||
self.update_json()
|
||||
return rv
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
rv = super().delete(*args, **kwargs)
|
||||
self.update_json()
|
||||
return rv
|
||||
|
||||
def update_json(self):
|
||||
with open(settings.MEDIA_ROOT / 'text.json', 'w') as fp:
|
||||
json.dump(self.asJson(), fp)
|
||||
|
||||
@staticmethod
|
||||
def asJson() -> 'dict[str, str]':
|
||||
rv = {}
|
||||
for x in Content.objects.all():
|
||||
rv[x.pk] = {'title': x.title, 'body': x.body, 'wide': x.wide}
|
||||
return rv
|
||||
131
backend/app/models/place.py
Normal file
131
backend/app/models/place.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageOps
|
||||
from tinymce.models import HTMLField
|
||||
from map_location.fields import LocationField
|
||||
# default_app_config = 'map_location.apps.MapLocationConfig'
|
||||
|
||||
from app.models.category import Category
|
||||
from common.form.audio_file import AudioFileField
|
||||
from common.form.img_with_preview import FileWithImagePreview
|
||||
|
||||
|
||||
def overwrite_img_upload(instance: 'Place', filename: str):
|
||||
path = instance.fixed_os_path('img.jpg')
|
||||
if path.is_file():
|
||||
os.remove(path)
|
||||
return instance.fixed_save_url('img.jpg')
|
||||
|
||||
|
||||
def overwrite_audio_upload(instance: 'Place', filename: str):
|
||||
path = instance.fixed_os_path('audio.mp3')
|
||||
if path.is_file():
|
||||
os.remove(path)
|
||||
return instance.fixed_save_url('audio.mp3')
|
||||
|
||||
|
||||
class Place(models.Model):
|
||||
category: 'models.ForeignKey[Category]' = models.ForeignKey(
|
||||
'Category', on_delete=models.CASCADE, related_name='places',
|
||||
verbose_name='Kategorie')
|
||||
|
||||
sort = models.IntegerField('Sortierung', default=0)
|
||||
isExtended = models.BooleanField('Bis 1.1.2025 verstecken')
|
||||
title = models.CharField('Titel', max_length=100)
|
||||
image = FileWithImagePreview('Bild', blank=True, null=True,
|
||||
upload_to=overwrite_img_upload)
|
||||
# help_text='Ideal: 600 x 400 px (JPEG oder PNG)'
|
||||
audio = AudioFileField('Audio', blank=True, null=True,
|
||||
upload_to=overwrite_audio_upload)
|
||||
location = LocationField('Position', blank=True, null=True, options={
|
||||
'map': {
|
||||
'center': [49.895, 10.890],
|
||||
'zoom': 14,
|
||||
},
|
||||
})
|
||||
description = HTMLField('Beschreibung')
|
||||
created = models.DateTimeField('Erstellt', auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Ort'
|
||||
verbose_name_plural = 'Orte'
|
||||
ordering = ('sort',)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.title
|
||||
|
||||
def fixed_os_path(self, name: str) -> Path:
|
||||
return settings.MEDIA_ROOT / str(self.pk) / name
|
||||
|
||||
def fixed_save_url(self, name: str) -> str:
|
||||
if self.pk is None:
|
||||
next_id = Place.objects.count() + 1
|
||||
return f'{next_id}/{name}'
|
||||
return f'{self.pk}/{name}'
|
||||
|
||||
@property
|
||||
def cover_image_url(self) -> 'str|None':
|
||||
if self.image:
|
||||
return self.image.url.replace('img.jpg', 'cov.jpg')
|
||||
return None
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
rv = super().save(*args, **kwargs)
|
||||
self.update_cover_image()
|
||||
Place.update_json()
|
||||
return rv
|
||||
|
||||
# def delete(self, *args, **kwargs):
|
||||
# theId = str(self.pk)
|
||||
# rv = super().delete(*args, **kwargs)
|
||||
# shutil.rmtree(settings.MEDIA_ROOT / theId, ignore_errors=True)
|
||||
# self.update_json()
|
||||
# return rv
|
||||
|
||||
def update_cover_image(self):
|
||||
path = self.fixed_os_path('cov.jpg')
|
||||
if self.image:
|
||||
img = Image.open(self.image.path)
|
||||
thumb = ImageOps.fit(img, (600, 400))
|
||||
# img.thumbnail((600, 400))
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
thumb.save(path, 'jpeg')
|
||||
else:
|
||||
if path.exists():
|
||||
os.remove(path)
|
||||
|
||||
@staticmethod
|
||||
def update_json():
|
||||
with open(settings.MEDIA_ROOT / 'places.json', 'w') as fp:
|
||||
json.dump(Place.asJson(), fp)
|
||||
|
||||
@staticmethod
|
||||
def asJson() -> 'list[dict[str, str]]':
|
||||
rv = []
|
||||
for x in Place.objects.all():
|
||||
rv.append({
|
||||
'id': x.pk,
|
||||
'name': x.title,
|
||||
'loc': [round(x.location.lat, 6),
|
||||
round(x.location.long, 6)] if x.location else None,
|
||||
'cat': x.category.pk,
|
||||
'cov': x.cover_image_url,
|
||||
'img': x.image.url if x.image else None,
|
||||
'audio': x.audio.url if x.audio else None,
|
||||
'later': x.isExtended,
|
||||
'desc': x.description,
|
||||
})
|
||||
return rv
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Place)
|
||||
def on_delete_Audiofile(sender, instance: 'Place', using, **kwargs):
|
||||
shutil.rmtree(settings.MEDIA_ROOT / str(instance.pk), ignore_errors=True)
|
||||
Place.update_json()
|
||||
Reference in New Issue
Block a user