Compare commits

8 Commits

40 changed files with 491 additions and 598 deletions

View File

@@ -30,11 +30,6 @@ docker-compose up
### Command Befehle
Erstellt die fehlenden Thumbs für die Alben in der Galerie:
<code>
python3 fet2020/manage.py create_thumbs
</code>
Erstellt alle Searchindexes neu:
<code>
python3 fet2020/manage.py rebuild_index

View File

@@ -74,6 +74,18 @@ def get_app_list(self, request, app_label=None):
}
app["models"].sort(key=lambda x: ordering[x["name"]])
elif app["app_label"] == "finance":
ordering = {
"Rechnungen": 1,
"Wiref Formulare": 2,
"Beschlüsse": 3,
"Bankdaten": 4,
}
app["models"].sort(key=lambda x: ordering[x["name"]])
else:
app["models"].sort(key=lambda x: x["name"])
return app_list

View File

@@ -22,7 +22,7 @@ def get_ep_client():
api_key = f.read()
api_key = api_key.rstrip()
if api_key == "":
if not api_key:
logger.info("API Key is missing. Path: %s", api_key_path)
return None
@@ -56,97 +56,114 @@ def get_ep_group(ep_group_name: str = "fet") -> dict[str, str]:
return ep_group
def ep_pad_exists(pad_id: str | None = None) -> bool | None:
def ep_pad_exists(pad_id: str = "") -> bool:
"""Check if pad exists.
Parameters
----------
pad_id : str | None
pad_id : str
Id of pad that is checked if it exists.
Returns
-------
bool | None
bool
- True: Pad exists.
- False: Pad doesn't exist.
- False: Pad not found.
- None: There is no pad id or no connection to etherpad server.
"""
if pad_id is None:
if not pad_id:
return None
if (ep_c := get_ep_client()) is None:
if not (ep_c := get_ep_client()):
return None
if (ep_group := get_ep_group()) is None:
if not (ep_group := get_ep_group()):
return None
lists = ep_c.listPads(groupID=ep_group["groupID"])
if any(pad_id in s for s in lists["padIDs"]):
logger.info("Etherpad exists. Pad: %s", pad_id)
logger.info("Etherpad '%s' exists.", pad_id)
return True
logger.info("Etherpad doesn't exist. Pad: %s", pad_id)
logger.info("Etherpad '%s' not found.", pad_id)
return False
def ep_create_new_pad(pad_id: str | None, text="helloworld"):
def ep_create_new_pad(pad_id: str, text="helloworld") -> str:
"""Create a new pad if it doesn't exist.
Parameters
----------
pad_id : str
Id of the new pad to be created.
text : str
Text for new pad.
Returns
-------
str
Id of the new pad.
- None: No pad is created.
"""
Create a pad if it doesn't exist.
Return a pad_id if new pad is created. Otherwise None (when the padID exists already).
"""
if pad_id is None:
if not pad_id:
return None
if (ep_c := get_ep_client()) is None:
if not (ep_c := get_ep_client()):
return None
if (ep_group := get_ep_group()) is None:
return None
if ep_pad_exists(pad_id) is False:
ep_c.createGroupPad(groupID=ep_group["groupID"], padName=pad_id, text=text)
logger.info("Create new etherpad. Pad: %s", pad_id)
return pad_id
return None
def ep_get_html(pad_id: str | None) -> str | None:
if (ep_c := get_ep_client()) is None:
return None
if (ep_group := get_ep_group()) is None:
if not (ep_group := get_ep_group()):
return None
if ep_pad_exists(pad_id):
return ep_c.getHTML(padID=ep_group["groupID"] + "$" + pad_id)["html"]
return ""
def ep_set_html(pad_id: str | None, html: str):
if (ep_c := get_ep_client()) is None:
logger.info("Can't create new etherpad '%s' because it already exists.", pad_id)
return None
if (ep_group := get_ep_group()) is None:
ep_c.createGroupPad(groupID=ep_group["groupID"], padName=pad_id, text=text)
logger.info("Create new etherpad '%s'.", pad_id)
return pad_id
def ep_get_html(pad_id: str) -> str:
if not pad_id:
return None
if not (ep_c := get_ep_client()):
return None
if ep_pad_exists(pad_id):
ep_c.setHTML(padID=ep_group["groupID"] + "$" + pad_id, html=html)
return True
return None
def ep_get_url(pad_id: str | None):
if (ep_group := get_ep_group()) is None:
if not (ep_group := get_ep_group()):
return None
if ep_pad_exists(pad_id):
return urljoin(
settings.ETHERPAD_CLIENT["exturl"],
"p/" + ep_group["groupID"] + "$" + str(pad_id),
)
if not ep_pad_exists(pad_id):
return None
return ep_c.getHTML(padID=ep_group["groupID"] + "$" + pad_id)["html"]
return "#"
def ep_set_html(pad_id: str, html: str) -> bool:
if not pad_id:
return None
if not (ep_c := get_ep_client()):
return None
if not (ep_group := get_ep_group()):
return None
if not ep_pad_exists(pad_id):
return None
ep_c.setHTML(padID=ep_group["groupID"] + "$" + pad_id, html=html)
return True
def ep_get_url(pad_id: str):
if not pad_id:
return None
if not (ep_group := get_ep_group()):
return None
if not ep_pad_exists(pad_id):
return None
return urljoin(settings.ETHERPAD_CLIENT["exturl"], "p/" + ep_group["groupID"] + "$" + str(pad_id))

View File

@@ -10,11 +10,11 @@ from .api import get_ep_client, get_ep_group
@ep_authenticated_user
def _create_ep_session(request, expires):
if (ep_c := get_ep_client()) is None:
if not (ep_c := get_ep_client()):
return None
if (ep_group := get_ep_group()) is None:
return None, None
if not (ep_group := get_ep_group()):
return None
author = ep_c.createAuthorIfNotExistsFor(
name=str(request.user),

View File

@@ -394,6 +394,8 @@ TAGGIT_FORCE_LOWERCASE = True
# THUMBNAIL
THUMBNAIL_DEFAULT_STORAGE_ALIAS = "default"
THUMBNAIL_ALIASES = {
"": {
"avatar": {"size": (50, 50), "crop": True},

View File

@@ -6,7 +6,7 @@ from posts.models import Event, FetMeeting, Post
def index(request):
posts = Post.articles.date_sorted_list()
posts = Post.articles.date_sorted()
# A maximum of 5 posts should be displayed on the startpage.
post_count = 5

View File

@@ -346,28 +346,28 @@ class BillAdmin(admin.ModelAdmin):
status=obj.get_status_display(),
)
@admin.action(description="Als 'Abgerechnet' markieren.")
@admin.action(description="Als 'Für Überweisung freigegeben' markieren.")
def make_cleared(self, request, queryset):
updated = queryset.update(status=Bill.Status.CLEARED)
self.message_user(
request,
ngettext(
"%d Rechnung wurde als 'Abgerechnet' markiert.",
"%d Rechnungen wurden als 'Abgerechnet' markiert.",
"%d Rechnung wurde als 'Für Überweisung freigegeben' markiert.",
"%d Rechnungen wurden als 'Für Überweisung freigegeben' markiert.",
updated,
)
% updated,
messages.SUCCESS,
)
@admin.action(description="Als 'Abgeschlossen' markieren.")
@admin.action(description="Als 'Abgeschlossen / Überwiesen' markieren.")
def make_finished(self, request, queryset):
updated = queryset.update(status=Bill.Status.FINISHED)
self.message_user(
request,
ngettext(
"%d Rechnung wurde als 'Abgeschlossen' markiert.",
"%d Rechnungen wurden als 'Abgeschlossen' markiert.",
"%d Rechnung wurde als 'Abgeschlossen / Überwiesen' markiert.",
"%d Rechnungen wurden als 'Abgeschlossen / Überwiesen' markiert.",
updated,
)
% updated,

View File

@@ -439,21 +439,19 @@ class ResolutionAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # to get the self.fields set
resolution = kwargs.get("instance")
bills = Bill.objects.filter(resolution=resolution)
total = 0
for elem in bills:
total += elem.amount
budget = 0.0
total = 0.0
if (resolution := kwargs.get("instance")) is not None:
for elem in Bill.objects.filter(resolution=resolution):
total += elem.amount
budget = resolution.budget
self.fields["total"].disabled = True
self.fields["total"].initial = total
self.fields["total"].label = "Gesamtsumme (EUR)"
self.fields["total"].required = False
budget = 0
if resolution:
budget = resolution.budget
self.fields["budget_remaining"].disabled = True
self.fields["budget_remaining"].initial = budget - total
self.fields["budget_remaining"].label = "Restbudget (EUR)"

View File

@@ -154,7 +154,7 @@ class Bill(models.Model):
class Affiliation(models.TextChoices):
VEREIN = "V", "Vereinsbudget"
OFFICIAL = "B", "Offizielles Budget"
OFFICIAL = "B", "Wiref-Budget"
REPRESENTATION = "R", "Bundesvertretung"
affiliation = models.CharField(
@@ -186,9 +186,9 @@ class Bill(models.Model):
class Status(models.TextChoices):
SUBMITTED = "S", "Eingereicht"
INCOMPLETED = "I", "Unvollständig"
CLEARED = "C", "Abgerechnet"
FINISHED = "F", "Abgeschlossen"
INCOMPLETED = "I", "Unvollständig / Abgelehnt"
CLEARED = "C", "Für Überweisung freigegeben"
FINISHED = "F", "Abgeschlossen / Überwiesen"
status = models.CharField(
max_length=1,

View File

@@ -15,11 +15,7 @@ class AlbumAdmin(admin.ModelAdmin):
"Der Ordner für die Fotos liegt am Server unter '/mnt/save/fotos/www'. "
"Fette Schriften sind Pflichtfelder."
)
return super().add_view(
request,
form_url,
extra_context=extra_context,
)
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 {}
@@ -27,12 +23,7 @@ class AlbumAdmin(admin.ModelAdmin):
"Der Ordner für die Fotos liegt am Server unter '/mnt/save/fotos/www'. "
"Fette Schriften sind Pflichtfelder."
)
return super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)
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

View File

@@ -0,0 +1,6 @@
from django.db import models
class Status(models.TextChoices):
DRAFT = "10", "DRAFT"
PUBLIC = "20", "PUBLIC"

View File

@@ -11,11 +11,7 @@ class AlbumAdminForm(forms.ModelForm):
widgets = {"description": CKEditorUploadingWidget(config_name="default")}
labels = {
"slug": "Permalink",
"event_place": "Event Ort",
"description": "Beschreibung",
}
labels = {"slug": "Permalink", "event_place": "Event Ort", "description": "Beschreibung"}
help_texts = {
"folder_name": "Füge den Ordnername (ohne Pfade) ein.",

View File

@@ -1,16 +0,0 @@
import logging
from django.core.management.base import BaseCommand
from gallery.utils import create_thumbs, get_folder_list
logger = logging.getLogger(__name__)
class Command(BaseCommand):
def handle(self, *args, **options):
for folder in get_folder_list():
logger.info("Folder '%s' in process.", folder)
create_thumbs(folder)
logger.info("Command 'create thumbs' ended.")

View File

@@ -0,0 +1,11 @@
from django.db import models
from .choices import Status
class AlbumManager(models.Manager):
def get_queryset(self):
return super().get_queryset().order_by("-event_date")
def public(self):
return self.get_queryset().filter(status=Status.PUBLIC)

View File

@@ -1,45 +1,44 @@
import logging
from random import randint
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from .choices import Status
from .managers import AlbumManager
from .utils import get_image_list
logger = logging.getLogger(__name__)
class Album(models.Model):
title = models.CharField(verbose_name="Titel", max_length=200)
slug = models.SlugField(unique=True, null=True, blank=True)
folder_name = models.CharField(verbose_name="Ordner Name", max_length=200)
thumbnail = models.CharField(verbose_name="Thumbnail", max_length=200, null=True, blank=True)
thumbnail = models.CharField(verbose_name="Thumbnail", max_length=200, blank=True, default="")
event_date = models.DateField(
verbose_name="Event Datum",
null=True,
blank=True,
default=timezone.now,
verbose_name="Event Datum", null=True, blank=True, default=timezone.now
)
event_place = models.CharField(max_length=200, blank=True)
photographer = models.CharField(
verbose_name="Fotograph(en)",
max_length=200,
null=True,
blank=True,
verbose_name="Fotograph(en)", max_length=200, blank=True, default=""
)
DRAFT = "10"
PUBLIC = "20"
STATUS = (
(DRAFT, "DRAFT"),
(PUBLIC, "PUBLIC"),
)
status = models.CharField(max_length=2, choices=STATUS, default=DRAFT)
# TextChoices
Status = Status
description = models.TextField(null=True, blank=True)
status = models.CharField(max_length=2, choices=Status.choices, default=Status.DRAFT)
description = models.TextField(blank=True, default="")
# Managers
objects = models.Manager()
objects = AlbumManager()
class Meta:
verbose_name = "Album"
@@ -57,14 +56,32 @@ class Album(models.Model):
def get_absolute_url(self):
return reverse("gallery:album", kwargs={"slug": self.slug})
def get_images(self):
def clean(self):
if not self.images:
logger.info("No thumbnails are generated.")
@property
def images(self) -> list:
return get_image_list(self.folder_name)
def get_images_length_sub_3(self):
return len(self.get_images()) - 3
return len(self.images) - 3
def get_model_name(self):
return self._meta.model_name
def get_thumbnail(self):
return None
@property
def get_album_thumbnail(self):
if img_list := self.images:
return next(
(
img["thumb_url"]
for img in img_list
if self.thumbnail and self.thumbnail in img["title"]
),
img_list[randint(0, len(img_list) - 1)]["thumb_url"],
)
self.status = Album.Status.DRAFT
logger.info("Album '%s' is empty.", self.title)
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"

View File

@@ -1,11 +1,12 @@
from django.urls import path
from . import apps, views
from .views import AlbumDetailView, DraftAlbumDetailView
app_name = apps.GalleryConfig.name
urlpatterns = [
path("", views.index, name="index"),
path("<slug:slug>/", views.show_album, name="album"),
path("draft/<slug:slug>/", views.show_draft_album, name="album_draft"),
path("<slug:slug>/", AlbumDetailView.as_view(), name="album"),
path("draft/<slug:slug>/", DraftAlbumDetailView.as_view(), name="album_draft"),
]

View File

@@ -1,80 +1,56 @@
import logging
import os
from pathlib import Path
from django.conf import settings
from django.core.validators import get_available_image_extensions
from PIL import Image, ImageOps
gallery_path = settings.GALLERY["path"]
gallery_thumb_path = settings.GALLERY["thumb_path"]
gallery_path = Path(settings.MEDIA_ROOT) / settings.GALLERY["path"]
gallery_path_url = Path(settings.MEDIA_URL) / settings.GALLERY["path"]
gallery_thumb_path = Path(settings.MEDIA_ROOT) / settings.GALLERY["thumb_path"]
gallery_thumb_path_url = Path(settings.MEDIA_URL) / settings.GALLERY["thumb_path"]
logger = logging.getLogger(__name__)
size = (320, 320)
valid_images = [".jpg", ".png"]
def get_image_list(folder_name):
file_path = os.path.join(settings.MEDIA_ROOT + gallery_path, folder_name)
def get_image_list(folder_name: str) -> list:
image_path = Path(gallery_path) / folder_name
thumb_path = Path(gallery_thumb_path) / folder_name
img_list = []
if os.path.exists(file_path):
for img in os.listdir(file_path):
ext = os.path.splitext(img)[1]
if ext.lower() not in valid_images:
continue
if not Path(image_path).exists():
logger.info("Image path '%s' not found.", image_path)
return img_list
thumb_path = os.path.join(settings.MEDIA_ROOT + gallery_thumb_path, folder_name)
thumb_file_path = os.path.join(thumb_path, f"thumb_{img}")
if os.path.exists(thumb_file_path):
thumb_url = os.path.join(
settings.MEDIA_URL + gallery_thumb_path,
folder_name + "/" + f"thumb_{img}",
)
else:
thumb_url = None
Path(thumb_path).mkdir(exist_ok=True)
img_dict = {
"title": img,
"image_url": os.path.join(
settings.MEDIA_URL + gallery_path,
folder_name + "/" + img,
),
"thumb_url": thumb_url,
}
for _file in os.listdir(image_path):
if Path(_file).suffix.lower()[1:] not in get_available_image_extensions():
continue
img_list.append(img_dict)
thumb_file_path = Path(thumb_path) / f"thumb_{_file}"
if not Path(thumb_file_path).exists():
with Image.open(Path(image_path) / _file, "r") as im:
if im._getexif() is not None:
im = ImageOps.exif_transpose(im)
thumb = ImageOps.fit(im, size, Image.Resampling.LANCZOS)
thumb.save(thumb_file_path)
logger.info("Save thumb 'thumb_%s'.", _file)
img_dict = {
"title": _file,
"image_url": Path(gallery_path_url) / folder_name / _file,
"thumb_url": Path(gallery_thumb_path_url) / folder_name / f"thumb_{_file}",
}
img_list.append(img_dict)
return img_list
def get_folder_list():
if os.path.exists(settings.MEDIA_ROOT + gallery_path):
return next(os.walk(settings.MEDIA_ROOT + gallery_path))[1]
if Path(gallery_path).exists():
return next(os.walk(gallery_path))[1]
return None
def create_thumbs(folder_path):
file_path = os.path.join(settings.MEDIA_ROOT + gallery_path, folder_path)
thumb_path = os.path.join(settings.MEDIA_ROOT + gallery_thumb_path, folder_path)
if os.path.exists(file_path):
os.makedirs(thumb_path, exist_ok=True)
for f in os.listdir(file_path):
ext = os.path.splitext(f)[1]
if ext.lower() not in valid_images:
continue
thumb_file_path = os.path.join(thumb_path, f"thumb_{f}")
if os.path.exists(thumb_file_path):
continue
image_path = os.path.join(file_path, f)
logger.info("Edit picture '%s'.", f)
with Image.open(str(image_path), "r") as image:
if image._getexif() is not None:
image = ImageOps.exif_transpose(image)
thumb = ImageOps.fit(image, size, Image.ANTIALIAS)
thumb.save(thumb_file_path)
logger.info("Save thumb 'thumb_%s'.", f)

View File

@@ -1,94 +1,65 @@
import logging
from collections import deque
from random import randint
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404
from django.shortcuts import render
from django.utils.text import slugify
from authentications.decorators import authenticated_user
from django.views.generic.detail import DetailView
from .models import Album
from .utils import create_thumbs, get_folder_list
from .utils import get_folder_list
logger = logging.getLogger(__name__)
def index(request):
if request.user.is_authenticated:
albums = deque(Album.objects.all().order_by("-event_date"))
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)
)
# get albums that are in the server but not in the db.
folders = get_folder_list()
if folders:
for folder in folders:
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 = deque(Album.objects.filter(status=Album.PUBLIC).order_by("-event_date"))
# Show only PUBLIC albums.
albums = Album.objects.public()
for album in list(albums):
img_list = album.get_images()
if img_list:
if album.thumbnail:
for img in img_list:
if album.thumbnail in img["title"]:
album.thumbnail = img["thumb_url"]
break
else:
value = randint(0, len(img_list) - 1)
album.thumbnail = img_list[value]["thumb_url"]
else:
value = randint(0, len(img_list) - 1)
album.thumbnail = img_list[value]["thumb_url"]
else:
# empty album is temporarily set to DRAFT.
album.thumbnail = settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
album.status = Album.DRAFT
context = {
"albums": albums,
}
context = {"albums": albums}
return render(request, "gallery/index.html", context)
def show_album(request, slug):
album = Album.objects.filter(slug=slug).first()
if not album:
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("wrong album slug")
class AlbumDetailView(DetailView):
model = Album
template_name = "gallery/album.html"
img_list = album.get_images()
if not img_list:
# empty album is temporarily set to DRAFT.
album.status = Album.DRAFT
create_thumbs(album.folder_name)
context = {
"album": album,
"images": img_list,
}
return render(request, "gallery/album.html", context)
def get_queryset(self):
return (
Album.objects.public()
if not self.request.user.is_authenticated
else Album.objects.all()
)
@authenticated_user
def show_draft_album(request, slug):
return show_album(request, slug)
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

View File

@@ -36,7 +36,7 @@ class JobOverviewInline(JobMemberInline):
verbose_name_plural = "Tätigkeitsübersicht"
def get_queryset(self, request):
return JobMember.members.get_all_jobs_sorted()
return JobMember.members.get_all_jobs()
class ActiveMemberInline(JobMemberInline):
@@ -81,11 +81,8 @@ class MemberAdmin(admin.ModelAdmin):
"nickname",
"mailaccount",
"role",
"description",
"image",
"birthday",
"phone",
"address",
"description",
),
},
),

View File

@@ -24,8 +24,9 @@ class ActiveMemberForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
member_qs = self.fields["member"].queryset.filter(role="A").order_by("firstname", "surname")
self.fields["member"].queryset = member_qs
self.fields["member"].queryset = (
self.fields["member"].queryset.filter(role="A").order_by("firstname", "surname")
)
class InactiveMemberForm(forms.ModelForm):
@@ -35,8 +36,9 @@ class InactiveMemberForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
member_qs = self.fields["member"].queryset.order_by("firstname", "surname")
self.fields["member"].queryset = member_qs
self.fields["member"].queryset = self.fields["member"].queryset.order_by(
"firstname", "surname"
)
class MemberForm(forms.ModelForm):
@@ -54,9 +56,6 @@ class MemberForm(forms.ModelForm):
labels = {
"description": "Beschreibung zu der Person",
"image": "Porträt",
"birthday": "Geburtstag",
"phone": "Telefonnummer",
"address": "Wohnadresse",
}
widgets = {"description": CKEditorUploadingWidget(config_name="default")}

View File

@@ -17,16 +17,13 @@ class ActiveJobMemberManager(models.Manager):
def get_queryset(self):
date_today = timezone.now().date()
qs = (
return (
super()
.get_queryset()
.filter(Q(member__role="A") & (Q(job_end__gt=date_today) | Q(job_end__isnull=True)))
.order_by("job_role", "member__firstname", "member__surname", "job_start")
)
return qs.filter(
Q(member__role="A") & (Q(job_end__gt=date_today) | Q(job_end__isnull=True)),
)
class InactiveJobMemberManager(models.Manager):
"""return a list of inactive member."""
@@ -40,39 +37,42 @@ class InactiveJobMemberManager(models.Manager):
def get_queryset(self):
date_today = timezone.now().date()
qs = super().get_queryset().order_by("member__firstname", "member__surname", "-job_start")
return qs.filter(
Q(member__role="P")
| (Q(job_end__lt=date_today + timedelta(days=1)) & Q(job_end__isnull=False)),
return (
super()
.get_queryset()
.filter(
Q(member__role="P")
| (Q(job_end__lt=date_today + timedelta(days=1)) & Q(job_end__isnull=False))
)
.order_by("member__firstname", "member__surname", "-job_start")
)
class JobMemberManager(models.Manager):
def get_members(self, role):
qs = self.get_queryset().order_by("member__firstname")
return qs.filter(Q(member__role=role))
def get_all_jobs_sorted(self):
qs = self.get_queryset().order_by(
F("job_end").desc(nulls_first=True),
"-job_start",
"job__name",
def get_all_jobs(self):
return self.get_queryset().order_by(
F("job_end").desc(nulls_first=True), "-job_start", "job__name"
)
return qs
def get_active_jobs(self, member_id):
date_today = timezone.now().date()
qs = self.get_queryset().filter(member__id=member_id).order_by("-job_start", "job__name")
return qs.filter(Q(job_end__gt=date_today) | Q(job_end__isnull=True))
return (
self.get_queryset()
.filter(Q(member__id=member_id) & (Q(job_end__gt=date_today) | Q(job_end__isnull=True)))
.order_by("-job_start", "job__name")
)
def get_inactive_jobs(self, member_id):
date_today = timezone.now().date()
qs = self.get_queryset().filter(member__id=member_id).order_by("-job_start", "job__name")
return qs.filter(Q(job_end__lt=date_today + timedelta(days=1)) & Q(job_end__isnull=False))
return (
self.get_queryset()
.filter(
Q(member__id=member_id)
& Q(job_end__lt=date_today + timedelta(days=1))
& Q(job_end__isnull=False)
)
.order_by("-job_start", "job__name")
)
class MemberManager(models.Manager):

View File

@@ -26,7 +26,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='member',
name='phone',
field=models.CharField(blank=True, max_length=17, validators=[members.validators.PhoneNumberValidator()]),
field=models.CharField(blank=True, max_length=17),
),
migrations.AlterField(
model_name='member',

View File

@@ -16,12 +16,12 @@ from .managers import (
MemberManager,
)
from .validators import (
PhoneNumberValidator,
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__)
@@ -55,23 +55,13 @@ class Member(models.Model):
default=MemberRole.ACTIVE,
)
description = models.TextField(null=True, blank=True)
description = models.TextField(blank=True, default="")
image = ThumbnailerImageField(
upload_to="uploads/members/image/",
validators=[validate_file_size, validate_image_dimension],
)
birthday = models.DateField(null=True, blank=True)
phone = models.CharField(
max_length=17,
blank=True,
validators=[PhoneNumberValidator()],
)
address = models.TextField(null=True, blank=True)
date_modified = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True)
@@ -88,7 +78,7 @@ class Member(models.Model):
# need to have 'View on site' link in admin app
def get_absolute_url(self):
return reverse("members:member", kwargs={"member_id": self.id})
return reverse("members:member", kwargs={"pk": self.pk})
def clean(self):
if not self.image:
@@ -98,7 +88,7 @@ class Member(models.Model):
try:
user = User.objects.get(username=self.username.lower())
except User.DoesNotExist as e:
logger.info("Username does not exist. Error: %s", e)
logger.info("Username not found. Error: %s", e)
else:
user.first_name = self.firstname
user.save()
@@ -108,35 +98,19 @@ class Member(models.Model):
@property
def image_url(self):
if self.image:
return self.image.url
# return default image
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
return self.image.url if self.image else fet_logo_url
@property
def avatar_url(self):
if self.image:
return self.image["avatar"].url
# return default image
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
return self.image["avatar"].url if self.image else fet_logo_url
@property
def portrait_url(self):
if self.image:
return self.image["portrait"].url
# return default image
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
return self.image["portrait"].url if self.image else fet_logo_url
@property
def thumb_url(self):
if self.image:
return self.image["thumb"].url
# return default image
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
return self.image["thumb"].url if self.image else fet_logo_url
class JobGroup(models.Model):
@@ -145,7 +119,7 @@ class JobGroup(models.Model):
shortterm = models.CharField(max_length=128, unique=True, blank=True)
slug = models.SlugField(unique=True, null=True, blank=True)
description = models.TextField(null=True, blank=True)
description = models.TextField(blank=True, default="")
# Managers
objects = models.Manager()

View File

@@ -1,12 +1,13 @@
from django.urls import path
from . import apps, views
from . import apps
from .views import ActiveMemberListView, JobListView, MemberDetailView, MemberListView
app_name = apps.MembersConfig.name
urlpatterns = [
path("members/", views.index, name="index"),
path("members/<str:role>/", views.members, name="members"),
path("member/<int:member_id>/", views.profile, name="member"),
path("section/<slug:slug>/", views.jobs, name="jobs"),
path("members/", ActiveMemberListView.as_view(), name="index"),
path("members/<str:role>/", MemberListView.as_view(), name="members"),
path("member/<int:pk>/", MemberDetailView.as_view(), name="member"),
path("section/<slug:slug>/", JobListView.as_view(), name="jobs"),
]

View File

@@ -1,14 +1,4 @@
from django.core.validators import RegexValidator, ValidationError
from django.utils.deconstruct import deconstructible
@deconstructible
class PhoneNumberValidator(RegexValidator):
regex = r"^\+?1?\d{9,15}$"
message = (
"Telefonnummer muss in diesem Format +999999999999 eingegeben werden. Bis zu 15 Zahlen "
"sind erlaubt."
)
from django.core.validators import ValidationError
def validate_domainonly_email(value):

View File

@@ -1,74 +1,57 @@
import logging
from django.db.models import F
from django.http import Http404
from django.shortcuts import render
from django.views.generic import ListView
from django.views.generic.detail import DetailView
from .models import JobGroup, JobMember, Member
logger = logging.getLogger(__name__)
class ActiveMemberListView(ListView):
allow_empty = False
model = Member
template_name = "members/members.html"
def get_queryset(self):
return Member.all_members.filter(role=Member.MemberRole.ACTIVE)
def index(request):
members = Member.all_members.filter(role=Member.MemberRole.ACTIVE)
class JobListView(ListView):
allow_empty = False
model = JobMember
template_name = "members/jobs.html"
context = {
"members": members,
}
def get_queryset(self):
return JobMember.active_member.get_all(slug=self.kwargs["slug"]).order_by(
F("job__order").asc(nulls_last=True), "job__name"
)
return render(request, "members/members.html", context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["active_job_group"] = JobGroup.objects.filter(slug=self.kwargs["slug"]).first()
return context
def jobs(request, slug=None):
try:
description = JobGroup.objects.filter(slug=slug).values().first()["description"]
except Exception:
logger.info("Wrong job '%s'", slug)
raise Http404("wrong job")
class MemberListView(ListView):
allow_empty = False
model = Member
template_name = "members/members.html"
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()
def get_queryset(self):
if self.kwargs["role"].capitalize() in [r.label for r in Member.MemberRole]:
return Member.all_members.filter(role=self.kwargs["role"].capitalize()[:1])
context = {
"description": description,
"job_members": job_members,
"active_job_group": active_job_group,
}
return render(request, "members/jobs.html", context)
return Member.all_members.all() if self.kwargs["role"] in "all" else None
def members(request, role=None):
for elem in Member.MemberRole:
if role == elem.label.lower():
members = Member.all_members.filter(role=elem.value)
break
else:
members = Member.all_members.all()
class MemberDetailView(DetailView):
model = Member
template_name = "members/member.html"
context = {
"members": members,
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return render(request, "members/members.html", context)
context["active_jobs"] = JobMember.members.get_active_jobs(self.object.id)
context["inactive_jobs"] = JobMember.members.get_inactive_jobs(self.object.id)
def profile(request, member_id=None):
member = Member.all_members.filter(id=member_id).first()
if not member:
logger.info("Wrong member id '%s'", member_id)
raise Http404("no member")
active_jobs = JobMember.members.get_active_jobs(member_id)
inactive_jobs = JobMember.members.get_inactive_jobs(member_id)
context = {
"member": member,
"active_jobs": active_jobs,
"inactive_jobs": inactive_jobs,
}
return render(request, "members/member.html", context)
return context

View File

@@ -10,8 +10,7 @@ from .models import Event, FetMeeting, FileUpload, News, Post
def make_fetmeeting(self, request, queryset):
qs = self.get_queryset(request).filter(id=request.POST["_selected_action"]).first()
agenda_key = ep_create_new_pad(qs.slug + "-agenda")
if not agenda_key:
if not (agenda_key := ep_create_new_pad(qs.slug + "-agenda")):
self.message_user(
request,
"Das Agenda konnte nicht erstellt werden.",
@@ -19,8 +18,7 @@ def make_fetmeeting(self, request, queryset):
)
return
protocol_key = ep_create_new_pad(qs.slug + "-protocol")
if not protocol_key:
if not (protocol_key := ep_create_new_pad(qs.slug + "-protocol")):
self.message_user(
request,
"Das Protokoll konnte nicht erstellt werden.",
@@ -79,7 +77,7 @@ class PostAdmin(admin.ModelAdmin):
)
def save_model(self, request, obj, form, change):
if obj.author is None:
if not obj.author:
obj.author = request.user
super().save_model(request, obj, form, change)

13
fet2020/posts/choices.py Normal file
View File

@@ -0,0 +1,13 @@
from django.db import models
class PostType(models.TextChoices):
NEWS = "N", "News"
EVENT = "E", "Event"
FETMEETING = "F", "FetMeeting"
class Status(models.TextChoices):
DRAFT = "10", "DRAFT"
ONLY_INTERN = "15", "ONLY_INTERN"
PUBLIC = "20", "PUBLIC"

View File

@@ -2,7 +2,8 @@ import datetime
from django.db import models
from django.db.models import Case, Q, When
from django.utils import timezone
from .choices import PostType, Status
class PublishedManager(models.Manager):
@@ -10,33 +11,36 @@ class PublishedManager(models.Manager):
"""
publish all posts with status 'PUBLIC'
"""
qs = self.get_queryset().filter(status="20") if public else self.get_queryset()
return qs
return self.get_queryset().filter(status=Status.PUBLIC) if public else self.get_queryset()
def published_all(self, public=True):
"""
publish all posts with status 'PUBLIC' and 'ONLY_INTERN'
"""
qs = self.get_queryset().filter(~Q(status="10")) if public else self.get_queryset()
return qs
return (
self.get_queryset().filter(~Q(status=Status.DRAFT)) if public else self.get_queryset()
)
class PostManager(PublishedManager, models.Manager):
def get_queryset(self):
qs = super().get_queryset()
qs = qs.annotate(
date=Case(
When(post_type="N", then="public_date"),
When(post_type="E", then="event_start__date"),
When(post_type="F", then="event_start__date"),
),
qs = (
super()
.get_queryset()
.annotate(
date=Case(
When(post_type=PostType.NEWS, then="public_date"),
When(post_type=PostType.EVENT, then="event_start__date"),
When(post_type=PostType.FETMEETING, then="event_start__date"),
),
)
)
return qs.order_by("-date", "-id")
def date_sorted_list(self, public=True):
def date_sorted(self, public=True):
return self.published(public)
def date_filtered_list(
def date_filter(
self,
public=True,
year=None,
@@ -46,7 +50,7 @@ class PostManager(PublishedManager, models.Manager):
qs_filter = Q()
if fet_meeting_only:
qs_filter &= Q(post_type="F")
qs_filter &= Q(post_type=PostType.FETMEETING)
if year:
qs_filter &= Q(date__year=year)
@@ -63,16 +67,16 @@ class ArticleManager(PublishedManager, models.Manager):
"""
def get_queryset(self):
qs = super().get_queryset().filter(Q(post_type="N") | Q(post_type="E"))
qs = super().get_queryset().filter(Q(post_type=PostType.NEWS) | Q(post_type=PostType.EVENT))
qs = qs.annotate(
date=Case(
When(post_type="N", then="public_date"),
When(post_type="E", then="event_start__date"),
When(post_type=PostType.NEWS, then="public_date"),
When(post_type=PostType.EVENT, then="event_start__date"),
),
)
return qs.order_by("-date", "-id")
def date_sorted_list(self, public=True):
def date_sorted(self, public=True):
return self.published(public)
def pinned(self, public=True):
@@ -95,10 +99,12 @@ class ArticleManager(PublishedManager, models.Manager):
return (
self.published(public)
.filter(is_pinned=True)
.filter(
(Q(post_type="N") & Q(public_date__gt=post_date))
| (Q(post_type="E") & Q(event_end__date__gt=event_date)),
Q(is_pinned=True)
& (
(Q(post_type=PostType.NEWS) & Q(public_date__gt=post_date))
| (Q(post_type=PostType.EVENT) & Q(event_end__date__gt=event_date))
)
)
.first()
)
@@ -110,10 +116,10 @@ class NewsManager(PublishedManager, models.Manager):
"""
def get_queryset(self):
qs = super().get_queryset().filter(post_type="N")
qs = super().get_queryset().filter(post_type=PostType.NEWS)
qs = qs.annotate(
date=Case(
When(post_type="N", then="public_date"),
When(post_type=PostType.NEWS, then="public_date"),
),
)
return qs.order_by("-date")
@@ -125,17 +131,21 @@ class AllEventManager(PublishedManager, models.Manager):
"""
def get_queryset(self):
qs = super().get_queryset().filter(Q(post_type="E") | Q(post_type="F"))
qs = (
super()
.get_queryset()
.filter(Q(post_type=PostType.EVENT) | Q(post_type=PostType.FETMEETING))
)
qs = qs.annotate(
date=Case(
When(post_type="E", then="event_start__date"),
When(post_type="F", then="event_start__date"),
When(post_type=PostType.EVENT, then="event_start__date"),
When(post_type=PostType.FETMEETING, then="event_start__date"),
),
)
return qs.order_by("-date")
def future_events(self, public=True):
date_today = timezone.now()
date_today = datetime.date.today()
qs = self.published(public).filter(event_start__gt=date_today)
return qs.reverse()
@@ -147,21 +157,21 @@ class EventManager(PublishedManager, models.Manager):
"""
def get_queryset(self):
qs = super().get_queryset().filter(post_type="E")
qs = super().get_queryset().filter(post_type=PostType.EVENT)
qs = qs.annotate(
date=Case(
When(post_type="E", then="event_start__date"),
When(post_type=PostType.EVENT, then="event_start__date"),
),
)
return qs.order_by("-date")
def future_events(self, public=True):
date_today = timezone.now()
date_today = datetime.date.today()
qs = self.published(public).filter(event_start__gt=date_today)
return qs.reverse()
def past_events(self, public=True):
date_today = timezone.now()
date_today = datetime.date.today()
qs = self.published(public).filter(event_start__lt=date_today)
return qs
@@ -172,20 +182,20 @@ class FetMeetingManager(PublishedManager, models.Manager):
"""
def get_queryset(self):
qs = super().get_queryset().filter(post_type="F")
qs = super().get_queryset().filter(post_type=PostType.FETMEETING)
qs = qs.annotate(
date=Case(
When(post_type="F", then="event_start__date"),
When(post_type=PostType.FETMEETING, then="event_start__date"),
),
)
return qs.order_by("-date")
def future_events(self):
date_today = timezone.now()
date_today = datetime.date.today()
qs = self.published().filter(event_start__gt=date_today)
return qs.reverse()
def past_events(self):
date_today = timezone.now()
date_today = datetime.date.today()
qs = self.published().filter(event_start__lt=date_today)
return qs

