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
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]

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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,
},
)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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")

View File

@@ -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