ruff check; update ValidationError params

This commit is contained in:
2026-01-06 17:45:06 +01:00
parent d2c4a6ef6a
commit 6eeb7769a4
16 changed files with 1282 additions and 1245 deletions

View File

@@ -1,39 +1,39 @@
# util functions for all apps # util functions for all apps
import uuid import uuid
from django.contrib.admin.utils import construct_change_message from django.contrib.admin.utils import construct_change_message
def add_log_action(request, form, app_label, model, add=True): def add_log_action(request, form, app_label, model, add=True):
from django.contrib.admin.models import ADDITION, CHANGE, LogEntry from django.contrib.admin.models import ADDITION, CHANGE, LogEntry
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
obj = form.save() obj = form.save()
content_type = ContentType.objects.get(app_label=app_label, model=model) content_type = ContentType.objects.get(app_label=app_label, model=model)
change_message = construct_change_message(form, None, add) change_message = construct_change_message(form, None, add)
action_flag = ADDITION if add else CHANGE action_flag = ADDITION if add else CHANGE
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id=request.user.pk, user_id=request.user.pk,
content_type_id=content_type.pk, content_type_id=content_type.pk,
object_id=obj.pk, object_id=obj.pk,
object_repr=str(obj), object_repr=str(obj),
action_flag=action_flag, action_flag=action_flag,
change_message=change_message, change_message=change_message,
) )
def create_perms(sender, **kwargs): def create_perms(sender, **kwargs):
from django.contrib.auth.models import Group, Permission from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
group, created = Group.objects.get_or_create(name=sender.label) group, _ = Group.objects.get_or_create(name=sender.label)
content_types = ContentType.objects.filter(app_label=sender.label) content_types = ContentType.objects.filter(app_label=sender.label)
for content_type in content_types: for content_type in content_types:
permissions = Permission.objects.filter(content_type=content_type) permissions = Permission.objects.filter(content_type=content_type)
[group.permissions.add(permission) for permission in permissions] [group.permissions.add(permission) for permission in permissions]
def create_random_id(): def create_random_id():
return str(uuid.uuid4())[:8] return str(uuid.uuid4())[:8]

View File

@@ -1,9 +1,8 @@
import datetime
import decimal import decimal
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django import forms 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.db.models import Count, Q
from django.forms import DateInput from django.forms import DateInput
from django.utils import timezone from django.utils import timezone
@@ -451,8 +450,8 @@ class ResolutionAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # to get the self.fields set super().__init__(*args, **kwargs) # to get the self.fields set
budget = decimal.Decimal(0.0) budget = decimal.Decimal("0.0")
total = decimal.Decimal(0.0) total = decimal.Decimal("0.0")
if resolution := kwargs.get("instance"): if resolution := kwargs.get("instance"):
for elem in Bill.objects.filter(resolution=resolution): for elem in Bill.objects.filter(resolution=resolution):
total += elem.amount total += elem.amount

View File

