Initial
This commit is contained in:
2
backend/app/models/__init__.py
Normal file
2
backend/app/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .organization import Organization # noqa: F401
|
||||
from .member import Member # noqa: F401
|
||||
97
backend/app/models/member.py
Normal file
97
backend/app/models/member.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from pathlib import PosixPath
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from app.form.file_with_img_preview import FileWithImagePreview, ImageValidator
|
||||
from app.utils import encrypt, overwrite_upload, random_secret
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
import base64
|
||||
|
||||
|
||||
class Member(models.Model):
|
||||
# auto-generated ids
|
||||
uuid = models.UUIDField(
|
||||
primary_key=True, default=uuid.uuid4, editable=False)
|
||||
secret = models.CharField(
|
||||
max_length=20, default=random_secret, editable=False)
|
||||
|
||||
# member info
|
||||
organization = models.ForeignKey(
|
||||
'Organization', on_delete=models.CASCADE, related_name='members')
|
||||
member_id = models.CharField('Mitglieder-Nr.', max_length=20)
|
||||
name = models.CharField('Name', max_length=100)
|
||||
valid_since = models.DateField('Mitglied seit')
|
||||
valid_until = models.DateField('Mitglied bis', blank=True, null=True)
|
||||
image = FileWithImagePreview(
|
||||
'Bild', blank=True, null=True,
|
||||
upload_to=overwrite_upload, validators=[ImageValidator.validate],
|
||||
help_text=ImageValidator.help_text)
|
||||
additional = models.JSONField(
|
||||
'Zusätzliche Daten (JSON)', blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Mitglied'
|
||||
verbose_name_plural = 'Mitglieder'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def export_url(self) -> str:
|
||||
return self.organization.exportBaseUrl + \
|
||||
f"#{self.organization.slug}/{self.uuid}/{self.secret}"
|
||||
|
||||
@property
|
||||
def export_os_path(self) -> PosixPath:
|
||||
return settings.EXPORT_PATH / self.organization.slug / str(self.uuid)
|
||||
|
||||
@property
|
||||
def image_save_url(self) -> str:
|
||||
return f'{self.organization.slug}/{self.uuid}'
|
||||
|
||||
@property
|
||||
def image_os_path(self) -> PosixPath:
|
||||
return settings.MEDIA_ROOT / self.organization.slug / str(self.uuid)
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
return json.dumps({
|
||||
'name': self.name,
|
||||
'org': self.organization.name,
|
||||
'id': self.member_id,
|
||||
'valid_since': str(self.valid_since or ''),
|
||||
'valid_until': str(self.valid_until or ''),
|
||||
'data': self.additional or '',
|
||||
'img': base64.b64encode(self.image.read()).decode(
|
||||
'utf-8') if self.image else None,
|
||||
})
|
||||
|
||||
@property
|
||||
def json_encrypted(self) -> bytes:
|
||||
return encrypt(self.json, self.secret)
|
||||
|
||||
def export(self):
|
||||
os.makedirs(self.export_os_path.parent, exist_ok=True)
|
||||
with open(self.export_os_path, 'wb') as fp:
|
||||
fp.write(self.json_encrypted)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk:
|
||||
try:
|
||||
prev = Member.objects.get(pk=self.pk)
|
||||
if prev.image != self.image:
|
||||
prev.image.delete(save=False)
|
||||
except Member.DoesNotExist:
|
||||
pass
|
||||
self.export()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
if self.image:
|
||||
self.image.delete(save=False)
|
||||
if os.path.isfile(self.export_os_path):
|
||||
os.remove(self.export_os_path)
|
||||
return super().delete(*args, **kwargs)
|
||||
53
backend/app/models/organization.py
Normal file
53
backend/app/models/organization.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from app.models.member import Member
|
||||
|
||||
|
||||
class Organization(models.Model):
|
||||
name = models.CharField('Name', max_length=100)
|
||||
slug = models.SlugField('URL Slug', unique=True, max_length=50)
|
||||
exportBaseUrl = models.URLField('Export Base-URL', max_length=200)
|
||||
members: 'models.QuerySet[Member]'
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Organisation'
|
||||
verbose_name_plural = 'Organisationen'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk:
|
||||
try:
|
||||
prev = Organization.objects.get(pk=self.pk)
|
||||
if prev.slug != self.slug:
|
||||
renameSlug(prev.slug, self.slug)
|
||||
except Organization.DoesNotExist:
|
||||
pass
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
deleteSlug(self.slug)
|
||||
return super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
def renameSlug(oldSlug: str, newSlug: str):
|
||||
if newSlug == oldSlug:
|
||||
return
|
||||
oldExportPath = settings.EXPORT_PATH / oldSlug
|
||||
oldMediaPath = settings.MEDIA_ROOT / oldSlug
|
||||
if os.path.exists(oldExportPath):
|
||||
os.rename(oldExportPath, settings.EXPORT_PATH / newSlug)
|
||||
if os.path.exists(oldMediaPath):
|
||||
os.rename(oldMediaPath, settings.MEDIA_ROOT / newSlug)
|
||||
|
||||
|
||||
def deleteSlug(oldSlug: str):
|
||||
shutil.rmtree(settings.EXPORT_PATH / oldSlug, ignore_errors=True)
|
||||
shutil.rmtree(settings.MEDIA_ROOT / oldSlug, ignore_errors=True)
|
||||
Reference in New Issue
Block a user