View File

@@ -8,18 +8,12 @@ from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
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 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,
@@ -30,44 +24,21 @@ from .managers import (
)
logger = logging.getLogger(__name__)
request_logger = logging.getLogger("django.request")
def create_pad_for_post(slug, item="agenda"):
"""
Create a Etherpad pad.
Return a Etherpad key.
"""
logger.info(f"Pad-Type: {item}")
logger.info("Pad-Type: %s", item)
pad_id = slug + "-" + item
if ep_create_new_pad(pad_id):
# 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(f"Template is set. Template: {page.title}")
if not ep_create_new_pad(pad_id):
return ""
return pad_id
# 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 "#"
class Category(models.Model):
# Titel des Posts
title = models.CharField(max_length=200)
subtitle = models.CharField(max_length=500, null=True, blank=True)
# Slug = Text Basierter url bestandteil zb Fetsitzung 22.1.2020 --> fetsitzung_22_1_2020 für Url
slug = models.SlugField(unique=True, null=True, blank=True)
# Ein Haupt Bild für den Post
image = models.ImageField(null=True, blank=True)
tags = TaggableManager(blank=True)
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
return pad_id
class Post(models.Model):
@@ -75,13 +46,12 @@ class Post(models.Model):
legacy_id = models.IntegerField(null=True, blank=True)
title = models.CharField(verbose_name="Titel", max_length=200)
subtitle = models.CharField(max_length=500, null=True, blank=True)
subtitle = models.CharField(max_length=500, blank=True, default="")
tags = TaggableManager(blank=True)
# Slug = Text Basierter url bestandteil zb Fetsitzung 22.1.2020 --> fetsitzung_22_1_2020 für Url
slug = models.SlugField(unique=True, blank=True)
body = models.TextField(null=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)
@@ -92,19 +62,10 @@ class Post(models.Model):
default=timezone.now,
)
__choices = [("N", _("News")), ("E", _("Event")), ("F", _("FetMeeting"))]
post_type = models.CharField(max_length=1, choices=__choices, editable=True)
class Status(models.TextChoices):
DRAFT = "10", _("DRAFT")
ONLY_INTERN = "15", _("ONLY_INTERN")
PUBLIC = "20", _("PUBLIC")
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,
verbose_name="Status", max_length=2, choices=Status.choices, default=Status.DRAFT
)
# post is pinned at main page
@@ -113,22 +74,18 @@ class Post(models.Model):
# 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, 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, null=True, blank=True)
agenda_key = models.CharField(max_length=200, null=True, blank=True)
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)
# useless (-_-)
legacy_rubrik_id = models.IntegerField(null=True, blank=True)
imported_from = models.CharField(max_length=200, null=True, blank=True)
# Managers
objects = PostManager()
articles = ArticleManager()
@@ -153,48 +110,45 @@ class Post(models.Model):
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:
"Agenda HTML from Etherpad Pad"
if self.agenda_key in [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:
"Protocol HTML from Etherpad Pad"
if self.protocol_key in [None, "#"]:
if self.protocol_key in self._possible_empty_key_value:
return None
return ep_get_html(self.protocol_key)
@agenda_html.setter
def agenda_html(self, value: str) -> str | None:
if self.agenda_key is None:
self.create_agenda_key()
if value is None or self.agenda_key in [None, "#"]:
return None
ep_set_html(self.agenda_key, value)
request_logger.info("Set agenda etherpad. Post: %s. Key: %s", self.slug, self.agenda_key)
return value
@protocol_html.setter
def protocol_html(self, value: str) -> str | None:
if self.protocol_key is None:
if self.protocol_key in self._possible_empty_key_value:
self.create_protocol_key()
if value is None or self.protocol_key in [None, "#"]:
if not value or not self.protocol_key:
return None
ep_set_html(self.protocol_key, value)
request_logger.info(
"Set protocol etherpad. Post: %s. Key: %s",
self.slug,
self.protocol_key,
)
logger.info("Set protocol etherpad '%s' for post '%s'.", self.protocol_key, self.slug)
return value
_agenda_filename = None
@@ -202,15 +156,15 @@ class Post(models.Model):
@property
def agenda_url(self) -> str | None:
if self.has_agenda is not True:
if not self.has_agenda:
self._agenda_url = None
self._agenda_filename = None
return self._agenda_url
if self._agenda_url is not None:
if self._agenda_url:
return self._agenda_url
if (url := ep_get_url(self.agenda_key)) not in [None, "#"]:
if url := ep_get_url(self.agenda_key):
self._agenda_url = url
self._agenda_filename = self.slug + "-agenda.pdf"
else:
@@ -221,7 +175,7 @@ class Post(models.Model):
@property
def agenda_filename(self) -> str | None:
if self._agenda_filename is not None:
if self._agenda_filename:
return self._agenda_filename
if self.has_agenda and self.agenda_url:
@@ -234,15 +188,15 @@ class Post(models.Model):
@property
def protocol_url(self) -> str | None:
if self.has_protocol is not True:
if not self.has_protocol:
self._protocol_url = None
self._protocol_filename = None
return self._protocol_url
if self._protocol_url is not None:
if self._protocol_url:
return self._protocol_url
if (url := ep_get_url(self.protocol_key)) not in [None, "#"]:
if url := ep_get_url(self.protocol_key):
self._protocol_url = url
self._protocol_filename = self.slug + "-protokoll.pdf"
else:
@@ -253,7 +207,7 @@ class Post(models.Model):
@property
def protocol_filename(self) -> str | None:
if self._protocol_filename is not None:
if self._protocol_filename:
return self._protocol_filename
if self.has_protocol and self.protocol_url:
@@ -290,14 +244,9 @@ class Post(models.Model):
@property
def imageurl(self) -> str:
"""
returns the url to the image
"""
if self.image:
return self.image.url
# return default image
return settings.STATIC_URL + "img/FET-Logo-2014-quadrat.png"
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:
@@ -307,7 +256,7 @@ class Post(models.Model):
@property
def published(self):
return self.status == self.Status.PUBLIC
return self.status == Status.PUBLIC
class News(Post):
@@ -372,14 +321,17 @@ class FetMeeting(Event):
if not self.slug:
self.slug = self.__get_slug()
if ep_pad_exists(self.agenda_key) is not True or self.slug not in self.agenda_key:
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 not in [None, "#"]:
if self.agenda_key:
self.has_agenda = True
if ep_pad_exists(self.protocol_key) is not True or self.slug not in self.protocol_key:
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 not in [None, "#"]:
if self.protocol_key:
self.has_protocol = True
if not self.post_type:
@@ -393,7 +345,7 @@ class FetMeeting(Event):
self.event_end = self.event_start + timedelta(hours=2)
# set FET Meeting always public
self.status = self.Status.PUBLIC
self.status = Status.PUBLIC
super().save(*args, **kwargs)

View File

@@ -17,7 +17,7 @@ class PostIndex(indexes.SearchIndex, indexes.Indexable):
return Post
def index_queryset(self, using=None):
return self.get_model().objects.date_sorted_list(public=False)
return self.get_model().objects.date_sorted(public=False)
def prepare_date(self, obj):
if obj.post_type == "N":

View File

@@ -39,7 +39,7 @@ class PostTestCase(TestCase):
post.event_start = timezone.now() - timedelta(1)
post.save()
post_list = Post.objects.date_sorted_list(public=False)
post_list = Post.objects.date_sorted(public=False)
self.assertEqual(post_list[0].title, "zukünftiges FET Fest")
self.assertEqual(post_list[1].title, "Informationen zur ÖH Wahl")
self.assertEqual(post_list[2].title, "vergangenes FET Fest")

View File

@@ -15,6 +15,7 @@ from documents.etherpadlib import add_ep_cookie
from fet2020.utils import add_log_action
from members.models import Member
from .choices import PostType
from .forms import (
EventUpdateForm,
FetMeetingCreateForm,
@@ -35,7 +36,7 @@ def index(request):
if request.method == "POST":
form = PostSearchForm(request.POST)
if form.is_valid():
posts = Post.objects.date_filtered_list(
posts = Post.objects.date_filter(
public_only,
form.cleaned_data["year"],
form.cleaned_data["month"],
@@ -44,7 +45,7 @@ def index(request):
else:
# If no input, all posts are shown.
form = PostSearchForm()
posts = Post.objects.date_filtered_list(public_only)
posts = Post.objects.date_filter(public_only)
context = {
"formset": form,
@@ -92,7 +93,7 @@ class PostDetailView(DetailView):
try:
response = add_ep_cookie(request, response)
except Exception as e:
logger.info("Etherpad Server doesn't work. Error: %s", e)
logger.info("Etherpad Server not working. Error: %s", e)
return response
@@ -134,9 +135,9 @@ class PostDetailView(DetailView):
def get_template_names(self):
template_name = "posts/news/detail.html"
if self.object.post_type == "E":
if self.object.post_type == PostType.EVENT:
template_name = "posts/event/detail.html"
elif self.object.post_type == "F":
elif self.object.post_type == PostType.FETMEETING:
template_name = "posts/fetmeeting/detail.html"
return template_name
@@ -145,7 +146,7 @@ class PostDetailView(DetailView):
"""
Helper function for getting previous post
"""
posts = Post.objects.date_sorted_list(self.public_only).filter(
posts = Post.objects.date_sorted(self.public_only).filter(
post_type=self.object.post_type,
)
qs = posts.filter(
@@ -165,7 +166,7 @@ class PostDetailView(DetailView):
Helper function for getting next post
"""
posts = (
Post.objects.date_sorted_list(self.public_only)
Post.objects.date_sorted(self.public_only)
.filter(post_type=self.object.post_type)
.reverse()
)
@@ -187,9 +188,9 @@ class PostUpdateView(LoginRequiredMixin, UpdateView):
def form_valid(self, form):
model = "news"
if self.object.post_type == "E":
if self.object.post_type == PostType.EVENT:
model = "event"
elif self.object.post_type == "F":
elif self.object.post_type == PostType.FETMEETING:
model = "fetmeeting"
add_log_action(self.request, form, "posts", model, False)
@@ -197,9 +198,9 @@ class PostUpdateView(LoginRequiredMixin, UpdateView):
def get_form_class(self):
form_class = NewsUpdateForm
if self.object.post_type == "E":
if self.object.post_type == PostType.EVENT:
form_class = EventUpdateForm
elif self.object.post_type == "F":
elif self.object.post_type == PostType.FETMEETING:
form_class = FetMeetingUpdateForm
return form_class
@@ -229,9 +230,9 @@ class PostUpdateView(LoginRequiredMixin, UpdateView):
def get_template_names(self):
template_name = "posts/news/update.html"
if self.object.post_type == "E":
if self.object.post_type == PostType.EVENT:
template_name = "posts/event/update.html"
elif self.object.post_type == "F":
elif self.object.post_type == PostType.FETMEETING:
template_name = "posts/fetmeeting/update.html"
return template_name
@@ -307,10 +308,8 @@ def show_pdf(request, html, filename):
idx = html.index("<body>")
html = html[:idx] + rendered + html[idx:]
pdf = render_to_pdf(html)
if not pdf:
raise Http404("can't create pdf file")
if not (pdf := render_to_pdf(html)):
raise Http404("can't create pdf file.")
response = HttpResponse(pdf, content_type="application/pdf")

View File

@@ -13,7 +13,7 @@
<h1 class="page-title">Fotos</h1>
<section class="text-gray-800 dark:text-gray-200 my-2 sm:my-4">
{% if album.status == album.DRAFT %}
{% if album.status == album.Status.DRAFT %}
<h2 class="text-gray-900 dark:text-gray-100 text-lg font-medium tracking-wide"><i class="fa-solid fa-eye-slash text-gray-600 dark:text-gray-300 mr-2" title="Album ist nicht öffentlich!"></i>{{ album.title }}</h2>
{% else %}
<h2 class="text-gray-900 dark:text-gray-100 text-lg font-medium tracking-wide">{{ album.title }}</h2>
@@ -34,7 +34,7 @@
{% endif %}
</section>
{% if request.user.is_authenticated %}
{% if request.user.is_authenticated and album.id %}
<a href="{% url 'admin:gallery_album_change' album.id %}" class="page-subtitle block btn-small btn-primary w-full sm:w-max sm:mr-0 sm:ml-auto">
<i class="fa-regular fa-folder-open mr-1"></i>Album bearbeiten
</a>
@@ -52,7 +52,7 @@
</div>
<div id="links" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 justify-items-center gap-3">
{% for image in images %}
{% for image in object.images %}
<a id="{{ image.title }}" href="{{ image.image_url }}" title="{{ image.title }}" class="block max-w-xs sm:max-w-none">
{% if image.thumb_url %}
<img src="{{ image.thumb_url }}" alt="{{ image.title }}" class="rounded-sm">

View File

@@ -15,15 +15,15 @@
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 justify-items-center gap-4">
{% for album in albums %}
{% if request.user.is_authenticated and album.status == album.DRAFT %}
{% if request.user.is_authenticated and album.status == album.Status.DRAFT %}
<a href="{% url 'gallery:album_draft' album.slug %}" class="block max-w-xs sm:max-w-none">
<img src="{{ album.thumbnail }}" class="rounded">
<img src="{{ album.get_album_thumbnail }}" class="rounded">
<h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2>
<h3 class="px-2 text-sm text-proprietary dark:text-sky-400 font-normal"><i class="fa-solid fa-calendar-day mr-1"></i>{{ album.event_date }}</h3>
</a>
{% elif album.status == album.PUBLIC %}
{% elif album.status == album.Status.PUBLIC %}
<a href="{% url 'gallery:album' album.slug %}" class="block max-w-xs sm:max-w-none">
<img src="{{ album.thumbnail }}" class="rounded">
<img src="{{ album.get_album_thumbnail }}" class="rounded">
<h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2>
<h3 class="px-2 text-sm text-proprietary dark:text-sky-400 font-normal"><i class="fa-solid fa-calendar-day mr-1"></i>{{ album.event_date }}</h3>
</a>

View File

@@ -106,13 +106,13 @@
{% endblock member_content %}
{% endif %}
{% if members %}
{% if member_list %}
<!-- show all, active or pension members -->
{% block members_content %}
{% endblock members_content %}
{% endif %}
{% if job_members %}
{% if jobmember_list %}
<!-- show job lists in a job group -->
{% block jobs_content %}
{% endblock jobs_content %}

View File

@@ -5,15 +5,15 @@
{% block jobs_content %}
<div class="db-page-content">
<h2>{{ active_job_group.name }}</h2>
{% if description %}
{{ description|safe }}
{% if active_job_group.description %}
{{ active_job_group.description|safe }}
{% endif %}
</div>
<div>
{% regroup job_members by job.name as all_jobmem_list %}
{% regroup jobmember_list by job.name as jobmem_list %}
{% for jobmem in all_jobmem_list %}
{% for jobmem in jobmem_list %}
<article id="{{ jobmem.list.0.job.slug }}" class="members-article">
<a href="#{{ jobmem.list.0.job.slug }}" class="title">
<i class="fa-solid fa-link"></i>

View File

@@ -14,7 +14,7 @@
<article class="members-article">
<div class="members-listing">
{% for member in members %}
{% for member in member_list %}
<figure>
<a href="{{ member.get_absolute_url }}">
<img loading="lazy" src="{{ member.thumb_url }}" alt="Portraitfoto von {{ member.firstname }}" class="w-36 h-36 bg-white">