@@ -1,6 +1,8 @@
import logging
from pathlib import Path 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.db import models
from django.urls import reverse from django.urls import reverse
@@ -8,6 +10,8 @@ from members.models import Member
from .validators import validate_bill_file_extension from .validators import validate_bill_file_extension
logger = logging.getLogger(__name__)
class BankData(models.Model): class BankData(models.Model):
# members can be deleted but never their bank datas # 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(): if not Resolution.objects.filter(id=_id).exists():
break break
else: 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( 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 self.id = _id

View File

@@ -27,11 +27,11 @@ def get_image_list(folder_name: str) -> list:
Path(thumb_path).mkdir(exist_ok=True) Path(thumb_path).mkdir(exist_ok=True)
for _file in os.listdir(image_path): for _file in image_path.iterdir():
if Path(_file).suffix.lower()[1:] not in get_available_image_extensions(): if _file.suffix.lower()[1:] not in get_available_image_extensions():
continue 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(): if not Path(thumb_file_path).exists():
with Image.open(Path(image_path) / _file, "r") as im: with Image.open(Path(image_path) / _file, "r") as im:
if im._getexif() is not None: if im._getexif() is not None:

View File

@@ -1,65 +1,67 @@
import logging import logging
from collections import deque from collections import deque
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404 from django.http import Http404
from django.shortcuts import render from django.shortcuts import render
from django.utils.text import slugify from django.utils.text import slugify
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from .models import Album from .models import Album
from .utils import get_folder_list from .utils import get_folder_list
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def index(request): def index(request):
if request.user.is_authenticated: if request.user.is_authenticated:
albums = deque(Album.objects.all()) albums = deque(Album.objects.all())
# Get albums that are in the server but not in the db. # Get albums that are in the server but not in the db.
for folder in get_folder_list(): for folder in get_folder_list():
if not Album.objects.filter(folder_name=folder): if not Album.objects.filter(folder_name=folder):
albums.append( albums.append(
Album(title=folder, slug=slugify(folder), folder_name=folder, event_date=None) Album(title=folder, slug=slugify(folder), folder_name=folder, event_date=None)
) )
else: else:
# Show only PUBLIC albums. # Show only PUBLIC albums.
albums = Album.objects.public() albums = Album.objects.public()
context = {"albums": albums} context = {"albums": albums}
return render(request, "gallery/index.html", context) return render(request, "gallery/index.html", context)
class AlbumDetailView(DetailView): class AlbumDetailView(DetailView):
model = Album model = Album
template_name = "gallery/album.html" template_name = "gallery/album.html"
def get_queryset(self): def get_queryset(self):
return ( return (
Album.objects.public() Album.objects.public()
if not self.request.user.is_authenticated if not self.request.user.is_authenticated
else Album.objects.all() else Album.objects.all()
) )
class DraftAlbumDetailView(LoginRequiredMixin, DetailView): class DraftAlbumDetailView(LoginRequiredMixin, DetailView):
model = Album model = Album
template_name = "gallery/album.html" template_name = "gallery/album.html"
def get_object(self, queryset=None): def get_object(self, queryset=None):
slug = self.kwargs.get(self.slug_url_kwarg) slug = self.kwargs.get(self.slug_url_kwarg)
if not (album := Album.objects.filter(slug=slug).first()): if not (album := Album.objects.filter(slug=slug).first()):
for folder in get_folder_list(): for folder in get_folder_list():
if slug == slugify(folder): if slug == slugify(folder):
album = Album( album = Album(
title=folder, slug=slugify(folder), folder_name=folder, event_date=None title=folder, slug=slugify(folder), folder_name=folder, event_date=None
) )
break break
else: else:
raise Http404("Album slug not found.") msg = f"Album mit dem Slug '{slug}' nicht gefunden."
logger.error("Album with slug '%s' not found.", slug)
return album raise Http404(msg)
return album

View File

@@ -1,205 +1,219 @@
import logging import logging
from datetime import date from datetime import date
from pathlib import Path from pathlib import Path
from django.core.validators import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models.constraints import UniqueConstraint from django.db.models.constraints import UniqueConstraint
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from documents.api import ep_create_new_pad, ep_get_html, ep_get_url from documents.api import ep_create_new_pad, ep_get_html, ep_get_url
from fet2020.utils import create_random_id from fet2020.utils import create_random_id
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TopicGroup(models.Model): class TopicGroup(models.Model):
title = models.CharField(verbose_name="Titel", max_length=128) title = models.CharField(verbose_name="Titel", max_length=128)
shortterm = models.CharField(max_length=128, unique=True, blank=True) shortterm = models.CharField(max_length=128, unique=True, blank=True)
slug = models.SlugField(unique=True) slug = models.SlugField(unique=True)
short_description = models.TextField(blank=True, default="") short_description = models.TextField(blank=True, default="")
order = models.PositiveSmallIntegerField( order = models.PositiveSmallIntegerField(
verbose_name="Reihenfolge", verbose_name="Reihenfolge",
unique=True, unique=True,
null=True, null=True,
blank=True, blank=True,
) )
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
verbose_name = "Themenbereich" verbose_name = "Themenbereich"
verbose_name_plural = "Themenbereiche" verbose_name_plural = "Themenbereiche"
def __str__(self): def __str__(self):
return self.title return self.title
def get_absolute_url(self): def get_absolute_url(self):
return reverse("intern:index") + "#" + self.slug return reverse("intern:index") + "#" + self.slug
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
if not self.shortterm: if not self.shortterm:
self.shortterm = self.title self.shortterm = self.title
self.slug = slugify(self.shortterm) self.slug = slugify(self.shortterm)
class Topic(models.Model): class Topic(models.Model):
title = models.CharField(max_length=128, verbose_name="Titel") title = models.CharField(max_length=128, verbose_name="Titel")
slug = models.SlugField() slug = models.SlugField()
archive = models.BooleanField(default=False, verbose_name="Archiv") archive = models.BooleanField(default=False, verbose_name="Archiv")
description = models.TextField(blank=True, default="") description = models.TextField(blank=True, default="")
topic_group = models.ForeignKey( topic_group = models.ForeignKey(
TopicGroup, TopicGroup,
on_delete=models.CASCADE, on_delete=models.CASCADE,
verbose_name="Themenbereich", verbose_name="Themenbereich",
) )
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
verbose_name = "Thema" verbose_name = "Thema"
verbose_name_plural = "Themen" verbose_name_plural = "Themen"
constraints = [ constraints = [
UniqueConstraint(fields=["slug", "topic_group"], name="unique_intern_slug_topic_group"), UniqueConstraint(fields=["slug", "topic_group"], name="unique_intern_slug_topic_group"),
UniqueConstraint( UniqueConstraint(
fields=["title", "topic_group"], fields=["title", "topic_group"],
name="unique_intern_title_topic_group", name="unique_intern_title_topic_group",
), ),
] ]
def __str__(self): def __str__(self):
return self.title return self.title
def get_absolute_url(self): def get_absolute_url(self):
kwargs = { kwargs = {
"topic_group_slug": self.topic_group.slug, "topic_group_slug": self.topic_group.slug,
"slug": self.slug, "slug": self.slug,
} }
return reverse("intern:topic", kwargs=kwargs) return reverse("intern:topic", kwargs=kwargs)
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
self.slug = slugify(self.title) self.slug = slugify(self.title)
class Attachment(models.Model): class Attachment(models.Model):
title = models.CharField(max_length=128, verbose_name="Titel") title = models.CharField(max_length=128, verbose_name="Titel")
slug = models.SlugField() slug = models.SlugField()
description = models.TextField(blank=True, default="") description = models.TextField(blank=True, default="")
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, verbose_name="Thema") topic = models.ForeignKey(Topic, on_delete=models.CASCADE, verbose_name="Thema")
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
verbose_name = "Anhang Ordner" verbose_name = "Anhang Ordner"
verbose_name_plural = "Anhang Ordner" verbose_name_plural = "Anhang Ordner"
constraints = [ constraints = [
UniqueConstraint(fields=["slug", "topic"], name="unique_intern_slug_topic"), UniqueConstraint(fields=["slug", "topic"], name="unique_intern_slug_topic"),
UniqueConstraint(fields=["title", "topic"], name="unique_intern_title_topic"), UniqueConstraint(fields=["title", "topic"], name="unique_intern_title_topic"),
] ]
def __str__(self): def __str__(self):
return self.topic.title + " / " + self.title return self.topic.title + " / " + self.title
def get_absolute_url(self): def get_absolute_url(self):
kwargs = { kwargs = {
"topic_group_slug": self.topic.topic_group.slug, "topic_group_slug": self.topic.topic_group.slug,
"topic_slug": self.topic.slug, "topic_slug": self.topic.slug,
"slug": self.slug, "slug": self.slug,
} }
return reverse("intern:attachment", kwargs=kwargs) return reverse("intern:attachment", kwargs=kwargs)
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
self.slug = slugify(self.title) self.slug = slugify(self.title)
class Etherpad(models.Model): class Etherpad(models.Model):
title = models.CharField(max_length=128, verbose_name="Titel") title = models.CharField(max_length=128, verbose_name="Titel")
slug_id = models.CharField(default=create_random_id, editable=False, max_length=8, unique=True) slug_id = models.CharField(default=create_random_id, editable=False, max_length=8, unique=True)
etherpad_key = models.CharField(blank=True, max_length=50) etherpad_key = models.CharField(blank=True, max_length=50)
date = models.DateField(default=date.today, verbose_name="Datum") date = models.DateField(default=date.today, verbose_name="Datum")
attachment = models.ForeignKey( attachment = models.ForeignKey(
Attachment, Attachment,
on_delete=models.CASCADE, on_delete=models.CASCADE,
verbose_name="Anhang Ordner", verbose_name="Anhang Ordner",
) )
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
verbose_name = "Etherpad" verbose_name = "Etherpad"
verbose_name_plural = "Etherpads" verbose_name_plural = "Etherpads"
constraints = [ constraints = [
UniqueConstraint(fields=["title", "date", "attachment"], name="unique_intern_etherpad"), UniqueConstraint(fields=["title", "date", "attachment"], name="unique_intern_etherpad"),
] ]
def __str__(self): def __str__(self):
return self.title return self.title
def get_absolute_url(self): def get_absolute_url(self):
return ep_get_url(self.etherpad_key) return ep_get_url(self.etherpad_key)
def clean(self): def clean(self):
pad_name = slugify(str(self.slug_id) + "-" + self.title[:40]) pad_name = slugify(str(self.slug_id) + "-" + self.title[:40])
if len(pad_name) > 50: if len(pad_name) > 50:
raise ValidationError( msg = (
( "Name ist zum Erstellen des Etherpads zu lange - max. 50 Zeichen. (Länge: "
"Name zum Erstellen des Etherpads ist zu lange - max. 50 Zeichen. (" "%(length)s, Name: %(pad_name)s)"
"Länge: %(length)s, Name: %(pad_name)s)" )
), logger.error(
params={"length": len(pad_name), "pad_name": pad_name}, (
) "Name '%(pad_name)s' is too long for creating a new etherpad - max. 50 "
"characters. (Length: %(length)s)",
if self.etherpad_key == "": ),
if ep_create_new_pad(pad_name): extra={"length": len(pad_name), "pad_name": pad_name},
self.etherpad_key = pad_name )
else: raise ValidationError(
raise ValidationError( msg,
f"Etherpad '{pad_name}' konnte nicht erstellt werden. This should never happen!" code="pad_name_too_long",
) params={"length": len(pad_name), "pad_name": pad_name},
)
@property
def etherpad_html(self): if self.etherpad_key == "":
return ep_get_html(self.etherpad_key) if ep_create_new_pad(pad_name):
self.etherpad_key = pad_name
def get_model_name(self): else:
return self._meta.model_name msg = "Etherpad '%(pad_name)s' konnte nicht erstellt werden."
logger.error(
"Etherpad '%(pad_name)s' could not be created. This should never happen!",
class FileUpload(models.Model): extra={"pad_name": pad_name},
title = models.CharField(blank=True, max_length=128, verbose_name="Titel") )
file_field = models.FileField(upload_to="uploads/intern/files/", verbose_name="Dokument") raise ValidationError(
date = models.DateField(default=date.today, verbose_name="Datum") msg, code="pad_creation_failed", params={"pad_name": pad_name}
)
attachment = models.ForeignKey(
Attachment, @property
on_delete=models.CASCADE, def etherpad_html(self):
verbose_name="Anhang Ordner", return ep_get_html(self.etherpad_key)
)
def get_model_name(self):
objects = models.Manager() return self._meta.model_name
class Meta:
verbose_name = "Datei" class FileUpload(models.Model):
verbose_name_plural = "Dateien" title = models.CharField(blank=True, max_length=128, verbose_name="Titel")
file_field = models.FileField(upload_to="uploads/intern/files/", verbose_name="Dokument")
def __str__(self): date = models.DateField(default=date.today, verbose_name="Datum")
return self.title
attachment = models.ForeignKey(
def clean(self): Attachment,
if not self.title: on_delete=models.CASCADE,
self.title = Path(self.file_field.name).stem 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

View File

@@ -1,230 +1,232 @@
from django.contrib import admin from django.contrib import admin
from .forms import ( from .forms import (
ActiveMemberForm, ActiveMemberForm,
InactiveMemberForm, InactiveMemberForm,
JobForm, JobForm,
JobGroupForm, JobGroupForm,
JobInlineForm, JobInlineForm,
MemberForm, MemberForm,
) )
from .models import Job, JobGroup, JobMember, Member from .models import Job, JobGroup, JobMember, Member
class MemberRoleFilter(admin.SimpleListFilter): class MemberRoleFilter(admin.SimpleListFilter):
title = "Rolle" title = "Rolle"
parameter_name = "role" parameter_name = "role"
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return ( return (
("A", "Aktiv"), ("A", "Aktiv"),
("P", "Pension"), ("P", "Pension"),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() in Member.MemberRole: if self.value() in Member.MemberRole:
return queryset.filter(role=self.value()) return queryset.filter(role=self.value())
return queryset
class JobMemberInline(admin.TabularInline):
model = JobMember
extra = 0 class JobMemberInline(admin.TabularInline):
model = JobMember
extra = 0
class JobOverviewInline(JobMemberInline):
verbose_name = "Tätigkeit"
verbose_name_plural = "Tätigkeitsübersicht" class JobOverviewInline(JobMemberInline):
verbose_name = "Tätigkeit"
def get_queryset(self, request): verbose_name_plural = "Tätigkeitsübersicht"
return JobMember.members.get_all_jobs()
def get_queryset(self, request):
return JobMember.members.get_all_jobs()
class ActiveMemberInline(JobMemberInline):
form = ActiveMemberForm
verbose_name = "Mitglied" class ActiveMemberInline(JobMemberInline):
verbose_name_plural = "Aktive Mitglieder Liste" form = ActiveMemberForm
verbose_name = "Mitglied"
def get_queryset(self, request): verbose_name_plural = "Aktive Mitglieder Liste"
return JobMember.active_member.get_queryset()
def get_queryset(self, request):
return JobMember.active_member.get_queryset()
class InactiveMemberInline(JobMemberInline):
form = InactiveMemberForm
verbose_name = "Mitglied" class InactiveMemberInline(JobMemberInline):
verbose_name_plural = "Inaktive Mitglieder Liste" form = InactiveMemberForm
verbose_name = "Mitglied"
def get_queryset(self, request): verbose_name_plural = "Inaktive Mitglieder Liste"
return JobMember.inactive_member.get_queryset()
def get_queryset(self, request):
return JobMember.inactive_member.get_queryset()
class JobInline(admin.TabularInline):
form = JobInlineForm
model = Job class JobInline(admin.TabularInline):
extra = 0 form = JobInlineForm
show_change_link = True model = Job
extra = 0
show_change_link = True
@admin.register(Member)
class MemberAdmin(admin.ModelAdmin):
form = MemberForm @admin.register(Member)
model = Member class MemberAdmin(admin.ModelAdmin):
form = MemberForm
fieldsets = ( model = Member
(
None, fieldsets = (
{ (
"fields": ( None,
( {
"firstname", "fields": (
"surname", (
), "firstname",
"nickname", "surname",
"mailaccount", ),
"role", "nickname",
"image", "mailaccount",
"description", "role",
), "image",
}, "description",
), ),
) },
inlines = (JobOverviewInline,) ),
)
list_display = ["nickname", "firstname", "surname", "mailaccount", "role"] inlines = (JobOverviewInline,)
list_filter = [MemberRoleFilter]
ordering = ["firstname"] list_display = ["nickname", "firstname", "surname", "mailaccount", "role"]
search_fields = ["firstname", "surname", "nickname", "mailaccount"] list_filter = [MemberRoleFilter]
ordering = ["firstname"]
def add_view(self, request, form_url="", extra_context=None): search_fields = ["firstname", "surname", "nickname", "mailaccount"]
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." def add_view(self, request, form_url="", extra_context=None):
return super().add_view( extra_context = extra_context or {}
request, extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
form_url, return super().add_view(
extra_context=extra_context, 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." def change_view(self, request, object_id, form_url="", extra_context=None):
return super().change_view( extra_context = extra_context or {}
request, extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
object_id, return super().change_view(
form_url, request,
extra_context=extra_context, 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) 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 @admin.register(Job)
model = Job class JobAdmin(admin.ModelAdmin):
form = JobForm
list_display = ["name", "job_group"] model = Job
ordering = ["name"]
search_fields = ["name"] list_display = ["name", "job_group"]
ordering = ["name"]
fieldsets = ( search_fields = ["name"]
(
None, fieldsets = (
{ (
"fields": ( None,
"name", {
"job_group", "fields": (
), "name",
}, "job_group",
), ),
( },
"Permalink", ),
{ (
"fields": ( "Permalink",
"shortterm", {
"slug", "fields": (
), "shortterm",
}, "slug",
), ),
) },
inlines = (ActiveMemberInline, InactiveMemberInline) ),
readonly_fields = ["slug"] )
inlines = (ActiveMemberInline, InactiveMemberInline)
def add_view(self, request, form_url="", extra_context=None): readonly_fields = ["slug"]
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." def add_view(self, request, form_url="", extra_context=None):
return super().add_view( extra_context = extra_context or {}
request, extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
form_url, return super().add_view(
extra_context=extra_context, 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." def change_view(self, request, object_id, form_url="", extra_context=None):
return super().change_view( extra_context = extra_context or {}
request, extra_context["help_text"] = "Fette Schriften sind Pflichfelder."
object_id, return super().change_view(
form_url, request,
extra_context=extra_context, 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) 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 @admin.register(JobGroup)
model = JobGroup class JobGroupAdmin(admin.ModelAdmin):
form = JobGroupForm
list_display = ["name"] model = JobGroup
ordering = ["name"]
search_fields = ["name"] list_display = ["name"]
ordering = ["name"]
fieldsets = ( search_fields = ["name"]
(
None, fieldsets = (
{ (
"fields": ( None,
"name", {
"description", "fields": (
), "name",
}, "description",
), ),
( },
"Permalink", ),
{ (
"fields": ( "Permalink",
"shortterm", {
"slug", "fields": (
), "shortterm",
}, "slug",
), ),
) },
inlines = (JobInline,) ),
readonly_fields = ["slug"] )
inlines = (JobInline,)
def add_view(self, request, form_url="", extra_context=None): readonly_fields = ["slug"]
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." def add_view(self, request, form_url="", extra_context=None):
return super().add_view( extra_context = extra_context or {}
request, extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
form_url, return super().add_view(
extra_context=extra_context, 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." def change_view(self, request, object_id, form_url="", extra_context=None):
return super().change_view( extra_context = extra_context or {}
request, extra_context["help_text"] = "Fette Schriften sind Pflichfelder."
object_id, return super().change_view(
form_url, request,
extra_context=extra_context, 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) def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)

View File

@@ -1,210 +1,207 @@
import logging import logging
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import ValidationError, validate_email from django.core.validators import validate_email
from django.db import models from django.db import models
from django.db.models import F from django.db.models import F
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from .managers import ( from .managers import (
ActiveJobMemberManager, ActiveJobMemberManager,
InactiveJobMemberManager, InactiveJobMemberManager,
JobMemberManager, JobMemberManager,
MemberManager, MemberManager,
) )
from .validators import ( from .validators import (
validate_domainonly_email, validate_domainonly_email,
validate_file_size, validate_file_size,
validate_image_dimension, validate_image_dimension,
) )
fet_logo_url = settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png" fet_logo_url = settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Member(models.Model): class Member(models.Model):
firstname = models.CharField("Vorname", max_length=128) firstname = models.CharField("Vorname", max_length=128)
surname = models.CharField("Nachname", max_length=128) surname = models.CharField("Nachname", max_length=128)
nickname = models.CharField("Spitzname", max_length=128) nickname = models.CharField("Spitzname", max_length=128)
# LDAP Username # LDAP Username
username = models.CharField(max_length=128, blank=True) username = models.CharField(max_length=128, blank=True)
# fet mail account # fet mail account
mailaccount = models.CharField( mailaccount = models.CharField(
"Mailadresse", "Mailadresse",
unique=True, unique=True,
max_length=128, max_length=128,
validators=[validate_email, validate_domainonly_email], validators=[validate_email, validate_domainonly_email],
error_messages={ error_messages={
"unique": "Diese Mailadresse existiert schon.", "unique": "Diese Mailadresse existiert schon.",
}, },
) )
class MemberRole(models.TextChoices): class MemberRole(models.TextChoices):
ACTIVE = "A", "Active" ACTIVE = "A", "Active"
PENSION = "P", "Pension" PENSION = "P", "Pension"
role = models.CharField( role = models.CharField(
"Rolle", "Rolle",
max_length=1, max_length=1,
choices=MemberRole.choices, choices=MemberRole.choices,
default=MemberRole.ACTIVE, default=MemberRole.ACTIVE,
) )
description = models.TextField(blank=True, default="") description = models.TextField(blank=True, default="")
image = ThumbnailerImageField( image = ThumbnailerImageField(
upload_to="uploads/members/image/", upload_to="uploads/members/image/",
validators=[validate_file_size, validate_image_dimension], validators=[validate_file_size, validate_image_dimension],
) )
date_modified = models.DateTimeField(auto_now=True) date_modified = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True) date_created = models.DateTimeField(auto_now_add=True)
# Managers # Managers
objects = models.Manager() objects = models.Manager()
all_members = MemberManager() all_members = MemberManager()
class Meta: class Meta:
verbose_name = "Mitglied" verbose_name = "Mitglied"
verbose_name_plural = "Mitglieder" verbose_name_plural = "Mitglieder"
def __str__(self): def __str__(self):
return self.firstname + " " + self.surname return self.firstname + " " + self.surname
# need to have 'View on site' link in admin app # need to have 'View on site' link in admin app
def get_absolute_url(self): def get_absolute_url(self):
return reverse("members:member", kwargs={"pk": self.pk}) return reverse("members:member", kwargs={"pk": self.pk})
def clean(self): def clean(self):
if not self.image: if self.username:
raise ValidationError("Es fehlt das Profilbild.") try:
user = User.objects.get(username=self.username.lower())
if self.username: except User.DoesNotExist as e:
try: logger.info("Username not found. Error: %s", e)
user = User.objects.get(username=self.username.lower()) else:
except User.DoesNotExist as e: user.first_name = self.firstname
logger.info("Username not found. Error: %s", e) user.save()
else:
user.first_name = self.firstname def get_model_name(self):
user.save() return self._meta.model_name
def get_model_name(self): @property
return self._meta.model_name def image_url(self):
return self.image.url if self.image else fet_logo_url
@property
def image_url(self): @property
return self.image.url if self.image else fet_logo_url def avatar_url(self):
return self.image["avatar"].url if self.image else fet_logo_url
@property
def avatar_url(self): @property
return self.image["avatar"].url if self.image else fet_logo_url def portrait_url(self):
return self.image["portrait"].url if self.image else fet_logo_url
@property
def portrait_url(self): @property
return self.image["portrait"].url if self.image else fet_logo_url def thumb_url(self):
return self.image["thumb"].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)
class JobGroup(models.Model): shortterm = models.CharField(max_length=128, unique=True, blank=True)
name = models.CharField(max_length=128) slug = models.SlugField(unique=True, null=True, blank=True)
shortterm = models.CharField(max_length=128, unique=True, blank=True) description = models.TextField(blank=True, default="")
slug = models.SlugField(unique=True, null=True, blank=True)
# Managers
description = models.TextField(blank=True, default="") objects = models.Manager()
# Managers class Meta:
objects = models.Manager() verbose_name = "Tätigkeitsbereich"
verbose_name_plural = "Tätigkeitsbereiche"
class Meta:
verbose_name = "Tätigkeitsbereich" def __str__(self):
verbose_name_plural = "Tätigkeitsbereiche" return self.name
def __str__(self): # need to have 'View on site' link in admin app
return self.name def get_absolute_url(self):
return reverse("members:jobs", kwargs={"slug": self.slug})
# need to have 'View on site' link in admin app
def get_absolute_url(self): def clean(self):
return reverse("members:jobs", kwargs={"slug": self.slug}) if not self.shortterm:
self.shortterm = slugify(self.name)
def clean(self): self.slug = slugify(self.shortterm)
if not self.shortterm:
self.shortterm = slugify(self.name)
self.slug = slugify(self.shortterm) class Job(models.Model):
name = models.CharField(max_length=128)
class Job(models.Model): shortterm = models.CharField(max_length=128, unique=True, blank=True)
name = models.CharField(max_length=128) slug = models.SlugField(unique=True, null=True, blank=True)
shortterm = models.CharField(max_length=128, unique=True, blank=True) order = models.PositiveSmallIntegerField(null=True, blank=True)
slug = models.SlugField(unique=True, null=True, blank=True)
job_group = models.ForeignKey(
order = models.PositiveSmallIntegerField(null=True, blank=True) JobGroup,
on_delete=models.CASCADE,
job_group = models.ForeignKey( verbose_name="Job Gruppe",
JobGroup, )
on_delete=models.CASCADE,
verbose_name="Job Gruppe", # Managers
) objects = models.Manager()
# Managers class Meta:
objects = models.Manager() ordering = (F("order").asc(nulls_last=True), "name")
class Meta: verbose_name = "Tätigkeit"
ordering = (F("order").asc(nulls_last=True), "name") verbose_name_plural = "Tätigkeiten"
verbose_name = "Tätigkeit" def __str__(self):
verbose_name_plural = "Tätigkeiten" return self.name
def __str__(self): def get_absolute_url(self):
return self.name return reverse("members:jobs", kwargs={"slug": self.job_group.slug}) + "#" + self.slug
def get_absolute_url(self): def clean(self):
return reverse("members:jobs", kwargs={"slug": self.job_group.slug}) + "#" + self.slug if not self.shortterm:
self.shortterm = slugify(self.name)
def clean(self): self.slug = slugify(self.shortterm)
if not self.shortterm:
self.shortterm = slugify(self.name)
self.slug = slugify(self.shortterm) class JobMember(models.Model):
member = models.ForeignKey(
Member,
class JobMember(models.Model): on_delete=models.CASCADE,
member = models.ForeignKey( verbose_name="Mitglied",
Member, )
on_delete=models.CASCADE, job = models.ForeignKey(
verbose_name="Mitglied", Job,
) on_delete=models.CASCADE,
job = models.ForeignKey( verbose_name="Tätigkeit",
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)
job_start = models.DateField("Job Start") class JobRole(models.TextChoices):
job_end = models.DateField("Job Ende", null=True, blank=True) PRESIDENT = ("10", "Vorsitz")
VICE_PRESIDENT = ("20", "Stv. Vorsitz")
class JobRole(models.TextChoices): SECOND_VICE_PRESIDENT = ("30", "2. Stv. Vorsitz")
PRESIDENT = ("10", "Vorsitz") PERSON_RESPONSIBLE = ("40", "Verantwortliche_r")
VICE_PRESIDENT = ("20", "Stv. Vorsitz") MEMBER = ("50", "Mitglied")
SECOND_VICE_PRESIDENT = ("30", "2. Stv. Vorsitz") SUBSTITUTE_MEMBER = ("60", "Ersatzmitglied")
PERSON_RESPONSIBLE = ("40", "Verantwortliche_r")
MEMBER = ("50", "Mitglied") job_role = models.CharField(max_length=2, choices=JobRole.choices, default=JobRole.MEMBER)
SUBSTITUTE_MEMBER = ("60", "Ersatzmitglied")
objects = models.Manager()
job_role = models.CharField(max_length=2, choices=JobRole.choices, default=JobRole.MEMBER) members = JobMemberManager()
active_member = ActiveJobMemberManager()
objects = models.Manager() inactive_member = InactiveJobMemberManager()
members = JobMemberManager()
active_member = ActiveJobMemberManager() def __str__(self):
inactive_member = InactiveJobMemberManager() return self.job.name
def __str__(self):
return self.job.name

View File

@@ -1,33 +1,31 @@
from collections import deque from collections import deque
from django import template from django import template
from django.db.models import F from django.db.models import F
from members.models import JobGroup, JobMember from members.models import JobGroup, JobMember
register = template.Library() register = template.Library()
@register.inclusion_tag("members/partials/jobs_sidebar.html") @register.inclusion_tag("members/partials/jobs_sidebar.html")
def get_jobs_sidebar(slug): def get_jobs_sidebar(slug):
job_groups = deque(JobGroup.objects.all()) job_groups = deque(JobGroup.objects.all())
# remove job group if there is no longer an active member # remove job group if there is no longer an active member
for elem in job_groups.copy(): for elem in job_groups.copy():
job_members = JobMember.active_member.get_all(slug=elem.slug) job_members = JobMember.active_member.get_all(slug=elem.slug)
if not job_members: if not job_members:
job_groups.remove(elem) job_groups.remove(elem)
job_members = JobMember.active_member.get_all(slug=slug).order_by( job_members = JobMember.active_member.get_all(slug=slug).order_by(
F("job__order").asc(nulls_last=True), F("job__order").asc(nulls_last=True),
"job__name", "job__name",
) )
active_job_group = JobGroup.objects.filter(slug=slug).first() active_job_group = JobGroup.objects.filter(slug=slug).first()
context = { return {
"job_groups": job_groups, "job_groups": job_groups,
"job_members": job_members, "job_members": job_members,
"active_job_group": active_job_group, "active_job_group": active_job_group,
} }
return context

View File

@@ -1,22 +1,26 @@
from django.core.validators import ValidationError from django.core.exceptions import ValidationError
def validate_domainonly_email(value): def validate_domainonly_email(value):
if "fet.at" not in value: if "fet.at" not in value:
raise ValidationError("In der Mailadresse fehlt die richtige Domäne.") 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: def validate_file_size(value):
raise ValidationError("Die maximale Dateigröße ist 10MB.") 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:
raise ValidationError( def validate_image_dimension(value):
"Das Bild ist zu klein. (Höhe: %(height)s, Breite: %(width)s)", if value.height < 150 or value.width < 150:
params={ msg = "Das Bild ist zu klein. (Höhe: %(height)s, Breite: %(width)s)"
"height": value.height, raise ValidationError(
"width": value.width, msg,
}, code="image_too_small",
) params={
"height": value.height,
"width": value.width,
},
)

View File

@@ -23,10 +23,7 @@ def create_token(username, masterpassword):
@authenticated_user @authenticated_user
def index(request): def index(request):
context = { context = {"mctoken": "", "valid_master_pwd": True}
"mctoken": "",
"valid_master_pwd": True
}
masterpassword = settings.MC_MASTERPASSWORD masterpassword = settings.MC_MASTERPASSWORD

View File

@@ -179,8 +179,7 @@ class EventManager(PublishedManager, models.Manager):
def past_events(self, public=True): def past_events(self, public=True):
date_today = timezone.now().date() date_today = timezone.now().date()
qs = self.published(public).filter(event_start__lt=date_today) return self.published(public).filter(event_start__lt=date_today)
return qs
class FetMeetingManager(PublishedManager, models.Manager): class FetMeetingManager(PublishedManager, models.Manager):
@@ -204,5 +203,4 @@ class FetMeetingManager(PublishedManager, models.Manager):
def past_events(self): def past_events(self):
date_today = timezone.now().date() date_today = timezone.now().date()
qs = self.published().filter(event_start__lt=date_today) return self.published().filter(event_start__lt=date_today)
return qs

View File

@@ -1,376 +1,384 @@
import logging import logging
from datetime import timedelta from datetime import timedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from core.models import CustomFlatPage 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 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 .choices import PostType, Status
from .managers import ( from .managers import (
AllEventManager, AllEventManager,
ArticleManager, ArticleManager,
EventManager, EventManager,
FetMeetingManager, FetMeetingManager,
NewsManager, NewsManager,
PostManager, PostManager,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def create_pad_for_post(slug, item="agenda"): def create_pad_for_post(slug, item="agenda"):
logger.info("Pad-Type: %s", item) logger.info("Pad-Type: %s", item)
pad_id = slug + "-" + item pad_id = slug + "-" + item
if not ep_create_new_pad(pad_id): if not ep_create_new_pad(pad_id):
return "" return ""
# Set template into the newly created pad if it exists. # Set template into the newly created pad if it exists.
if page := CustomFlatPage.objects.filter(title__iexact=item).first(): if page := CustomFlatPage.objects.filter(title__iexact=item).first():
ep_set_html(pad_id, page.content) ep_set_html(pad_id, page.content)
logger.info("Template '%s' is set.", page.title) logger.info("Template '%s' is set.", page.title)
return pad_id return pad_id
class Post(models.Model): class Post(models.Model):
# legacy id is for the posts from the old website # legacy id is for the posts from the old website
legacy_id = models.IntegerField(null=True, blank=True) legacy_id = models.IntegerField(null=True, blank=True)
title = models.CharField(verbose_name="Titel", max_length=200) title = models.CharField(verbose_name="Titel", max_length=200)
subtitle = models.CharField(max_length=500, blank=True, default="") subtitle = models.CharField(max_length=500, blank=True, default="")
tags = TaggableManager(blank=True) tags = TaggableManager(blank=True)
slug = models.SlugField(unique=True, blank=True) slug = models.SlugField(unique=True, blank=True)
body = models.TextField(blank=True, default="") body = models.TextField(blank=True, default="")
image = models.ImageField(null=True, blank=True) image = models.ImageField(null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
public_date = models.DateField( public_date = models.DateField(
verbose_name="Veröffentlichung", verbose_name="Veröffentlichung",
null=True, null=True,
blank=True, blank=True,
default=timezone.now, default=timezone.now,
) )
post_type = models.CharField(max_length=1, choices=PostType.choices, editable=True) post_type = models.CharField(max_length=1, choices=PostType.choices, editable=True)
status = models.CharField( status = models.CharField(
verbose_name="Status", max_length=2, choices=Status.choices, default=Status.DRAFT verbose_name="Status", max_length=2, choices=Status.choices, default=Status.DRAFT
) )
# post is pinned at main page # post is pinned at main page
is_pinned = models.BooleanField(verbose_name="ANGEHEFTET", default=False) is_pinned = models.BooleanField(verbose_name="ANGEHEFTET", default=False)
# addional infos for events # addional infos for events
event_start = models.DateTimeField(verbose_name="Event Start", null=True, blank=True) 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_end = models.DateTimeField(verbose_name="Event Ende", null=True, blank=True)
event_place = models.CharField(max_length=200, blank=True, default="") event_place = models.CharField(max_length=200, blank=True, default="")
# protocol for fet meeting # protocol for fet meeting
has_protocol = models.BooleanField(default=False) has_protocol = models.BooleanField(default=False)
has_agenda = models.BooleanField(default=False) has_agenda = models.BooleanField(default=False)
protocol_key = models.CharField(max_length=200, blank=True, default="") protocol_key = models.CharField(max_length=200, blank=True, default="")
agenda_key = models.CharField(max_length=200, blank=True, default="") agenda_key = models.CharField(max_length=200, blank=True, default="")
# TimeStamps # TimeStamps
date_modified = models.DateTimeField(auto_now=True) date_modified = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True) date_created = models.DateTimeField(auto_now_add=True)
# Managers # Managers
objects = PostManager() objects = PostManager()
articles = ArticleManager() articles = ArticleManager()
def __str__(self): def __str__(self):
return "Post ({}, {}): {}".format( return "Post ({}, {}): {}".format(
self.slug, self.slug,
self.public_date.strftime("%d.%m.%Y"), self.public_date.strftime("%d.%m.%Y"),
self.title, self.title,
) )
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# save the post with some defaults # save the post with some defaults
if not self.public_date: if not self.public_date:
self.public_date = timezone.now().date() self.public_date = timezone.now().date()
if not self.slug: if not self.slug:
self.slug = slugify(self.public_date) + "-" + slugify(self.title) self.slug = slugify(self.public_date) + "-" + slugify(self.title)
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("posts:post", kwargs={"slug": self.slug}) return reverse("posts:post", kwargs={"slug": self.slug})
# "#" for backward compatibility # "#" for backward compatibility
_possible_empty_key_value = ["#", "", None] _possible_empty_key_value = ["#", "", None]
@property @property
def agenda_html(self) -> str | None: def agenda_html(self) -> str | None:
if self.agenda_key in self._possible_empty_key_value: if self.agenda_key in self._possible_empty_key_value:
return None return None
return ep_get_html(self.agenda_key) return ep_get_html(self.agenda_key)
@agenda_html.setter @agenda_html.setter
def agenda_html(self, value: str) -> str | None: def agenda_html(self, value: str) -> str | None:
if self.agenda_key in self._possible_empty_key_value: if self.agenda_key in self._possible_empty_key_value:
self.create_agenda_key() self.create_agenda_key()
if not value or not self.agenda_key: if not value or not self.agenda_key:
return None return None
ep_set_html(self.agenda_key, value) ep_set_html(self.agenda_key, value)
logger.info("Set agenda etherpad '%s' for post '%s'.", self.agenda_key, self.slug) logger.info("Set agenda etherpad '%s' for post '%s'.", self.agenda_key, self.slug)
return value return value
@property @property
def protocol_html(self) -> str | None: def protocol_html(self) -> str | None:
if self.protocol_key in self._possible_empty_key_value: if self.protocol_key in self._possible_empty_key_value:
return None return None
return ep_get_html(self.protocol_key) return ep_get_html(self.protocol_key)
@protocol_html.setter @protocol_html.setter
def protocol_html(self, value: str) -> str | None: def protocol_html(self, value: str) -> str | None:
if self.protocol_key in self._possible_empty_key_value: if self.protocol_key in self._possible_empty_key_value:
self.create_protocol_key() self.create_protocol_key()
if not value or not self.protocol_key: if not value or not self.protocol_key:
return None return None
ep_set_html(self.protocol_key, value) ep_set_html(self.protocol_key, value)
logger.info("Set protocol etherpad '%s' for post '%s'.", self.protocol_key, self.slug) logger.info("Set protocol etherpad '%s' for post '%s'.", self.protocol_key, self.slug)
return value return value
_agenda_filename = None _agenda_filename = None
_agenda_url = None _agenda_url = None
@property @property
def agenda_url(self) -> str | None: def agenda_url(self) -> str | None:
if not self.has_agenda: if not self.has_agenda:
self._agenda_url = None self._agenda_url = None
self._agenda_filename = None self._agenda_filename = None
return self._agenda_url return self._agenda_url
if self._agenda_url: if self._agenda_url:
return self._agenda_url return self._agenda_url
if url := ep_get_url(self.agenda_key): if url := ep_get_url(self.agenda_key):
self._agenda_url = url self._agenda_url = url
self._agenda_filename = self.slug + "-agenda.pdf" self._agenda_filename = self.slug + "-agenda.pdf"
else: else:
self._agenda_url = None self._agenda_url = None
self._agenda_filename = None self._agenda_filename = None
return self._agenda_url return self._agenda_url
@property @property
def agenda_filename(self) -> str | None: def agenda_filename(self) -> str | None:
# TODO: fix pdf render # TODO: fix pdf render
# if self._agenda_filename: # if self._agenda_filename:
# return self._agenda_filename # return self._agenda_filename
# if self.has_agenda and self.agenda_url: # if self.has_agenda and self.agenda_url:
# return self.slug + "-agenda.pdf" # return self.slug + "-agenda.pdf"
return None return None
_protocol_filename = None _protocol_filename = None
_protocol_url = None _protocol_url = None
@property @property
def protocol_url(self) -> str | None: def protocol_url(self) -> str | None:
if not self.has_protocol: if not self.has_protocol:
self._protocol_url = None self._protocol_url = None
self._protocol_filename = None self._protocol_filename = None
return self._protocol_url return self._protocol_url
if self._protocol_url: if self._protocol_url:
return self._protocol_url return self._protocol_url
if url := ep_get_url(self.protocol_key): if url := ep_get_url(self.protocol_key):
self._protocol_url = url self._protocol_url = url
self._protocol_filename = self.slug + "-protokoll.pdf" self._protocol_filename = self.slug + "-protokoll.pdf"
else: else:
self._protocol_url = None self._protocol_url = None
self._protocol_filename = None self._protocol_filename = None
return self._protocol_url return self._protocol_url
@property @property
def protocol_filename(self) -> str | None: def protocol_filename(self) -> str | None:
# TODO: fix pdf render # TODO: fix pdf render
# if self._protocol_filename: # if self._protocol_filename:
# return self._protocol_filename # return self._protocol_filename
# if self.has_protocol and self.protocol_url: # if self.has_protocol and self.protocol_url:
# return self.slug + "-protokoll.pdf" # return self.slug + "-protokoll.pdf"
return None return None
def create_agenda_key(self) -> None: def create_agenda_key(self) -> None:
""" """
Create a Etherpad Id for the Pad associated to this post. Create a Etherpad Id for the Pad associated to this post.
Create the pad if it doesn't exist. Create the pad if it doesn't exist.
""" """
if self.slug: if self.slug:
self.agenda_key = create_pad_for_post(self.slug, "agenda") self.agenda_key = create_pad_for_post(self.slug, "agenda")
def create_protocol_key(self) -> None: def create_protocol_key(self) -> None:
""" """
Create a Etherpad Id for the Pad associated to this post. Create a Etherpad Id for the Pad associated to this post.
Create the pad if it doesn't exist. Create the pad if it doesn't exist.
""" """
if self.slug: if self.slug:
self.protocol_key = create_pad_for_post(self.slug, "protocol") self.protocol_key = create_pad_for_post(self.slug, "protocol")
def get_model_name(self): def get_model_name(self):
return self._meta.model_name return self._meta.model_name
@property @property
def three_tag_names(self): def three_tag_names(self):
return self.tags.names()[:3] return self.tags.names()[:3]
@property @property
def tag_names(self): def tag_names(self):
return [t for t in self.tags.names()] return [t for t in self.tags.names()]
@property @property
def imageurl(self) -> str: def imageurl(self) -> str:
return ( return (
self.image.url if self.image else settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png" self.image.url if self.image else settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
) )
def clean(self): def clean(self):
if self.event_end and self.event_end < self.event_start: if self.event_end and self.event_end < self.event_start:
raise ValidationError("Das Ende des Events liegt vor dem Beginn.") msg = "Das Ende des Events liegt vor dem Beginn."
if self.event_start and self.post_type not in ["E", "F"]: raise ValidationError(msg, code="invalid_event_end")
raise ValidationError("Für diesen Post Typ ist kein Event Start zulässig.") if self.event_start and self.post_type not in ["E", "F"]:
msg = "Für diesen Post Typ ist kein Event Start zulässig."
@property raise ValidationError(msg, code="invalid_event_start")
def published(self):
return self.status == Status.PUBLIC @property
def published(self):
return self.status == Status.PUBLIC
class News(Post):
objects = NewsManager()
class News(Post):
class Meta: objects = NewsManager()
proxy = True
class Meta:
verbose_name = "News" proxy = True
verbose_name_plural = "News"
verbose_name = "News"
def save(self, *args, **kwargs): verbose_name_plural = "News"
if not self.post_type:
self.post_type = "N" def save(self, *args, **kwargs):
if not self.post_type:
super().save(*args, **kwargs) self.post_type = "N"
super().save(*args, **kwargs)
class Event(Post):
only_events = EventManager()
all_events = AllEventManager() class Event(Post):
only_events = EventManager()
class Meta: all_events = AllEventManager()
proxy = True
class Meta:
verbose_name = "Event" proxy = True
verbose_name_plural = "Events"
verbose_name = "Event"
def __init__(self, *args, **kwargs): verbose_name_plural = "Events"
super().__init__(*args, **kwargs)
self.post_type = "E" def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def save(self, *args, **kwargs): self.post_type = "E"
if not self.post_type:
self.post_type = "E" def save(self, *args, **kwargs):
if not self.event_end: if not self.post_type:
self.event_end = self.event_start + timedelta(hours=2) self.post_type = "E"
if not self.event_end:
super().save(*args, **kwargs) self.event_end = self.event_start + timedelta(hours=2)
def clean(self): super().save(*args, **kwargs)
super().clean()
if not self.event_start: def clean(self):
raise ValidationError("Das Datum des Events fehlt.") super().clean()
if not self.event_start:
msg = "Das Datum des Events fehlt."
class FetMeeting(Event): raise ValidationError(msg, code="missing_event_start")
objects = FetMeetingManager()
class Meta: class FetMeeting(Event):
proxy = True objects = FetMeetingManager()
verbose_name = "Fet Sitzung" class Meta:
verbose_name_plural = "Fet Sitzungen" proxy = True
def __init__(self, *args, **kwargs): verbose_name = "Fet Sitzung"
super().__init__(*args, **kwargs) verbose_name_plural = "Fet Sitzungen"
self.post_type = "F"
def __init__(self, *args, **kwargs):
def save(self, *args, **kwargs): super().__init__(*args, **kwargs)
self.title = "Fachschaftssitzung" self.post_type = "F"
if not self.slug:
self.slug = self.__get_slug() def save(self, *args, **kwargs):
self.title = "Fachschaftssitzung"
if not ep_pad_exists(self.agenda_key) or self.agenda_key in self._possible_empty_key_value: if not self.slug:
self.create_agenda_key() self.slug = self.__get_slug()
if self.agenda_key:
self.has_agenda = True if not ep_pad_exists(self.agenda_key) or self.agenda_key in self._possible_empty_key_value:
self.create_agenda_key()
if ( if self.agenda_key:
not ep_pad_exists(self.protocol_key) self.has_agenda = True
or self.protocol_key in self._possible_empty_key_value
): if (
self.create_protocol_key() not ep_pad_exists(self.protocol_key)
if self.protocol_key: or self.protocol_key in self._possible_empty_key_value
self.has_protocol = True ):
self.create_protocol_key()
if not self.post_type: if self.protocol_key:
self.post_type = "F" self.has_protocol = True
if not self.event_place: if not self.post_type:
self.event_place = "FET" self.post_type = "F"
# make duration 2 hours if not specified otherwise if not self.event_place:
if not self.event_end: self.event_place = "FET"
self.event_end = self.event_start + timedelta(hours=2)
# make duration 2 hours if not specified otherwise
# set FET Meeting always public if not self.event_end:
self.status = Status.PUBLIC self.event_end = self.event_start + timedelta(hours=2)
super().save(*args, **kwargs) # set FET Meeting always public
self.status = Status.PUBLIC
def __get_slug(self) -> str:
slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung") super().save(*args, **kwargs)
if Post.objects.filter(slug=slug).exists() and Post.objects.get(slug=slug).id != self.id: def __get_slug(self) -> str:
raise ValidationError("Es existiert bereits eine Sitzung mit demselben Datum.") slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung")
return slug 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."
def clean(self): logger.error(
super().clean() "A fet meeting with same date (slug: %(slug)s) is already existing.",
if not self.slug: extra={"slug": slug},
self.slug = self.__get_slug() )
raise ValidationError(msg, code="duplicate_fet_meeting")
class FileUpload(models.Model): return slug
title = models.CharField(verbose_name="Titel", max_length=200)
file_field = models.FileField(verbose_name="Dokument", upload_to="uploads/posts/files/") def clean(self):
post = models.ForeignKey(Post, on_delete=models.CASCADE) super().clean()
if not self.slug:
objects = models.Manager() self.slug = self.__get_slug()
def __str__(self):
return self.title 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

View File

@@ -1,36 +1,36 @@
from haystack import indexes from haystack import indexes
from html2text import html2text from html2text import html2text
from .models import Post from .models import Post
class PostIndex(indexes.SearchIndex, indexes.Indexable): class PostIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True) text = indexes.CharField(document=True)
title = indexes.EdgeNgramField(model_attr="title") title = indexes.EdgeNgramField(model_attr="title")
body = indexes.EdgeNgramField(model_attr="body", null=True) body = indexes.EdgeNgramField(model_attr="body", null=True)
status = indexes.CharField(model_attr="status") status = indexes.CharField(model_attr="status")
date = indexes.DateField() date = indexes.DateField()
agenda = indexes.EdgeNgramField() agenda = indexes.EdgeNgramField()
protocol = indexes.EdgeNgramField() protocol = indexes.EdgeNgramField()
def get_model(self): def get_model(self):
return Post return Post
def index_queryset(self, using=None): def index_queryset(self, using=None):
return self.get_model().objects.date_sorted(public=False) return self.get_model().objects.date_sorted(public=False)
def prepare_date(self, obj): def prepare_date(self, obj):
if obj.post_type == "N": if obj.post_type == "E" or obj.post_type == "F":
return obj.public_date return obj.event_start.date()
elif 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): def prepare_agenda(self, obj):
if obj.has_agenda and obj.agenda_html: if obj.has_agenda and obj.agenda_html:
return html2text(obj.agenda_html) return html2text(obj.agenda_html)
return None return None
def prepare_protocol(self, obj): def prepare_protocol(self, obj):
if obj.has_protocol and obj.protocol_html: if obj.has_protocol and obj.protocol_html:
return html2text(obj.protocol_html) return html2text(obj.protocol_html)
return None return None

View File

@@ -117,15 +117,17 @@ class PostDetailView(DetailView):
if not obj.published: if not obj.published:
related_posts.remove(obj) related_posts.remove(obj)
context = { context.update(
"post": self.object, {
"files": files, "post": self.object,
"author": author, "files": files,
"author_image": author_image, "author": author,
"next": self.post_next(), "author_image": author_image,
"prev": self.post_prev(), "next": self.post_next(),
"related_posts": related_posts[:4], "prev": self.post_prev(),
} "related_posts": related_posts[:4],
}
)
return context return context
@@ -309,7 +311,8 @@ def show_pdf(request, html, filename):
html = html[:idx] + rendered + html[idx:] html = html[:idx] + rendered + html[idx:]
if not (pdf := render_to_pdf(html)): 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") response = HttpResponse(pdf, content_type="application/pdf")

View File

@@ -1,6 +1,6 @@
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
from django.db import models from django.db import models
from django.forms import ValidationError
from .mails import send_mail_approved, send_mail_rejected, send_mail_returned from .mails import send_mail_approved, send_mail_rejected, send_mail_returned
from .managers import RentalItemsManager from .managers import RentalItemsManager
@@ -116,7 +116,8 @@ class Rental(models.Model):
self.date_end = self.date_start self.date_end = self.date_start
if self.date_start > self.date_end: 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: def calc_total_deposit(self) -> int:
total_deposit = 0 total_deposit = 0