ruff check; update ValidationError params
This commit is contained in:
@@ -1,39 +1,39 @@
|
||||
# util functions for all apps
|
||||
import uuid
|
||||
|
||||
from django.contrib.admin.utils import construct_change_message
|
||||
|
||||
|
||||
def add_log_action(request, form, app_label, model, add=True):
|
||||
from django.contrib.admin.models import ADDITION, CHANGE, LogEntry
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
obj = form.save()
|
||||
content_type = ContentType.objects.get(app_label=app_label, model=model)
|
||||
change_message = construct_change_message(form, None, add)
|
||||
action_flag = ADDITION if add else CHANGE
|
||||
|
||||
LogEntry.objects.log_action(
|
||||
user_id=request.user.pk,
|
||||
content_type_id=content_type.pk,
|
||||
object_id=obj.pk,
|
||||
object_repr=str(obj),
|
||||
action_flag=action_flag,
|
||||
change_message=change_message,
|
||||
)
|
||||
|
||||
|
||||
def create_perms(sender, **kwargs):
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
group, created = Group.objects.get_or_create(name=sender.label)
|
||||
|
||||
content_types = ContentType.objects.filter(app_label=sender.label)
|
||||
for content_type in content_types:
|
||||
permissions = Permission.objects.filter(content_type=content_type)
|
||||
[group.permissions.add(permission) for permission in permissions]
|
||||
|
||||
|
||||
def create_random_id():
|
||||
return str(uuid.uuid4())[:8]
|
||||
# util functions for all apps
|
||||
import uuid
|
||||
|
||||
from django.contrib.admin.utils import construct_change_message
|
||||
|
||||
|
||||
def add_log_action(request, form, app_label, model, add=True):
|
||||
from django.contrib.admin.models import ADDITION, CHANGE, LogEntry
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
obj = form.save()
|
||||
content_type = ContentType.objects.get(app_label=app_label, model=model)
|
||||
change_message = construct_change_message(form, None, add)
|
||||
action_flag = ADDITION if add else CHANGE
|
||||
|
||||
LogEntry.objects.log_action(
|
||||
user_id=request.user.pk,
|
||||
content_type_id=content_type.pk,
|
||||
object_id=obj.pk,
|
||||
object_repr=str(obj),
|
||||
action_flag=action_flag,
|
||||
change_message=change_message,
|
||||
)
|
||||
|
||||
|
||||
def create_perms(sender, **kwargs):
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
group, _ = Group.objects.get_or_create(name=sender.label)
|
||||
|
||||
content_types = ContentType.objects.filter(app_label=sender.label)
|
||||
for content_type in content_types:
|
||||
permissions = Permission.objects.filter(content_type=content_type)
|
||||
[group.permissions.add(permission) for permission in permissions]
|
||||
|
||||
|
||||
def create_random_id():
|
||||
return str(uuid.uuid4())[:8]
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import datetime
|
||||
import decimal
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django import forms
|
||||
from django.core.validators import ValidationError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Count, Q
|
||||
from django.forms import DateInput
|
||||
from django.utils import timezone
|
||||
@@ -451,8 +450,8 @@ class ResolutionAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs) # to get the self.fields set
|
||||
|
||||
budget = decimal.Decimal(0.0)
|
||||
total = decimal.Decimal(0.0)
|
||||
budget = decimal.Decimal("0.0")
|
||||
total = decimal.Decimal("0.0")
|
||||
if resolution := kwargs.get("instance"):
|
||||
for elem in Bill.objects.filter(resolution=resolution):
|
||||
total += elem.amount
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.validators import FileExtensionValidator, ValidationError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
@@ -8,6 +10,8 @@ from members.models import Member
|
||||
|
||||
from .validators import validate_bill_file_extension
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BankData(models.Model):
|
||||
# members can be deleted but never their bank datas
|
||||
@@ -86,8 +90,18 @@ class Resolution(models.Model):
|
||||
if not Resolution.objects.filter(id=_id).exists():
|
||||
break
|
||||
else:
|
||||
msg = (
|
||||
"Es wurden zu viele Beschlüsse in dieser Woche %(week)s vom Jahr %(year)s "
|
||||
"erstellt."
|
||||
)
|
||||
logger.error(
|
||||
"Too many resolutions created in week %(week)s of year %(year)s.",
|
||||
extra={"week": week, "year": year},
|
||||
)
|
||||
raise ValidationError(
|
||||
f"Es wurden zu viele Beschlüsse in dieser Woche angelegt. (ID: {_id})"
|
||||
msg,
|
||||
code="too_many_resolutions",
|
||||
params={"week": week, "year": year},
|
||||
)
|
||||
|
||||
self.id = _id
|
||||
|
||||
@@ -27,11 +27,11 @@ def get_image_list(folder_name: str) -> list:
|
||||
|
||||
Path(thumb_path).mkdir(exist_ok=True)
|
||||
|
||||
for _file in os.listdir(image_path):
|
||||
if Path(_file).suffix.lower()[1:] not in get_available_image_extensions():
|
||||
for _file in image_path.iterdir():
|
||||
if _file.suffix.lower()[1:] not in get_available_image_extensions():
|
||||
continue
|
||||
|
||||
thumb_file_path = Path(thumb_path) / f"thumb_{_file}"
|
||||
thumb_file_path = Path(thumb_path) / f"thumb_{_file.name}"
|
||||
if not Path(thumb_file_path).exists():
|
||||
with Image.open(Path(image_path) / _file, "r") as im:
|
||||
if im._getexif() is not None:
|
||||
|
||||
@@ -1,65 +1,67 @@
|
||||
import logging
|
||||
from collections import deque
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from django.utils.text import slugify
|
||||
from django.views.generic.detail import DetailView
|
||||
|
||||
from .models import Album
|
||||
from .utils import get_folder_list
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def index(request):
|
||||
if request.user.is_authenticated:
|
||||
albums = deque(Album.objects.all())
|
||||
|
||||
# Get albums that are in the server but not in the db.
|
||||
for folder in get_folder_list():
|
||||
if not Album.objects.filter(folder_name=folder):
|
||||
albums.append(
|
||||
Album(title=folder, slug=slugify(folder), folder_name=folder, event_date=None)
|
||||
)
|
||||
|
||||
else:
|
||||
# Show only PUBLIC albums.
|
||||
albums = Album.objects.public()
|
||||
|
||||
context = {"albums": albums}
|
||||
|
||||
return render(request, "gallery/index.html", context)
|
||||
|
||||
|
||||
class AlbumDetailView(DetailView):
|
||||
model = Album
|
||||
template_name = "gallery/album.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Album.objects.public()
|
||||
if not self.request.user.is_authenticated
|
||||
else Album.objects.all()
|
||||
)
|
||||
|
||||
|
||||
class DraftAlbumDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Album
|
||||
template_name = "gallery/album.html"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
slug = self.kwargs.get(self.slug_url_kwarg)
|
||||
|
||||
if not (album := Album.objects.filter(slug=slug).first()):
|
||||
for folder in get_folder_list():
|
||||
if slug == slugify(folder):
|
||||
album = Album(
|
||||
title=folder, slug=slugify(folder), folder_name=folder, event_date=None
|
||||
)
|
||||
break
|
||||
else:
|
||||
raise Http404("Album slug not found.")
|
||||
|
||||
return album
|
||||
import logging
|
||||
from collections import deque
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from django.utils.text import slugify
|
||||
from django.views.generic.detail import DetailView
|
||||
|
||||
from .models import Album
|
||||
from .utils import get_folder_list
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def index(request):
|
||||
if request.user.is_authenticated:
|
||||
albums = deque(Album.objects.all())
|
||||
|
||||
# Get albums that are in the server but not in the db.
|
||||
for folder in get_folder_list():
|
||||
if not Album.objects.filter(folder_name=folder):
|
||||
albums.append(
|
||||
Album(title=folder, slug=slugify(folder), folder_name=folder, event_date=None)
|
||||
)
|
||||
|
||||
else:
|
||||
# Show only PUBLIC albums.
|
||||
albums = Album.objects.public()
|
||||
|
||||
context = {"albums": albums}
|
||||
|
||||
return render(request, "gallery/index.html", context)
|
||||
|
||||
|
||||
class AlbumDetailView(DetailView):
|
||||
model = Album
|
||||
template_name = "gallery/album.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Album.objects.public()
|
||||
if not self.request.user.is_authenticated
|
||||
else Album.objects.all()
|
||||
)
|
||||
|
||||
|
||||
class DraftAlbumDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Album
|
||||
template_name = "gallery/album.html"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
slug = self.kwargs.get(self.slug_url_kwarg)
|
||||
|
||||
if not (album := Album.objects.filter(slug=slug).first()):
|
||||
for folder in get_folder_list():
|
||||
if slug == slugify(folder):
|
||||
album = Album(
|
||||
title=folder, slug=slugify(folder), folder_name=folder, event_date=None
|
||||
)
|
||||
break
|
||||
else:
|
||||
msg = f"Album mit dem Slug '{slug}' nicht gefunden."
|
||||
logger.error("Album with slug '%s' not found.", slug)
|
||||
raise Http404(msg)
|
||||
|
||||
return album
|
||||
|
||||
@@ -1,205 +1,219 @@
|
||||
import logging
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.validators import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models.constraints import UniqueConstraint
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from documents.api import ep_create_new_pad, ep_get_html, ep_get_url
|
||||
from fet2020.utils import create_random_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TopicGroup(models.Model):
|
||||
title = models.CharField(verbose_name="Titel", max_length=128)
|
||||
|
||||
shortterm = models.CharField(max_length=128, unique=True, blank=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
|
||||
short_description = models.TextField(blank=True, default="")
|
||||
|
||||
order = models.PositiveSmallIntegerField(
|
||||
verbose_name="Reihenfolge",
|
||||
unique=True,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Themenbereich"
|
||||
verbose_name_plural = "Themenbereiche"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("intern:index") + "#" + self.slug
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
if not self.shortterm:
|
||||
self.shortterm = self.title
|
||||
self.slug = slugify(self.shortterm)
|
||||
|
||||
|
||||
class Topic(models.Model):
|
||||
title = models.CharField(max_length=128, verbose_name="Titel")
|
||||
slug = models.SlugField()
|
||||
|
||||
archive = models.BooleanField(default=False, verbose_name="Archiv")
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
topic_group = models.ForeignKey(
|
||||
TopicGroup,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Themenbereich",
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Thema"
|
||||
verbose_name_plural = "Themen"
|
||||
|
||||
constraints = [
|
||||
UniqueConstraint(fields=["slug", "topic_group"], name="unique_intern_slug_topic_group"),
|
||||
UniqueConstraint(
|
||||
fields=["title", "topic_group"],
|
||||
name="unique_intern_title_topic_group",
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
"topic_group_slug": self.topic_group.slug,
|
||||
"slug": self.slug,
|
||||
}
|
||||
return reverse("intern:topic", kwargs=kwargs)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
self.slug = slugify(self.title)
|
||||
|
||||
|
||||
class Attachment(models.Model):
|
||||
title = models.CharField(max_length=128, verbose_name="Titel")
|
||||
|
||||
slug = models.SlugField()
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, verbose_name="Thema")
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Anhang Ordner"
|
||||
verbose_name_plural = "Anhang Ordner"
|
||||
|
||||
constraints = [
|
||||
UniqueConstraint(fields=["slug", "topic"], name="unique_intern_slug_topic"),
|
||||
UniqueConstraint(fields=["title", "topic"], name="unique_intern_title_topic"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.topic.title + " / " + self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
"topic_group_slug": self.topic.topic_group.slug,
|
||||
"topic_slug": self.topic.slug,
|
||||
"slug": self.slug,
|
||||
}
|
||||
return reverse("intern:attachment", kwargs=kwargs)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
self.slug = slugify(self.title)
|
||||
|
||||
|
||||
class Etherpad(models.Model):
|
||||
title = models.CharField(max_length=128, verbose_name="Titel")
|
||||
|
||||
slug_id = models.CharField(default=create_random_id, editable=False, max_length=8, unique=True)
|
||||
etherpad_key = models.CharField(blank=True, max_length=50)
|
||||
date = models.DateField(default=date.today, verbose_name="Datum")
|
||||
|
||||
attachment = models.ForeignKey(
|
||||
Attachment,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Anhang Ordner",
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Etherpad"
|
||||
verbose_name_plural = "Etherpads"
|
||||
|
||||
constraints = [
|
||||
UniqueConstraint(fields=["title", "date", "attachment"], name="unique_intern_etherpad"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return ep_get_url(self.etherpad_key)
|
||||
|
||||
def clean(self):
|
||||
pad_name = slugify(str(self.slug_id) + "-" + self.title[:40])
|
||||
if len(pad_name) > 50:
|
||||
raise ValidationError(
|
||||
(
|
||||
"Name zum Erstellen des Etherpads ist zu lange - max. 50 Zeichen. ("
|
||||
"Länge: %(length)s, Name: %(pad_name)s)"
|
||||
),
|
||||
params={"length": len(pad_name), "pad_name": pad_name},
|
||||
)
|
||||
|
||||
if self.etherpad_key == "":
|
||||
if ep_create_new_pad(pad_name):
|
||||
self.etherpad_key = pad_name
|
||||
else:
|
||||
raise ValidationError(
|
||||
f"Etherpad '{pad_name}' konnte nicht erstellt werden. This should never happen!"
|
||||
)
|
||||
|
||||
@property
|
||||
def etherpad_html(self):
|
||||
return ep_get_html(self.etherpad_key)
|
||||
|
||||
def get_model_name(self):
|
||||
return self._meta.model_name
|
||||
|
||||
|
||||
class FileUpload(models.Model):
|
||||
title = models.CharField(blank=True, max_length=128, verbose_name="Titel")
|
||||
file_field = models.FileField(upload_to="uploads/intern/files/", verbose_name="Dokument")
|
||||
date = models.DateField(default=date.today, verbose_name="Datum")
|
||||
|
||||
attachment = models.ForeignKey(
|
||||
Attachment,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Anhang Ordner",
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Datei"
|
||||
verbose_name_plural = "Dateien"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def clean(self):
|
||||
if not self.title:
|
||||
self.title = Path(self.file_field.name).stem
|
||||
import logging
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models.constraints import UniqueConstraint
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from documents.api import ep_create_new_pad, ep_get_html, ep_get_url
|
||||
from fet2020.utils import create_random_id
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TopicGroup(models.Model):
|
||||
title = models.CharField(verbose_name="Titel", max_length=128)
|
||||
|
||||
shortterm = models.CharField(max_length=128, unique=True, blank=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
|
||||
short_description = models.TextField(blank=True, default="")
|
||||
|
||||
order = models.PositiveSmallIntegerField(
|
||||
verbose_name="Reihenfolge",
|
||||
unique=True,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Themenbereich"
|
||||
verbose_name_plural = "Themenbereiche"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("intern:index") + "#" + self.slug
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
if not self.shortterm:
|
||||
self.shortterm = self.title
|
||||
self.slug = slugify(self.shortterm)
|
||||
|
||||
|
||||
class Topic(models.Model):
|
||||
title = models.CharField(max_length=128, verbose_name="Titel")
|
||||
slug = models.SlugField()
|
||||
|
||||
archive = models.BooleanField(default=False, verbose_name="Archiv")
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
topic_group = models.ForeignKey(
|
||||
TopicGroup,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Themenbereich",
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Thema"
|
||||
verbose_name_plural = "Themen"
|
||||
|
||||
constraints = [
|
||||
UniqueConstraint(fields=["slug", "topic_group"], name="unique_intern_slug_topic_group"),
|
||||
UniqueConstraint(
|
||||
fields=["title", "topic_group"],
|
||||
name="unique_intern_title_topic_group",
|
||||
),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
"topic_group_slug": self.topic_group.slug,
|
||||
"slug": self.slug,
|
||||
}
|
||||
return reverse("intern:topic", kwargs=kwargs)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
self.slug = slugify(self.title)
|
||||
|
||||
|
||||
class Attachment(models.Model):
|
||||
title = models.CharField(max_length=128, verbose_name="Titel")
|
||||
|
||||
slug = models.SlugField()
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, verbose_name="Thema")
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Anhang Ordner"
|
||||
verbose_name_plural = "Anhang Ordner"
|
||||
|
||||
constraints = [
|
||||
UniqueConstraint(fields=["slug", "topic"], name="unique_intern_slug_topic"),
|
||||
UniqueConstraint(fields=["title", "topic"], name="unique_intern_title_topic"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.topic.title + " / " + self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {
|
||||
"topic_group_slug": self.topic.topic_group.slug,
|
||||
"topic_slug": self.topic.slug,
|
||||
"slug": self.slug,
|
||||
}
|
||||
return reverse("intern:attachment", kwargs=kwargs)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
self.slug = slugify(self.title)
|
||||
|
||||
|
||||
class Etherpad(models.Model):
|
||||
title = models.CharField(max_length=128, verbose_name="Titel")
|
||||
|
||||
slug_id = models.CharField(default=create_random_id, editable=False, max_length=8, unique=True)
|
||||
etherpad_key = models.CharField(blank=True, max_length=50)
|
||||
date = models.DateField(default=date.today, verbose_name="Datum")
|
||||
|
||||
attachment = models.ForeignKey(
|
||||
Attachment,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Anhang Ordner",
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Etherpad"
|
||||
verbose_name_plural = "Etherpads"
|
||||
|
||||
constraints = [
|
||||
UniqueConstraint(fields=["title", "date", "attachment"], name="unique_intern_etherpad"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return ep_get_url(self.etherpad_key)
|
||||
|
||||
def clean(self):
|
||||
pad_name = slugify(str(self.slug_id) + "-" + self.title[:40])
|
||||
if len(pad_name) > 50:
|
||||
msg = (
|
||||
"Name ist zum Erstellen des Etherpads zu lange - max. 50 Zeichen. (Länge: "
|
||||
"%(length)s, Name: %(pad_name)s)"
|
||||
)
|
||||
logger.error(
|
||||
(
|
||||
"Name '%(pad_name)s' is too long for creating a new etherpad - max. 50 "
|
||||
"characters. (Length: %(length)s)",
|
||||
),
|
||||
extra={"length": len(pad_name), "pad_name": pad_name},
|
||||
)
|
||||
raise ValidationError(
|
||||
msg,
|
||||
code="pad_name_too_long",
|
||||
params={"length": len(pad_name), "pad_name": pad_name},
|
||||
)
|
||||
|
||||
if self.etherpad_key == "":
|
||||
if ep_create_new_pad(pad_name):
|
||||
self.etherpad_key = pad_name
|
||||
else:
|
||||
msg = "Etherpad '%(pad_name)s' konnte nicht erstellt werden."
|
||||
logger.error(
|
||||
"Etherpad '%(pad_name)s' could not be created. This should never happen!",
|
||||
extra={"pad_name": pad_name},
|
||||
)
|
||||
raise ValidationError(
|
||||
msg, code="pad_creation_failed", params={"pad_name": pad_name}
|
||||
)
|
||||
|
||||
@property
|
||||
def etherpad_html(self):
|
||||
return ep_get_html(self.etherpad_key)
|
||||
|
||||
def get_model_name(self):
|
||||
return self._meta.model_name
|
||||
|
||||
|
||||
class FileUpload(models.Model):
|
||||
title = models.CharField(blank=True, max_length=128, verbose_name="Titel")
|
||||
file_field = models.FileField(upload_to="uploads/intern/files/", verbose_name="Dokument")
|
||||
date = models.DateField(default=date.today, verbose_name="Datum")
|
||||
|
||||
attachment = models.ForeignKey(
|
||||
Attachment,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Anhang Ordner",
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Datei"
|
||||
verbose_name_plural = "Dateien"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def clean(self):
|
||||
if not self.title:
|
||||
self.title = Path(self.file_field.name).stem
|
||||
|
||||
@@ -1,230 +1,232 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .forms import (
|
||||
ActiveMemberForm,
|
||||
InactiveMemberForm,
|
||||
JobForm,
|
||||
JobGroupForm,
|
||||
JobInlineForm,
|
||||
MemberForm,
|
||||
)
|
||||
from .models import Job, JobGroup, JobMember, Member
|
||||
|
||||
|
||||
class MemberRoleFilter(admin.SimpleListFilter):
|
||||
title = "Rolle"
|
||||
parameter_name = "role"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
("A", "Aktiv"),
|
||||
("P", "Pension"),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() in Member.MemberRole:
|
||||
return queryset.filter(role=self.value())
|
||||
|
||||
|
||||
class JobMemberInline(admin.TabularInline):
|
||||
model = JobMember
|
||||
extra = 0
|
||||
|
||||
|
||||
class JobOverviewInline(JobMemberInline):
|
||||
verbose_name = "Tätigkeit"
|
||||
verbose_name_plural = "Tätigkeitsübersicht"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return JobMember.members.get_all_jobs()
|
||||
|
||||
|
||||
class ActiveMemberInline(JobMemberInline):
|
||||
form = ActiveMemberForm
|
||||
verbose_name = "Mitglied"
|
||||
verbose_name_plural = "Aktive Mitglieder Liste"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return JobMember.active_member.get_queryset()
|
||||
|
||||
|
||||
class InactiveMemberInline(JobMemberInline):
|
||||
form = InactiveMemberForm
|
||||
verbose_name = "Mitglied"
|
||||
verbose_name_plural = "Inaktive Mitglieder Liste"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return JobMember.inactive_member.get_queryset()
|
||||
|
||||
|
||||
class JobInline(admin.TabularInline):
|
||||
form = JobInlineForm
|
||||
model = Job
|
||||
extra = 0
|
||||
show_change_link = True
|
||||
|
||||
|
||||
@admin.register(Member)
|
||||
class MemberAdmin(admin.ModelAdmin):
|
||||
form = MemberForm
|
||||
model = Member
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
(
|
||||
"firstname",
|
||||
"surname",
|
||||
),
|
||||
"nickname",
|
||||
"mailaccount",
|
||||
"role",
|
||||
"image",
|
||||
"description",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = (JobOverviewInline,)
|
||||
|
||||
list_display = ["nickname", "firstname", "surname", "mailaccount", "role"]
|
||||
list_filter = [MemberRoleFilter]
|
||||
ordering = ["firstname"]
|
||||
search_fields = ["firstname", "surname", "nickname", "mailaccount"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(Job)
|
||||
class JobAdmin(admin.ModelAdmin):
|
||||
form = JobForm
|
||||
model = Job
|
||||
|
||||
list_display = ["name", "job_group"]
|
||||
ordering = ["name"]
|
||||
search_fields = ["name"]
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"name",
|
||||
"job_group",
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Permalink",
|
||||
{
|
||||
"fields": (
|
||||
"shortterm",
|
||||
"slug",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = (ActiveMemberInline, InactiveMemberInline)
|
||||
readonly_fields = ["slug"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(JobGroup)
|
||||
class JobGroupAdmin(admin.ModelAdmin):
|
||||
form = JobGroupForm
|
||||
model = JobGroup
|
||||
|
||||
list_display = ["name"]
|
||||
ordering = ["name"]
|
||||
search_fields = ["name"]
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"name",
|
||||
"description",
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Permalink",
|
||||
{
|
||||
"fields": (
|
||||
"shortterm",
|
||||
"slug",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = (JobInline,)
|
||||
readonly_fields = ["slug"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
from django.contrib import admin
|
||||
|
||||
from .forms import (
|
||||
ActiveMemberForm,
|
||||
InactiveMemberForm,
|
||||
JobForm,
|
||||
JobGroupForm,
|
||||
JobInlineForm,
|
||||
MemberForm,
|
||||
)
|
||||
from .models import Job, JobGroup, JobMember, Member
|
||||
|
||||
|
||||
class MemberRoleFilter(admin.SimpleListFilter):
|
||||
title = "Rolle"
|
||||
parameter_name = "role"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
("A", "Aktiv"),
|
||||
("P", "Pension"),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() in Member.MemberRole:
|
||||
return queryset.filter(role=self.value())
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class JobMemberInline(admin.TabularInline):
|
||||
model = JobMember
|
||||
extra = 0
|
||||
|
||||
|
||||
class JobOverviewInline(JobMemberInline):
|
||||
verbose_name = "Tätigkeit"
|
||||
verbose_name_plural = "Tätigkeitsübersicht"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return JobMember.members.get_all_jobs()
|
||||
|
||||
|
||||
class ActiveMemberInline(JobMemberInline):
|
||||
form = ActiveMemberForm
|
||||
verbose_name = "Mitglied"
|
||||
verbose_name_plural = "Aktive Mitglieder Liste"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return JobMember.active_member.get_queryset()
|
||||
|
||||
|
||||
class InactiveMemberInline(JobMemberInline):
|
||||
form = InactiveMemberForm
|
||||
verbose_name = "Mitglied"
|
||||
verbose_name_plural = "Inaktive Mitglieder Liste"
|
||||
|
||||
def get_queryset(self, request):
|
||||
return JobMember.inactive_member.get_queryset()
|
||||
|
||||
|
||||
class JobInline(admin.TabularInline):
|
||||
form = JobInlineForm
|
||||
model = Job
|
||||
extra = 0
|
||||
show_change_link = True
|
||||
|
||||
|
||||
@admin.register(Member)
|
||||
class MemberAdmin(admin.ModelAdmin):
|
||||
form = MemberForm
|
||||
model = Member
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
(
|
||||
"firstname",
|
||||
"surname",
|
||||
),
|
||||
"nickname",
|
||||
"mailaccount",
|
||||
"role",
|
||||
"image",
|
||||
"description",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = (JobOverviewInline,)
|
||||
|
||||
list_display = ["nickname", "firstname", "surname", "mailaccount", "role"]
|
||||
list_filter = [MemberRoleFilter]
|
||||
ordering = ["firstname"]
|
||||
search_fields = ["firstname", "surname", "nickname", "mailaccount"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(Job)
|
||||
class JobAdmin(admin.ModelAdmin):
|
||||
form = JobForm
|
||||
model = Job
|
||||
|
||||
list_display = ["name", "job_group"]
|
||||
ordering = ["name"]
|
||||
search_fields = ["name"]
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"name",
|
||||
"job_group",
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Permalink",
|
||||
{
|
||||
"fields": (
|
||||
"shortterm",
|
||||
"slug",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = (ActiveMemberInline, InactiveMemberInline)
|
||||
readonly_fields = ["slug"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(JobGroup)
|
||||
class JobGroupAdmin(admin.ModelAdmin):
|
||||
form = JobGroupForm
|
||||
model = JobGroup
|
||||
|
||||
list_display = ["name"]
|
||||
ordering = ["name"]
|
||||
search_fields = ["name"]
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"name",
|
||||
"description",
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"Permalink",
|
||||
{
|
||||
"fields": (
|
||||
"shortterm",
|
||||
"slug",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
inlines = (JobInline,)
|
||||
readonly_fields = ["slug"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
return super().add_view(
|
||||
request,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichfelder."
|
||||
return super().change_view(
|
||||
request,
|
||||
object_id,
|
||||
form_url,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
@@ -1,210 +1,207 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import ValidationError, validate_email
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
|
||||
from .managers import (
|
||||
ActiveJobMemberManager,
|
||||
InactiveJobMemberManager,
|
||||
JobMemberManager,
|
||||
MemberManager,
|
||||
)
|
||||
from .validators import (
|
||||
validate_domainonly_email,
|
||||
validate_file_size,
|
||||
validate_image_dimension,
|
||||
)
|
||||
|
||||
fet_logo_url = settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Member(models.Model):
|
||||
firstname = models.CharField("Vorname", max_length=128)
|
||||
surname = models.CharField("Nachname", max_length=128)
|
||||
nickname = models.CharField("Spitzname", max_length=128)
|
||||
|
||||
# LDAP Username
|
||||
username = models.CharField(max_length=128, blank=True)
|
||||
|
||||
# fet mail account
|
||||
mailaccount = models.CharField(
|
||||
"Mailadresse",
|
||||
unique=True,
|
||||
max_length=128,
|
||||
validators=[validate_email, validate_domainonly_email],
|
||||
error_messages={
|
||||
"unique": "Diese Mailadresse existiert schon.",
|
||||
},
|
||||
)
|
||||
|
||||
class MemberRole(models.TextChoices):
|
||||
ACTIVE = "A", "Active"
|
||||
PENSION = "P", "Pension"
|
||||
|
||||
role = models.CharField(
|
||||
"Rolle",
|
||||
max_length=1,
|
||||
choices=MemberRole.choices,
|
||||
default=MemberRole.ACTIVE,
|
||||
)
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
image = ThumbnailerImageField(
|
||||
upload_to="uploads/members/image/",
|
||||
validators=[validate_file_size, validate_image_dimension],
|
||||
)
|
||||
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# Managers
|
||||
objects = models.Manager()
|
||||
all_members = MemberManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Mitglied"
|
||||
verbose_name_plural = "Mitglieder"
|
||||
|
||||
def __str__(self):
|
||||
return self.firstname + " " + self.surname
|
||||
|
||||
# need to have 'View on site' link in admin app
|
||||
def get_absolute_url(self):
|
||||
return reverse("members:member", kwargs={"pk": self.pk})
|
||||
|
||||
def clean(self):
|
||||
if not self.image:
|
||||
raise ValidationError("Es fehlt das Profilbild.")
|
||||
|
||||
if self.username:
|
||||
try:
|
||||
user = User.objects.get(username=self.username.lower())
|
||||
except User.DoesNotExist as e:
|
||||
logger.info("Username not found. Error: %s", e)
|
||||
else:
|
||||
user.first_name = self.firstname
|
||||
user.save()
|
||||
|
||||
def get_model_name(self):
|
||||
return self._meta.model_name
|
||||
|
||||
@property
|
||||
def image_url(self):
|
||||
return self.image.url if self.image else fet_logo_url
|
||||
|
||||
@property
|
||||
def avatar_url(self):
|
||||
return self.image["avatar"].url if self.image else fet_logo_url
|
||||
|
||||
@property
|
||||
def portrait_url(self):
|
||||
return self.image["portrait"].url if self.image else fet_logo_url
|
||||
|
||||
@property
|
||||
def thumb_url(self):
|
||||
return self.image["thumb"].url if self.image else fet_logo_url
|
||||
|
||||
|
||||
class JobGroup(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
shortterm = models.CharField(max_length=128, unique=True, blank=True)
|
||||
slug = models.SlugField(unique=True, null=True, blank=True)
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
# Managers
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tätigkeitsbereich"
|
||||
verbose_name_plural = "Tätigkeitsbereiche"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# need to have 'View on site' link in admin app
|
||||
def get_absolute_url(self):
|
||||
return reverse("members:jobs", kwargs={"slug": self.slug})
|
||||
|
||||
def clean(self):
|
||||
if not self.shortterm:
|
||||
self.shortterm = slugify(self.name)
|
||||
self.slug = slugify(self.shortterm)
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
shortterm = models.CharField(max_length=128, unique=True, blank=True)
|
||||
slug = models.SlugField(unique=True, null=True, blank=True)
|
||||
|
||||
order = models.PositiveSmallIntegerField(null=True, blank=True)
|
||||
|
||||
job_group = models.ForeignKey(
|
||||
JobGroup,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Job Gruppe",
|
||||
)
|
||||
|
||||
# Managers
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
ordering = (F("order").asc(nulls_last=True), "name")
|
||||
|
||||
verbose_name = "Tätigkeit"
|
||||
verbose_name_plural = "Tätigkeiten"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("members:jobs", kwargs={"slug": self.job_group.slug}) + "#" + self.slug
|
||||
|
||||
def clean(self):
|
||||
if not self.shortterm:
|
||||
self.shortterm = slugify(self.name)
|
||||
self.slug = slugify(self.shortterm)
|
||||
|
||||
|
||||
class JobMember(models.Model):
|
||||
member = models.ForeignKey(
|
||||
Member,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Mitglied",
|
||||
)
|
||||
job = models.ForeignKey(
|
||||
Job,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Tätigkeit",
|
||||
)
|
||||
|
||||
job_start = models.DateField("Job Start")
|
||||
job_end = models.DateField("Job Ende", null=True, blank=True)
|
||||
|
||||
class JobRole(models.TextChoices):
|
||||
PRESIDENT = ("10", "Vorsitz")
|
||||
VICE_PRESIDENT = ("20", "Stv. Vorsitz")
|
||||
SECOND_VICE_PRESIDENT = ("30", "2. Stv. Vorsitz")
|
||||
PERSON_RESPONSIBLE = ("40", "Verantwortliche_r")
|
||||
MEMBER = ("50", "Mitglied")
|
||||
SUBSTITUTE_MEMBER = ("60", "Ersatzmitglied")
|
||||
|
||||
job_role = models.CharField(max_length=2, choices=JobRole.choices, default=JobRole.MEMBER)
|
||||
|
||||
objects = models.Manager()
|
||||
members = JobMemberManager()
|
||||
active_member = ActiveJobMemberManager()
|
||||
inactive_member = InactiveJobMemberManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.job.name
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import validate_email
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
|
||||
from .managers import (
|
||||
ActiveJobMemberManager,
|
||||
InactiveJobMemberManager,
|
||||
JobMemberManager,
|
||||
MemberManager,
|
||||
)
|
||||
from .validators import (
|
||||
validate_domainonly_email,
|
||||
validate_file_size,
|
||||
validate_image_dimension,
|
||||
)
|
||||
|
||||
fet_logo_url = settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Member(models.Model):
|
||||
firstname = models.CharField("Vorname", max_length=128)
|
||||
surname = models.CharField("Nachname", max_length=128)
|
||||
nickname = models.CharField("Spitzname", max_length=128)
|
||||
|
||||
# LDAP Username
|
||||
username = models.CharField(max_length=128, blank=True)
|
||||
|
||||
# fet mail account
|
||||
mailaccount = models.CharField(
|
||||
"Mailadresse",
|
||||
unique=True,
|
||||
max_length=128,
|
||||
validators=[validate_email, validate_domainonly_email],
|
||||
error_messages={
|
||||
"unique": "Diese Mailadresse existiert schon.",
|
||||
},
|
||||
)
|
||||
|
||||
class MemberRole(models.TextChoices):
|
||||
ACTIVE = "A", "Active"
|
||||
PENSION = "P", "Pension"
|
||||
|
||||
role = models.CharField(
|
||||
"Rolle",
|
||||
max_length=1,
|
||||
choices=MemberRole.choices,
|
||||
default=MemberRole.ACTIVE,
|
||||
)
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
image = ThumbnailerImageField(
|
||||
upload_to="uploads/members/image/",
|
||||
validators=[validate_file_size, validate_image_dimension],
|
||||
)
|
||||
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# Managers
|
||||
objects = models.Manager()
|
||||
all_members = MemberManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Mitglied"
|
||||
verbose_name_plural = "Mitglieder"
|
||||
|
||||
def __str__(self):
|
||||
return self.firstname + " " + self.surname
|
||||
|
||||
# need to have 'View on site' link in admin app
|
||||
def get_absolute_url(self):
|
||||
return reverse("members:member", kwargs={"pk": self.pk})
|
||||
|
||||
def clean(self):
|
||||
if self.username:
|
||||
try:
|
||||
user = User.objects.get(username=self.username.lower())
|
||||
except User.DoesNotExist as e:
|
||||
logger.info("Username not found. Error: %s", e)
|
||||
else:
|
||||
user.first_name = self.firstname
|
||||
user.save()
|
||||
|
||||
def get_model_name(self):
|
||||
return self._meta.model_name
|
||||
|
||||
@property
|
||||
def image_url(self):
|
||||
return self.image.url if self.image else fet_logo_url
|
||||
|
||||
@property
|
||||
def avatar_url(self):
|
||||
return self.image["avatar"].url if self.image else fet_logo_url
|
||||
|
||||
@property
|
||||
def portrait_url(self):
|
||||
return self.image["portrait"].url if self.image else fet_logo_url
|
||||
|
||||
@property
|
||||
def thumb_url(self):
|
||||
return self.image["thumb"].url if self.image else fet_logo_url
|
||||
|
||||
|
||||
class JobGroup(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
shortterm = models.CharField(max_length=128, unique=True, blank=True)
|
||||
slug = models.SlugField(unique=True, null=True, blank=True)
|
||||
|
||||
description = models.TextField(blank=True, default="")
|
||||
|
||||
# Managers
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tätigkeitsbereich"
|
||||
verbose_name_plural = "Tätigkeitsbereiche"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# need to have 'View on site' link in admin app
|
||||
def get_absolute_url(self):
|
||||
return reverse("members:jobs", kwargs={"slug": self.slug})
|
||||
|
||||
def clean(self):
|
||||
if not self.shortterm:
|
||||
self.shortterm = slugify(self.name)
|
||||
self.slug = slugify(self.shortterm)
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
shortterm = models.CharField(max_length=128, unique=True, blank=True)
|
||||
slug = models.SlugField(unique=True, null=True, blank=True)
|
||||
|
||||
order = models.PositiveSmallIntegerField(null=True, blank=True)
|
||||
|
||||
job_group = models.ForeignKey(
|
||||
JobGroup,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Job Gruppe",
|
||||
)
|
||||
|
||||
# Managers
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
ordering = (F("order").asc(nulls_last=True), "name")
|
||||
|
||||
verbose_name = "Tätigkeit"
|
||||
verbose_name_plural = "Tätigkeiten"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("members:jobs", kwargs={"slug": self.job_group.slug}) + "#" + self.slug
|
||||
|
||||
def clean(self):
|
||||
if not self.shortterm:
|
||||
self.shortterm = slugify(self.name)
|
||||
self.slug = slugify(self.shortterm)
|
||||
|
||||
|
||||
class JobMember(models.Model):
|
||||
member = models.ForeignKey(
|
||||
Member,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Mitglied",
|
||||
)
|
||||
job = models.ForeignKey(
|
||||
Job,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Tätigkeit",
|
||||
)
|
||||
|
||||
job_start = models.DateField("Job Start")
|
||||
job_end = models.DateField("Job Ende", null=True, blank=True)
|
||||
|
||||
class JobRole(models.TextChoices):
|
||||
PRESIDENT = ("10", "Vorsitz")
|
||||
VICE_PRESIDENT = ("20", "Stv. Vorsitz")
|
||||
SECOND_VICE_PRESIDENT = ("30", "2. Stv. Vorsitz")
|
||||
PERSON_RESPONSIBLE = ("40", "Verantwortliche_r")
|
||||
MEMBER = ("50", "Mitglied")
|
||||
SUBSTITUTE_MEMBER = ("60", "Ersatzmitglied")
|
||||
|
||||
job_role = models.CharField(max_length=2, choices=JobRole.choices, default=JobRole.MEMBER)
|
||||
|
||||
objects = models.Manager()
|
||||
members = JobMemberManager()
|
||||
active_member = ActiveJobMemberManager()
|
||||
inactive_member = InactiveJobMemberManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.job.name
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
from collections import deque
|
||||
|
||||
from django import template
|
||||
from django.db.models import F
|
||||
|
||||
from members.models import JobGroup, JobMember
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag("members/partials/jobs_sidebar.html")
|
||||
def get_jobs_sidebar(slug):
|
||||
job_groups = deque(JobGroup.objects.all())
|
||||
|
||||
# remove job group if there is no longer an active member
|
||||
for elem in job_groups.copy():
|
||||
job_members = JobMember.active_member.get_all(slug=elem.slug)
|
||||
if not job_members:
|
||||
job_groups.remove(elem)
|
||||
|
||||
job_members = JobMember.active_member.get_all(slug=slug).order_by(
|
||||
F("job__order").asc(nulls_last=True),
|
||||
"job__name",
|
||||
)
|
||||
active_job_group = JobGroup.objects.filter(slug=slug).first()
|
||||
|
||||
context = {
|
||||
"job_groups": job_groups,
|
||||
"job_members": job_members,
|
||||
"active_job_group": active_job_group,
|
||||
}
|
||||
|
||||
return context
|
||||
from collections import deque
|
||||
|
||||
from django import template
|
||||
from django.db.models import F
|
||||
|
||||
from members.models import JobGroup, JobMember
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag("members/partials/jobs_sidebar.html")
|
||||
def get_jobs_sidebar(slug):
|
||||
job_groups = deque(JobGroup.objects.all())
|
||||
|
||||
# remove job group if there is no longer an active member
|
||||
for elem in job_groups.copy():
|
||||
job_members = JobMember.active_member.get_all(slug=elem.slug)
|
||||
if not job_members:
|
||||
job_groups.remove(elem)
|
||||
|
||||
job_members = JobMember.active_member.get_all(slug=slug).order_by(
|
||||
F("job__order").asc(nulls_last=True),
|
||||
"job__name",
|
||||
)
|
||||
active_job_group = JobGroup.objects.filter(slug=slug).first()
|
||||
|
||||
return {
|
||||
"job_groups": job_groups,
|
||||
"job_members": job_members,
|
||||
"active_job_group": active_job_group,
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
from django.core.validators import ValidationError
|
||||
|
||||
|
||||
def validate_domainonly_email(value):
|
||||
if "fet.at" not in value:
|
||||
raise ValidationError("In der Mailadresse fehlt die richtige Domäne.")
|
||||
|
||||
|
||||
def validate_file_size(value):
|
||||
if value.size > 10 * 1024 * 1024:
|
||||
raise ValidationError("Die maximale Dateigröße ist 10MB.")
|
||||
|
||||
|
||||
def validate_image_dimension(value):
|
||||
if value.height < 150 or value.width < 150:
|
||||
raise ValidationError(
|
||||
"Das Bild ist zu klein. (Höhe: %(height)s, Breite: %(width)s)",
|
||||
params={
|
||||
"height": value.height,
|
||||
"width": value.width,
|
||||
},
|
||||
)
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
def validate_domainonly_email(value):
|
||||
if "fet.at" not in value:
|
||||
msg = "In der Mailadresse fehlt die richtige Domäne."
|
||||
raise ValidationError(msg, code="invalid_domain")
|
||||
|
||||
|
||||
def validate_file_size(value):
|
||||
if value.size > 10 * 1024 * 1024:
|
||||
msg = "Die maximale Dateigröße ist 10MB."
|
||||
raise ValidationError(msg, code="file_too_large")
|
||||
|
||||
|
||||
def validate_image_dimension(value):
|
||||
if value.height < 150 or value.width < 150:
|
||||
msg = "Das Bild ist zu klein. (Höhe: %(height)s, Breite: %(width)s)"
|
||||
raise ValidationError(
|
||||
msg,
|
||||
code="image_too_small",
|
||||
params={
|
||||
"height": value.height,
|
||||
"width": value.width,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -23,10 +23,7 @@ def create_token(username, masterpassword):
|
||||
|
||||
@authenticated_user
|
||||
def index(request):
|
||||
context = {
|
||||
"mctoken": "",
|
||||
"valid_master_pwd": True
|
||||
}
|
||||
context = {"mctoken": "", "valid_master_pwd": True}
|
||||
|
||||
masterpassword = settings.MC_MASTERPASSWORD
|
||||
|
||||
|
||||
@@ -179,8 +179,7 @@ class EventManager(PublishedManager, models.Manager):
|
||||
|
||||
def past_events(self, public=True):
|
||||
date_today = timezone.now().date()
|
||||
qs = self.published(public).filter(event_start__lt=date_today)
|
||||
return qs
|
||||
return self.published(public).filter(event_start__lt=date_today)
|
||||
|
||||
|
||||
class FetMeetingManager(PublishedManager, models.Manager):
|
||||
@@ -204,5 +203,4 @@ class FetMeetingManager(PublishedManager, models.Manager):
|
||||
|
||||
def past_events(self):
|
||||
date_today = timezone.now().date()
|
||||
qs = self.published().filter(event_start__lt=date_today)
|
||||
return qs
|
||||
return self.published().filter(event_start__lt=date_today)
|
||||
|
||||
@@ -1,376 +1,384 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from core.models import CustomFlatPage
|
||||
from documents.api import ep_create_new_pad, ep_get_html, ep_get_url, ep_pad_exists, ep_set_html
|
||||
|
||||
from .choices import PostType, Status
|
||||
from .managers import (
|
||||
AllEventManager,
|
||||
ArticleManager,
|
||||
EventManager,
|
||||
FetMeetingManager,
|
||||
NewsManager,
|
||||
PostManager,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_pad_for_post(slug, item="agenda"):
|
||||
logger.info("Pad-Type: %s", item)
|
||||
|
||||
pad_id = slug + "-" + item
|
||||
if not ep_create_new_pad(pad_id):
|
||||
return ""
|
||||
|
||||
# Set template into the newly created pad if it exists.
|
||||
if page := CustomFlatPage.objects.filter(title__iexact=item).first():
|
||||
ep_set_html(pad_id, page.content)
|
||||
logger.info("Template '%s' is set.", page.title)
|
||||
|
||||
return pad_id
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
# legacy id is for the posts from the old website
|
||||
legacy_id = models.IntegerField(null=True, blank=True)
|
||||
|
||||
title = models.CharField(verbose_name="Titel", max_length=200)
|
||||
subtitle = models.CharField(max_length=500, blank=True, default="")
|
||||
tags = TaggableManager(blank=True)
|
||||
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
|
||||
body = models.TextField(blank=True, default="")
|
||||
image = models.ImageField(null=True, blank=True)
|
||||
|
||||
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
public_date = models.DateField(
|
||||
verbose_name="Veröffentlichung",
|
||||
null=True,
|
||||
blank=True,
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
post_type = models.CharField(max_length=1, choices=PostType.choices, editable=True)
|
||||
|
||||
status = models.CharField(
|
||||
verbose_name="Status", max_length=2, choices=Status.choices, default=Status.DRAFT
|
||||
)
|
||||
|
||||
# post is pinned at main page
|
||||
is_pinned = models.BooleanField(verbose_name="ANGEHEFTET", default=False)
|
||||
|
||||
# addional infos for events
|
||||
event_start = models.DateTimeField(verbose_name="Event Start", null=True, blank=True)
|
||||
event_end = models.DateTimeField(verbose_name="Event Ende", null=True, blank=True)
|
||||
event_place = models.CharField(max_length=200, blank=True, default="")
|
||||
|
||||
# protocol for fet meeting
|
||||
has_protocol = models.BooleanField(default=False)
|
||||
has_agenda = models.BooleanField(default=False)
|
||||
protocol_key = models.CharField(max_length=200, blank=True, default="")
|
||||
agenda_key = models.CharField(max_length=200, blank=True, default="")
|
||||
|
||||
# TimeStamps
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# Managers
|
||||
objects = PostManager()
|
||||
articles = ArticleManager()
|
||||
|
||||
def __str__(self):
|
||||
return "Post ({}, {}): {}".format(
|
||||
self.slug,
|
||||
self.public_date.strftime("%d.%m.%Y"),
|
||||
self.title,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# save the post with some defaults
|
||||
if not self.public_date:
|
||||
self.public_date = timezone.now().date()
|
||||
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.public_date) + "-" + slugify(self.title)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("posts:post", kwargs={"slug": self.slug})
|
||||
|
||||
# "#" for backward compatibility
|
||||
_possible_empty_key_value = ["#", "", None]
|
||||
|
||||
@property
|
||||
def agenda_html(self) -> str | None:
|
||||
if self.agenda_key in self._possible_empty_key_value:
|
||||
return None
|
||||
|
||||
return ep_get_html(self.agenda_key)
|
||||
|
||||
@agenda_html.setter
|
||||
def agenda_html(self, value: str) -> str | None:
|
||||
if self.agenda_key in self._possible_empty_key_value:
|
||||
self.create_agenda_key()
|
||||
|
||||
if not value or not self.agenda_key:
|
||||
return None
|
||||
|
||||
ep_set_html(self.agenda_key, value)
|
||||
logger.info("Set agenda etherpad '%s' for post '%s'.", self.agenda_key, self.slug)
|
||||
return value
|
||||
|
||||
@property
|
||||
def protocol_html(self) -> str | None:
|
||||
if self.protocol_key in self._possible_empty_key_value:
|
||||
return None
|
||||
|
||||
return ep_get_html(self.protocol_key)
|
||||
|
||||
@protocol_html.setter
|
||||
def protocol_html(self, value: str) -> str | None:
|
||||
if self.protocol_key in self._possible_empty_key_value:
|
||||
self.create_protocol_key()
|
||||
|
||||
if not value or not self.protocol_key:
|
||||
return None
|
||||
|
||||
ep_set_html(self.protocol_key, value)
|
||||
logger.info("Set protocol etherpad '%s' for post '%s'.", self.protocol_key, self.slug)
|
||||
return value
|
||||
|
||||
_agenda_filename = None
|
||||
_agenda_url = None
|
||||
|
||||
@property
|
||||
def agenda_url(self) -> str | None:
|
||||
if not self.has_agenda:
|
||||
self._agenda_url = None
|
||||
self._agenda_filename = None
|
||||
return self._agenda_url
|
||||
|
||||
if self._agenda_url:
|
||||
return self._agenda_url
|
||||
|
||||
if url := ep_get_url(self.agenda_key):
|
||||
self._agenda_url = url
|
||||
self._agenda_filename = self.slug + "-agenda.pdf"
|
||||
else:
|
||||
self._agenda_url = None
|
||||
self._agenda_filename = None
|
||||
|
||||
return self._agenda_url
|
||||
|
||||
@property
|
||||
def agenda_filename(self) -> str | None:
|
||||
# TODO: fix pdf render
|
||||
# if self._agenda_filename:
|
||||
# return self._agenda_filename
|
||||
|
||||
# if self.has_agenda and self.agenda_url:
|
||||
# return self.slug + "-agenda.pdf"
|
||||
|
||||
return None
|
||||
|
||||
_protocol_filename = None
|
||||
_protocol_url = None
|
||||
|
||||
@property
|
||||
def protocol_url(self) -> str | None:
|
||||
if not self.has_protocol:
|
||||
self._protocol_url = None
|
||||
self._protocol_filename = None
|
||||
return self._protocol_url
|
||||
|
||||
if self._protocol_url:
|
||||
return self._protocol_url
|
||||
|
||||
if url := ep_get_url(self.protocol_key):
|
||||
self._protocol_url = url
|
||||
self._protocol_filename = self.slug + "-protokoll.pdf"
|
||||
else:
|
||||
self._protocol_url = None
|
||||
self._protocol_filename = None
|
||||
|
||||
return self._protocol_url
|
||||
|
||||
@property
|
||||
def protocol_filename(self) -> str | None:
|
||||
# TODO: fix pdf render
|
||||
# if self._protocol_filename:
|
||||
# return self._protocol_filename
|
||||
|
||||
# if self.has_protocol and self.protocol_url:
|
||||
# return self.slug + "-protokoll.pdf"
|
||||
|
||||
return None
|
||||
|
||||
def create_agenda_key(self) -> None:
|
||||
"""
|
||||
Create a Etherpad Id for the Pad associated to this post.
|
||||
Create the pad if it doesn't exist.
|
||||
"""
|
||||
if self.slug:
|
||||
self.agenda_key = create_pad_for_post(self.slug, "agenda")
|
||||
|
||||
def create_protocol_key(self) -> None:
|
||||
"""
|
||||
Create a Etherpad Id for the Pad associated to this post.
|
||||
Create the pad if it doesn't exist.
|
||||
"""
|
||||
if self.slug:
|
||||
self.protocol_key = create_pad_for_post(self.slug, "protocol")
|
||||
|
||||
def get_model_name(self):
|
||||
return self._meta.model_name
|
||||
|
||||
@property
|
||||
def three_tag_names(self):
|
||||
return self.tags.names()[:3]
|
||||
|
||||
@property
|
||||
def tag_names(self):
|
||||
return [t for t in self.tags.names()]
|
||||
|
||||
@property
|
||||
def imageurl(self) -> str:
|
||||
return (
|
||||
self.image.url if self.image else settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if self.event_end and self.event_end < self.event_start:
|
||||
raise ValidationError("Das Ende des Events liegt vor dem Beginn.")
|
||||
if self.event_start and self.post_type not in ["E", "F"]:
|
||||
raise ValidationError("Für diesen Post Typ ist kein Event Start zulässig.")
|
||||
|
||||
@property
|
||||
def published(self):
|
||||
return self.status == Status.PUBLIC
|
||||
|
||||
|
||||
class News(Post):
|
||||
objects = NewsManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
verbose_name = "News"
|
||||
verbose_name_plural = "News"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.post_type:
|
||||
self.post_type = "N"
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Event(Post):
|
||||
only_events = EventManager()
|
||||
all_events = AllEventManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
verbose_name = "Event"
|
||||
verbose_name_plural = "Events"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.post_type = "E"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.post_type:
|
||||
self.post_type = "E"
|
||||
if not self.event_end:
|
||||
self.event_end = self.event_start + timedelta(hours=2)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.event_start:
|
||||
raise ValidationError("Das Datum des Events fehlt.")
|
||||
|
||||
|
||||
class FetMeeting(Event):
|
||||
objects = FetMeetingManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
verbose_name = "Fet Sitzung"
|
||||
verbose_name_plural = "Fet Sitzungen"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.post_type = "F"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.title = "Fachschaftssitzung"
|
||||
if not self.slug:
|
||||
self.slug = self.__get_slug()
|
||||
|
||||
if not ep_pad_exists(self.agenda_key) or self.agenda_key in self._possible_empty_key_value:
|
||||
self.create_agenda_key()
|
||||
if self.agenda_key:
|
||||
self.has_agenda = True
|
||||
|
||||
if (
|
||||
not ep_pad_exists(self.protocol_key)
|
||||
or self.protocol_key in self._possible_empty_key_value
|
||||
):
|
||||
self.create_protocol_key()
|
||||
if self.protocol_key:
|
||||
self.has_protocol = True
|
||||
|
||||
if not self.post_type:
|
||||
self.post_type = "F"
|
||||
|
||||
if not self.event_place:
|
||||
self.event_place = "FET"
|
||||
|
||||
# make duration 2 hours if not specified otherwise
|
||||
if not self.event_end:
|
||||
self.event_end = self.event_start + timedelta(hours=2)
|
||||
|
||||
# set FET Meeting always public
|
||||
self.status = Status.PUBLIC
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __get_slug(self) -> str:
|
||||
slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung")
|
||||
|
||||
if Post.objects.filter(slug=slug).exists() and Post.objects.get(slug=slug).id != self.id:
|
||||
raise ValidationError("Es existiert bereits eine Sitzung mit demselben Datum.")
|
||||
|
||||
return slug
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.slug:
|
||||
self.slug = self.__get_slug()
|
||||
|
||||
|
||||
class FileUpload(models.Model):
|
||||
title = models.CharField(verbose_name="Titel", max_length=200)
|
||||
file_field = models.FileField(verbose_name="Dokument", upload_to="uploads/posts/files/")
|
||||
post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from core.models import CustomFlatPage
|
||||
from documents.api import ep_create_new_pad, ep_get_html, ep_get_url, ep_pad_exists, ep_set_html
|
||||
|
||||
from .choices import PostType, Status
|
||||
from .managers import (
|
||||
AllEventManager,
|
||||
ArticleManager,
|
||||
EventManager,
|
||||
FetMeetingManager,
|
||||
NewsManager,
|
||||
PostManager,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_pad_for_post(slug, item="agenda"):
|
||||
logger.info("Pad-Type: %s", item)
|
||||
|
||||
pad_id = slug + "-" + item
|
||||
if not ep_create_new_pad(pad_id):
|
||||
return ""
|
||||
|
||||
# Set template into the newly created pad if it exists.
|
||||
if page := CustomFlatPage.objects.filter(title__iexact=item).first():
|
||||
ep_set_html(pad_id, page.content)
|
||||
logger.info("Template '%s' is set.", page.title)
|
||||
|
||||
return pad_id
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
# legacy id is for the posts from the old website
|
||||
legacy_id = models.IntegerField(null=True, blank=True)
|
||||
|
||||
title = models.CharField(verbose_name="Titel", max_length=200)
|
||||
subtitle = models.CharField(max_length=500, blank=True, default="")
|
||||
tags = TaggableManager(blank=True)
|
||||
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
|
||||
body = models.TextField(blank=True, default="")
|
||||
image = models.ImageField(null=True, blank=True)
|
||||
|
||||
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
public_date = models.DateField(
|
||||
verbose_name="Veröffentlichung",
|
||||
null=True,
|
||||
blank=True,
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
post_type = models.CharField(max_length=1, choices=PostType.choices, editable=True)
|
||||
|
||||
status = models.CharField(
|
||||
verbose_name="Status", max_length=2, choices=Status.choices, default=Status.DRAFT
|
||||
)
|
||||
|
||||
# post is pinned at main page
|
||||
is_pinned = models.BooleanField(verbose_name="ANGEHEFTET", default=False)
|
||||
|
||||
# addional infos for events
|
||||
event_start = models.DateTimeField(verbose_name="Event Start", null=True, blank=True)
|
||||
event_end = models.DateTimeField(verbose_name="Event Ende", null=True, blank=True)
|
||||
event_place = models.CharField(max_length=200, blank=True, default="")
|
||||
|
||||
# protocol for fet meeting
|
||||
has_protocol = models.BooleanField(default=False)
|
||||
has_agenda = models.BooleanField(default=False)
|
||||
protocol_key = models.CharField(max_length=200, blank=True, default="")
|
||||
agenda_key = models.CharField(max_length=200, blank=True, default="")
|
||||
|
||||
# TimeStamps
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# Managers
|
||||
objects = PostManager()
|
||||
articles = ArticleManager()
|
||||
|
||||
def __str__(self):
|
||||
return "Post ({}, {}): {}".format(
|
||||
self.slug,
|
||||
self.public_date.strftime("%d.%m.%Y"),
|
||||
self.title,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# save the post with some defaults
|
||||
if not self.public_date:
|
||||
self.public_date = timezone.now().date()
|
||||
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.public_date) + "-" + slugify(self.title)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("posts:post", kwargs={"slug": self.slug})
|
||||
|
||||
# "#" for backward compatibility
|
||||
_possible_empty_key_value = ["#", "", None]
|
||||
|
||||
@property
|
||||
def agenda_html(self) -> str | None:
|
||||
if self.agenda_key in self._possible_empty_key_value:
|
||||
return None
|
||||
|
||||
return ep_get_html(self.agenda_key)
|
||||
|
||||
@agenda_html.setter
|
||||
def agenda_html(self, value: str) -> str | None:
|
||||
if self.agenda_key in self._possible_empty_key_value:
|
||||
self.create_agenda_key()
|
||||
|
||||
if not value or not self.agenda_key:
|
||||
return None
|
||||
|
||||
ep_set_html(self.agenda_key, value)
|
||||
logger.info("Set agenda etherpad '%s' for post '%s'.", self.agenda_key, self.slug)
|
||||
return value
|
||||
|
||||
@property
|
||||
def protocol_html(self) -> str | None:
|
||||
if self.protocol_key in self._possible_empty_key_value:
|
||||
return None
|
||||
|
||||
return ep_get_html(self.protocol_key)
|
||||
|
||||
@protocol_html.setter
|
||||
def protocol_html(self, value: str) -> str | None:
|
||||
if self.protocol_key in self._possible_empty_key_value:
|
||||
self.create_protocol_key()
|
||||
|
||||
if not value or not self.protocol_key:
|
||||
return None
|
||||
|
||||
ep_set_html(self.protocol_key, value)
|
||||
logger.info("Set protocol etherpad '%s' for post '%s'.", self.protocol_key, self.slug)
|
||||
return value
|
||||
|
||||
_agenda_filename = None
|
||||
_agenda_url = None
|
||||
|
||||
@property
|
||||
def agenda_url(self) -> str | None:
|
||||
if not self.has_agenda:
|
||||
self._agenda_url = None
|
||||
self._agenda_filename = None
|
||||
return self._agenda_url
|
||||
|
||||
if self._agenda_url:
|
||||
return self._agenda_url
|
||||
|
||||
if url := ep_get_url(self.agenda_key):
|
||||
self._agenda_url = url
|
||||
self._agenda_filename = self.slug + "-agenda.pdf"
|
||||
else:
|
||||
self._agenda_url = None
|
||||
self._agenda_filename = None
|
||||
|
||||
return self._agenda_url
|
||||
|
||||
@property
|
||||
def agenda_filename(self) -> str | None:
|
||||
# TODO: fix pdf render
|
||||
# if self._agenda_filename:
|
||||
# return self._agenda_filename
|
||||
|
||||
# if self.has_agenda and self.agenda_url:
|
||||
# return self.slug + "-agenda.pdf"
|
||||
|
||||
return None
|
||||
|
||||
_protocol_filename = None
|
||||
_protocol_url = None
|
||||
|
||||
@property
|
||||
def protocol_url(self) -> str | None:
|
||||
if not self.has_protocol:
|
||||
self._protocol_url = None
|
||||
self._protocol_filename = None
|
||||
return self._protocol_url
|
||||
|
||||
if self._protocol_url:
|
||||
return self._protocol_url
|
||||
|
||||
if url := ep_get_url(self.protocol_key):
|
||||
self._protocol_url = url
|
||||
self._protocol_filename = self.slug + "-protokoll.pdf"
|
||||
else:
|
||||
self._protocol_url = None
|
||||
self._protocol_filename = None
|
||||
|
||||
return self._protocol_url
|
||||
|
||||
@property
|
||||
def protocol_filename(self) -> str | None:
|
||||
# TODO: fix pdf render
|
||||
# if self._protocol_filename:
|
||||
# return self._protocol_filename
|
||||
|
||||
# if self.has_protocol and self.protocol_url:
|
||||
# return self.slug + "-protokoll.pdf"
|
||||
|
||||
return None
|
||||
|
||||
def create_agenda_key(self) -> None:
|
||||
"""
|
||||
Create a Etherpad Id for the Pad associated to this post.
|
||||
Create the pad if it doesn't exist.
|
||||
"""
|
||||
if self.slug:
|
||||
self.agenda_key = create_pad_for_post(self.slug, "agenda")
|
||||
|
||||
def create_protocol_key(self) -> None:
|
||||
"""
|
||||
Create a Etherpad Id for the Pad associated to this post.
|
||||
Create the pad if it doesn't exist.
|
||||
"""
|
||||
if self.slug:
|
||||
self.protocol_key = create_pad_for_post(self.slug, "protocol")
|
||||
|
||||
def get_model_name(self):
|
||||
return self._meta.model_name
|
||||
|
||||
@property
|
||||
def three_tag_names(self):
|
||||
return self.tags.names()[:3]
|
||||
|
||||
@property
|
||||
def tag_names(self):
|
||||
return [t for t in self.tags.names()]
|
||||
|
||||
@property
|
||||
def imageurl(self) -> str:
|
||||
return (
|
||||
self.image.url if self.image else settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
if self.event_end and self.event_end < self.event_start:
|
||||
msg = "Das Ende des Events liegt vor dem Beginn."
|
||||
raise ValidationError(msg, code="invalid_event_end")
|
||||
if self.event_start and self.post_type not in ["E", "F"]:
|
||||
msg = "Für diesen Post Typ ist kein Event Start zulässig."
|
||||
raise ValidationError(msg, code="invalid_event_start")
|
||||
|
||||
@property
|
||||
def published(self):
|
||||
return self.status == Status.PUBLIC
|
||||
|
||||
|
||||
class News(Post):
|
||||
objects = NewsManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
verbose_name = "News"
|
||||
verbose_name_plural = "News"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.post_type:
|
||||
self.post_type = "N"
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Event(Post):
|
||||
only_events = EventManager()
|
||||
all_events = AllEventManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
verbose_name = "Event"
|
||||
verbose_name_plural = "Events"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.post_type = "E"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.post_type:
|
||||
self.post_type = "E"
|
||||
if not self.event_end:
|
||||
self.event_end = self.event_start + timedelta(hours=2)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.event_start:
|
||||
msg = "Das Datum des Events fehlt."
|
||||
raise ValidationError(msg, code="missing_event_start")
|
||||
|
||||
|
||||
class FetMeeting(Event):
|
||||
objects = FetMeetingManager()
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
verbose_name = "Fet Sitzung"
|
||||
verbose_name_plural = "Fet Sitzungen"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.post_type = "F"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.title = "Fachschaftssitzung"
|
||||
if not self.slug:
|
||||
self.slug = self.__get_slug()
|
||||
|
||||
if not ep_pad_exists(self.agenda_key) or self.agenda_key in self._possible_empty_key_value:
|
||||
self.create_agenda_key()
|
||||
if self.agenda_key:
|
||||
self.has_agenda = True
|
||||
|
||||
if (
|
||||
not ep_pad_exists(self.protocol_key)
|
||||
or self.protocol_key in self._possible_empty_key_value
|
||||
):
|
||||
self.create_protocol_key()
|
||||
if self.protocol_key:
|
||||
self.has_protocol = True
|
||||
|
||||
if not self.post_type:
|
||||
self.post_type = "F"
|
||||
|
||||
if not self.event_place:
|
||||
self.event_place = "FET"
|
||||
|
||||
# make duration 2 hours if not specified otherwise
|
||||
if not self.event_end:
|
||||
self.event_end = self.event_start + timedelta(hours=2)
|
||||
|
||||
# set FET Meeting always public
|
||||
self.status = Status.PUBLIC
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __get_slug(self) -> str:
|
||||
slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung")
|
||||
|
||||
if Post.objects.filter(slug=slug).exists() and Post.objects.get(slug=slug).id != self.id:
|
||||
msg = "Es existiert bereits eine Sitzung mit demselben Datum."
|
||||
logger.error(
|
||||
"A fet meeting with same date (slug: %(slug)s) is already existing.",
|
||||
extra={"slug": slug},
|
||||
)
|
||||
raise ValidationError(msg, code="duplicate_fet_meeting")
|
||||
|
||||
return slug
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.slug:
|
||||
self.slug = self.__get_slug()
|
||||
|
||||
|
||||
class FileUpload(models.Model):
|
||||
title = models.CharField(verbose_name="Titel", max_length=200)
|
||||
file_field = models.FileField(verbose_name="Dokument", upload_to="uploads/posts/files/")
|
||||
post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
||||
|
||||
objects = models.Manager()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
from haystack import indexes
|
||||
from html2text import html2text
|
||||
|
||||
from .models import Post
|
||||
|
||||
|
||||
class PostIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True)
|
||||
title = indexes.EdgeNgramField(model_attr="title")
|
||||
body = indexes.EdgeNgramField(model_attr="body", null=True)
|
||||
status = indexes.CharField(model_attr="status")
|
||||
date = indexes.DateField()
|
||||
agenda = indexes.EdgeNgramField()
|
||||
protocol = indexes.EdgeNgramField()
|
||||
|
||||
def get_model(self):
|
||||
return Post
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.date_sorted(public=False)
|
||||
|
||||
def prepare_date(self, obj):
|
||||
if obj.post_type == "N":
|
||||
return obj.public_date
|
||||
elif obj.post_type == "E" or obj.post_type == "F":
|
||||
return obj.event_start.date()
|
||||
|
||||
def prepare_agenda(self, obj):
|
||||
if obj.has_agenda and obj.agenda_html:
|
||||
return html2text(obj.agenda_html)
|
||||
return None
|
||||
|
||||
def prepare_protocol(self, obj):
|
||||
if obj.has_protocol and obj.protocol_html:
|
||||
return html2text(obj.protocol_html)
|
||||
return None
|
||||
from haystack import indexes
|
||||
from html2text import html2text
|
||||
|
||||
from .models import Post
|
||||
|
||||
|
||||
class PostIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
text = indexes.CharField(document=True)
|
||||
title = indexes.EdgeNgramField(model_attr="title")
|
||||
body = indexes.EdgeNgramField(model_attr="body", null=True)
|
||||
status = indexes.CharField(model_attr="status")
|
||||
date = indexes.DateField()
|
||||
agenda = indexes.EdgeNgramField()
|
||||
protocol = indexes.EdgeNgramField()
|
||||
|
||||
def get_model(self):
|
||||
return Post
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.date_sorted(public=False)
|
||||
|
||||
def prepare_date(self, obj):
|
||||
if obj.post_type == "E" or obj.post_type == "F":
|
||||
return obj.event_start.date()
|
||||
|
||||
return obj.public_date # obj.post_type == "N"
|
||||
|
||||
def prepare_agenda(self, obj):
|
||||
if obj.has_agenda and obj.agenda_html:
|
||||
return html2text(obj.agenda_html)
|
||||
return None
|
||||
|
||||
def prepare_protocol(self, obj):
|
||||
if obj.has_protocol and obj.protocol_html:
|
||||
return html2text(obj.protocol_html)
|
||||
return None
|
||||
|
||||
@@ -117,15 +117,17 @@ class PostDetailView(DetailView):
|
||||
if not obj.published:
|
||||
related_posts.remove(obj)
|
||||
|
||||
context = {
|
||||
"post": self.object,
|
||||
"files": files,
|
||||
"author": author,
|
||||
"author_image": author_image,
|
||||
"next": self.post_next(),
|
||||
"prev": self.post_prev(),
|
||||
"related_posts": related_posts[:4],
|
||||
}
|
||||
context.update(
|
||||
{
|
||||
"post": self.object,
|
||||
"files": files,
|
||||
"author": author,
|
||||
"author_image": author_image,
|
||||
"next": self.post_next(),
|
||||
"prev": self.post_prev(),
|
||||
"related_posts": related_posts[:4],
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
@@ -309,7 +311,8 @@ def show_pdf(request, html, filename):
|
||||
html = html[:idx] + rendered + html[idx:]
|
||||
|
||||
if not (pdf := render_to_pdf(html)):
|
||||
raise Http404("can't create pdf file.")
|
||||
msg = "PDF konnte nicht erstellt werden."
|
||||
raise Http404(msg)
|
||||
|
||||
response = HttpResponse(pdf, content_type="application/pdf")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.db import models
|
||||
from django.forms import ValidationError
|
||||
|
||||
from .mails import send_mail_approved, send_mail_rejected, send_mail_returned
|
||||
from .managers import RentalItemsManager
|
||||
@@ -116,7 +116,8 @@ class Rental(models.Model):
|
||||
self.date_end = self.date_start
|
||||
|
||||
if self.date_start > self.date_end:
|
||||
raise ValidationError("Das Abholdatum muss vor dem Rückgabedatum liegen.")
|
||||
msg = "Das Abholdatum muss vor dem Rückgabedatum liegen."
|
||||
raise ValidationError(msg, code="invalid_date_range")
|
||||
|
||||
def calc_total_deposit(self) -> int:
|
||||
total_deposit = 0
|
||||
|
||||
Reference in New Issue
Block a user