This commit is contained in:
2023-01-12 17:16:38 +01:00
89 changed files with 1540 additions and 821 deletions

View File

@@ -59,3 +59,17 @@ python3 fet2020/manage.py collectstatic
<code> <code>
python3 fet2020/manage.py check --deploy python3 fet2020/manage.py check --deploy
</code> </code>
Code Formatting (zuerst isort und danach black anwenden):
```bash
isort **/*.py
```
```bash
black **/*.py
```
```bash
isort **/templatetags/*.py
```
```bash
black **/templatetags/*.py
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,12 @@
import logging import logging
import ldap3 from ldap3 import HASHED_SALTED_SHA, MODIFY_REPLACE, Connection, Server
from ldap3.core.exceptions import LDAPBindError from ldap3.core.exceptions import LDAPBindError
from ldap3.utils.hashed import hashed
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
host = "ldap://juri.fet.htu.tuwien.ac.at"
port = 389
def authentication(username, password): def authentication(username, password):
@@ -11,20 +14,38 @@ def authentication(username, password):
if password is None or password.strip() == "": if password is None or password.strip() == "":
return None return None
server_uri = "ldap://juri.fet.htu.tuwien.ac.at" server = Server(host, port=port, use_ssl=True)
server = ldap3.Server(server_uri, port=389, use_ssl=True)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at" userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try: try:
conn = ldap3.Connection(server, user=userdn, password=password, auto_bind=True) c = Connection(server, user=userdn, password=password, auto_bind=True)
conn.search("dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at", "(objectclass=person)") if c.extend.standard.who_am_i():
for user in sorted(conn.entries): return username
if f"DN: uid={username}" in str(user):
return username
except LDAPBindError as e: except LDAPBindError as e:
logger.info(f"Username does not exist. Error: {e}") logger.info(f"LDAP Bind error. Error: {e}")
except Exception as e: except Exception as e:
logger.info(f"Connection to server lost. Error: {e}") logger.info(f"Auth exception. Error: {e}")
logger.info(f"This username has been typed: '{username}'") logger.info(f"This username has been typed: '{username}'")
return None return None
def change_password(username, old_password, new_password):
server = Server(host, port=port, use_ssl=True)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=old_password, auto_bind=True)
hashed_password = hashed(HASHED_SALTED_SHA, new_password)
c.modify(userdn, {"userPassword": [(MODIFY_REPLACE, [hashed_password])]})
return username
except LDAPBindError as e:
logger.info(f"LDAP Bind error. Error: {e}")
except Exception as e:
logger.info(f"Auth change-password exception. Error: {e}")
return None

View File

@@ -1,6 +1,49 @@
from django import forms from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.contrib.auth.models import User
from django.core.validators import ValidationError
from .authentications import authentication, change_password
class LoginForm(forms.Form): class LoginForm(AuthenticationForm):
username = forms.CharField() def clean(self):
password = forms.CharField(label="Passwort", widget=forms.PasswordInput()) username = self.cleaned_data.get("username")
password = self.cleaned_data.get("password")
if username is not None and password:
auth_user = authentication(username, password)
if auth_user:
try:
self.user_cache = User.objects.get(username=auth_user.lower())
except User.DoesNotExist:
self.user_cache = User.objects.create_user(auth_user.lower())
else:
raise self.get_invalid_login_error()
if self.user_cache is None:
raise self.get_invalid_login_error()
else:
self.confirm_login_allowed(self.user_cache)
return self.cleaned_data
class LdapPasswordChangeForm(PasswordChangeForm):
def clean_old_password(self):
old_password = self.cleaned_data["old_password"]
if not authentication(self.user.username, old_password):
raise ValidationError(
self.error_messages["password_incorrect"],
code="password_incorrect",
)
return old_password
def clean(self):
old_password = self.cleaned_data["old_password"]
new_password = self.cleaned_data["new_password1"]
if not change_password(self.user, old_password, new_password):
raise ValidationError("Passwort im LDAP ändern funktioniert nicht.")
def save(self):
return self.user

View File

@@ -1,11 +1,20 @@
from django.urls import path from django.urls import path
from . import apps from . import apps, views
from . import views
app_name = apps.AuthenticationsConfig.name app_name = apps.AuthenticationsConfig.name
urlpatterns = [ urlpatterns = [
path("login/", views.loginPage, name="login"), path("login/", views.AuthLoginView.as_view(), name="login"),
path("logout/", views.logoutUser, name="logout"), path("logout/", views.logoutUser, name="logout"),
path(
"change-password/",
views.LdapPasswordChangeView.as_view(),
name="password_change",
),
path(
"change-password/done/",
views.LdapPasswordChangeDoneView.as_view(),
name="password_change_done",
),
] ]

View File

@@ -1,46 +1,22 @@
from django.contrib import messages from django.contrib.auth import logout
from django.contrib.auth import login, logout from django.contrib.auth.views import (
from django.contrib.auth.models import User LoginView,
from django.shortcuts import render, redirect PasswordChangeDoneView,
PasswordChangeView,
)
from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy
from documents.etherpadlib import del_ep_cookie from documents.etherpadlib import del_ep_cookie
from .authentications import authentication
from .decorators import unauthenticated_user, authenticated_user from .decorators import authenticated_user
from .forms import LoginForm from .forms import LdapPasswordChangeForm, LoginForm
@unauthenticated_user class AuthLoginView(LoginView):
def loginPage(request): authentication_form = LoginForm
if request.method == "POST": redirect_authenticated_user = True
username = request.POST.get("username").lower() template_name = "authentications/login.html"
password = request.POST.get("password")
auth_user = authentication(username, password)
if auth_user:
try:
user = User.objects.get(username=auth_user.lower())
except User.DoesNotExist:
user = User.objects.create_user(auth_user.lower())
login(request, user)
try:
return redirect(request.GET.get("next"))
except:
return redirect("home")
else:
messages.error(
request,
"Anmeldung nicht erfolgreich. Bitte überprüfe Benutzername und Passwort.",
)
form = LoginForm()
context = {
"form": form,
}
return render(request, "authentications/login.html", context)
@authenticated_user @authenticated_user
@@ -55,3 +31,13 @@ def logoutUser(request):
response = del_ep_cookie(request, response) response = del_ep_cookie(request, response)
return response return response
class LdapPasswordChangeView(PasswordChangeView):
form_class = LdapPasswordChangeForm
success_url = reverse_lazy("authentications:password_change_done")
template_name = "authentications/change_password.html"
class LdapPasswordChangeDoneView(PasswordChangeDoneView):
template_name = "authentications/change_password_done.html"

View File

@@ -5,7 +5,6 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
logger = logging.getLogger("blackboard") logger = logging.getLogger("blackboard")

View File

@@ -1,7 +1,6 @@
from django.urls import path from django.urls import path
from . import apps from . import apps, views
from . import views
app_name = apps.BlackboardConfig.name app_name = apps.BlackboardConfig.name

View File

@@ -1,5 +1,4 @@
import taggit.admin import taggit.admin
from django.contrib import admin, auth from django.contrib import admin, auth
from django.contrib.flatpages.admin import FlatPageAdmin from django.contrib.flatpages.admin import FlatPageAdmin
from django.contrib.flatpages.models import FlatPage from django.contrib.flatpages.models import FlatPage
@@ -37,13 +36,13 @@ class CustomFlatPageAdmin(FlatPageAdmin):
# Set the ordering of models in Admin Dashboard # Set the ordering of models in Admin Dashboard
def get_app_list(self, request): def get_app_list(self, request, app_label=None):
""" """
Return a sorted list of all the installed apps that have been Return a sorted list of all the installed apps that have been
registered in this site. registered in this site.
""" """
# Retrieve the original list # Retrieve the original list
app_dict = self._build_app_dict(request) app_dict = self._build_app_dict(request, app_label)
app_list = sorted(app_dict.values(), key=lambda x: x["name"].lower()) app_list = sorted(app_dict.values(), key=lambda x: x["name"].lower())
# Sort the models customably within each app. # Sort the models customably within each app.

View File

@@ -1,5 +1,4 @@
from ckeditor_uploader.widgets import CKEditorUploadingWidget from ckeditor_uploader.widgets import CKEditorUploadingWidget
from django import forms from django import forms
from .models import CustomFlatPage from .models import CustomFlatPage

View File

View File

@@ -0,0 +1,10 @@
from django import template
from fet2020 import __version__, build
register = template.Library()
@register.simple_tag
def version():
return f"Version {__version__} Build {build}"

View File

@@ -3,10 +3,8 @@ import os
from contextlib import contextmanager from contextlib import contextmanager
from urllib.parse import urljoin from urllib.parse import urljoin
from etherpad_lite import EtherpadLiteClient, EtherpadException
from django.conf import settings from django.conf import settings
from etherpad_lite import EtherpadException, EtherpadLiteClient
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -4,6 +4,7 @@ from django.conf import settings
from django.utils import timezone from django.utils import timezone
from authentications.decorators import ep_authenticated_user from authentications.decorators import ep_authenticated_user
from .api import get_ep_client from .api import get_ep_client

View File

@@ -0,0 +1,7 @@
from django.utils.version import get_version
VERSION = (1, 0, 0, "final", 0)
BUILD = 0
__version__ = get_version(VERSION)
build = BUILD

View File

@@ -1,7 +1,8 @@
import environ
import os import os
from urllib.parse import urljoin from urllib.parse import urljoin
import environ
env = environ.Env( env = environ.Env(
# set casting, default value # set casting, default value
DEBUG=(bool, True), DEBUG=(bool, True),
@@ -66,6 +67,7 @@ AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", "django.contrib.auth.backends.ModelBackend",
] ]
LOGIN_REDIRECT_URL = "home"
LOGIN_URL = "/auth/login" LOGIN_URL = "/auth/login"
@@ -316,7 +318,7 @@ CKEDITOR_CONFIGS = {
"-", "-",
"Blockquote", "Blockquote",
"CreateDiv", "CreateDiv",
], ],
}, },
"/", "/",
{ {
@@ -326,15 +328,9 @@ CKEDITOR_CONFIGS = {
"JustifyCenter", "JustifyCenter",
"JustifyRight", "JustifyRight",
"JustifyBlock", "JustifyBlock",
], ],
},
{ "name": "links",
"items": [
"Link",
"Unlink",
"Anchor"
]
}, },
{"name": "links", "items": ["Link", "Unlink", "Anchor"]},
], ],
}, },
} }

View File

@@ -1,23 +1,22 @@
from rest_framework import routers
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin, flatpages from django.contrib import admin, flatpages
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
from django.urls import include, path, re_path from django.urls import include, path, re_path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from rest_framework import routers
from posts.viewsets import PostViewSet
from members.viewsets import ( from members.viewsets import (
MemberViewSet,
JobViewSet,
JobGroupViewSet, JobGroupViewSet,
JobMemberViewSet, JobMemberViewSet,
JobViewSet,
MemberViewSet,
) )
from posts.viewsets import PostViewSet
from . import views from . import views
from .sitemaps import sitemaps from .sitemaps import sitemaps
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r"posts", PostViewSet) router.register(r"posts", PostViewSet)
router.register(r"members", MemberViewSet) router.register(r"members", MemberViewSet)

View File

@@ -25,5 +25,6 @@ def add_log_action(request, form, app_label, model, add=True):
change_message=change_message, change_message=change_message,
) )
def create_random_id(): def create_random_id():
return str(uuid.uuid4())[:8] return str(uuid.uuid4())[:8]

View File

@@ -2,19 +2,11 @@ from collections import deque
from django.shortcuts import render from django.shortcuts import render
from posts.models import Post, FetMeeting, Event from posts.models import Event, FetMeeting, Post
def index(request): def index(request):
posts = deque(Post.articles.date_sorted_list()) posts = deque(Post.articles.date_sorted_list())
posts_for_tags = deque(Post.objects.get_last_months_posts())
def get_tags(lst):
for p in lst:
for t in list(p.tags.names()):
yield "#" + t
t = set(t for t in get_tags(posts_for_tags))
# set the pinned post # set the pinned post
pinned_post = Post.articles.pinned() pinned_post = Post.articles.pinned()
@@ -37,7 +29,6 @@ def index(request):
"featured_post": pinned_post, "featured_post": pinned_post,
"featured_event": featured_event, "featured_event": featured_event,
"featured_meeting": featured_meeting, "featured_meeting": featured_meeting,
"tags_list": "&nbsp;".join(t),
} }
return render(request, "home.html", context) return render(request, "home.html", context)

View File

@@ -1,6 +1,5 @@
from haystack import indexes
from django.utils import timezone from django.utils import timezone
from haystack import indexes
from .models import Album from .models import Album

View File

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

View File

@@ -1,9 +1,8 @@
import logging import logging
import os import os
from PIL import Image, ExifTags, ImageOps
from django.conf import settings from django.conf import settings
from PIL import ExifTags, Image, ImageOps
gallery_path = settings.GALLERY["path"] gallery_path = settings.GALLERY["path"]
gallery_thumb_path = settings.GALLERY["thumb_path"] gallery_thumb_path = settings.GALLERY["thumb_path"]

View File

@@ -7,6 +7,7 @@ from django.shortcuts import render
from django.utils.text import slugify from django.utils.text import slugify
from authentications.decorators import authenticated_user from authentications.decorators import authenticated_user
from .models import Album from .models import Album
from .utils import create_thumbs, get_folder_list from .utils import create_thumbs, get_folder_list

View File

@@ -2,17 +2,17 @@ from django.contrib import admin
from django.db.models import F from django.db.models import F
from .forms import ( from .forms import (
TopicGroupAdminForm,
TopicAdminForm,
AttachmentAdminForm, AttachmentAdminForm,
EtherpadAdminForm,
FileUploadAdminForm,
TopicInlineForm,
AttachmentInlineForm, AttachmentInlineForm,
EtherpadAdminForm,
EtherpadInlineForm, EtherpadInlineForm,
FileUploadAdminForm,
FileUploadInlineForm, FileUploadInlineForm,
TopicAdminForm,
TopicGroupAdminForm,
TopicInlineForm,
) )
from .models import TopicGroup, Topic, Attachment, Etherpad, FileUpload from .models import Attachment, Etherpad, FileUpload, Topic, TopicGroup
class TopicInline(admin.TabularInline): class TopicInline(admin.TabularInline):

View File

@@ -1,11 +1,11 @@
from ckeditor.widgets import CKEditorWidget from ckeditor.widgets import CKEditorWidget
from ckeditor_uploader.widgets import CKEditorUploadingWidget from ckeditor_uploader.widgets import CKEditorUploadingWidget
from django import forms from django import forms
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from tasks.models import Task, TaskList from tasks.models import Task, TaskList
from .models import TopicGroup, Topic, Attachment, Etherpad, FileUpload
from .models import Attachment, Etherpad, FileUpload, Topic, TopicGroup
class DateInput(forms.DateInput): class DateInput(forms.DateInput):

View File

@@ -1,7 +1,6 @@
from django.urls import include, path from django.urls import include, path
from . import apps from . import apps, views
from . import views
from .views import ( from .views import (
AttachmentCreateView, AttachmentCreateView,
AttachmentDetailView, AttachmentDetailView,
@@ -14,7 +13,6 @@ from .views import (
TopicUpdateView, TopicUpdateView,
) )
app_name = apps.InternConfig.name app_name = apps.InternConfig.name
attachment_urlpatterns = [ attachment_urlpatterns = [
@@ -26,17 +24,17 @@ attachment_urlpatterns = [
path( path(
"update/", "update/",
AttachmentUpdateView.as_view(), AttachmentUpdateView.as_view(),
name="attachment-update", name="attachment_update",
), ),
path( path(
"etherpad-create/", "create-etherpad/",
EtherpadCreateView.as_view(), EtherpadCreateView.as_view(),
name="etherpad-create", name="etherpad_create",
), ),
path( path(
"file-create/", "create-file/",
FileUploadCreateView.as_view(), FileUploadCreateView.as_view(),
name="file-create", name="file_create",
), ),
] ]
@@ -45,22 +43,22 @@ topic_urlpatterns = [
path( path(
"update/", "update/",
TopicUpdateView.as_view(), TopicUpdateView.as_view(),
name="topic-update", name="topic_update",
), ),
path( path(
"attachment-create/", "create-attachment/",
AttachmentCreateView.as_view(), AttachmentCreateView.as_view(),
name="attachment-create", name="attachment_create",
), ),
path("create/", TaskCreateView.as_view(), name="task-create"), path("create-task/", TaskCreateView.as_view(), name="task_create"),
] ]
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path( path(
"<slug:topic_group_slug>/topic-create/", "<slug:topic_group_slug>/create-topic/",
TopicCreateView.as_view(), TopicCreateView.as_view(),
name="topic-create", name="topic_create",
), ),
path("<slug:topic_group_slug>/<slug:slug>/", include(topic_urlpatterns)), path("<slug:topic_group_slug>/<slug:slug>/", include(topic_urlpatterns)),
path( path(

View File

@@ -1,18 +1,19 @@
import logging import logging
from collections import deque from collections import deque
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import F, Q from django.db.models import F, Q
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse, reverse_lazy
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy, reverse
from authentications.decorators import authenticated_user from authentications.decorators import authenticated_user
from documents.etherpadlib import add_ep_cookie from documents.etherpadlib import add_ep_cookie
from fet2020.utils import add_log_action from fet2020.utils import add_log_action
from tasks.forms import InternTaskCreateForm from tasks.forms import InternTaskCreateForm
from tasks.models import Task from tasks.models import Task
from .forms import ( from .forms import (
AttachmentCreateForm, AttachmentCreateForm,
AttachmentUpdateForm, AttachmentUpdateForm,
@@ -21,7 +22,7 @@ from .forms import (
TopicCreateForm, TopicCreateForm,
TopicUpdateForm, TopicUpdateForm,
) )
from .models import TopicGroup, Topic, Attachment, Etherpad, FileUpload from .models import Attachment, Etherpad, FileUpload, Topic, TopicGroup
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -1,6 +1,13 @@
from django.contrib import admin from django.contrib import admin
from .forms import JobForm, JobGroupForm, JobInlineForm, MemberForm from .forms import (
ActiveMemberForm,
InactiveMemberForm,
JobForm,
JobGroupForm,
JobInlineForm,
MemberForm,
)
from .models import Job, JobGroup, JobMember, Member from .models import Job, JobGroup, JobMember, Member
@@ -28,8 +35,12 @@ class JobOverviewInline(JobMemberInline):
verbose_name = "Tätigkeit" verbose_name = "Tätigkeit"
verbose_name_plural = "Tätigkeitsübersicht" verbose_name_plural = "Tätigkeitsübersicht"
def get_queryset(self, request):
return JobMember.members.get_all_jobs_sorted()
class ActiveMemberInline(JobMemberInline): class ActiveMemberInline(JobMemberInline):
form = ActiveMemberForm
verbose_name = "Mitglied" verbose_name = "Mitglied"
verbose_name_plural = "Aktive Mitglieder Liste" verbose_name_plural = "Aktive Mitglieder Liste"
@@ -38,6 +49,7 @@ class ActiveMemberInline(JobMemberInline):
class InactiveMemberInline(JobMemberInline): class InactiveMemberInline(JobMemberInline):
form = InactiveMemberForm
verbose_name = "Mitglied" verbose_name = "Mitglied"
verbose_name_plural = "Inaktive Mitglieder Liste" verbose_name_plural = "Inaktive Mitglieder Liste"

View File

@@ -1,8 +1,7 @@
from ckeditor_uploader.widgets import CKEditorUploadingWidget from ckeditor_uploader.widgets import CKEditorUploadingWidget
from django import forms from django import forms
from .models import Member, Job, JobGroup from .models import Job, JobGroup, JobMember, Member
class JobInlineForm(forms.ModelForm): class JobInlineForm(forms.ModelForm):
@@ -11,6 +10,31 @@ class JobInlineForm(forms.ModelForm):
fields = ["name"] fields = ["name"]
class ActiveMemberForm(forms.ModelForm):
class Meta:
fields = "__all__"
model = JobMember
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["member"].queryset = (
self.fields["member"]
.queryset.filter(role="A")
.order_by("firstname", "surname")
)
class InactiveMemberForm(forms.ModelForm):
class Meta:
fields = "__all__"
model = JobMember
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
class MemberForm(forms.ModelForm): class MemberForm(forms.ModelForm):
class Meta: class Meta:
fields = "__all__" fields = "__all__"

View File

@@ -1,7 +1,7 @@
from datetime import timedelta from datetime import timedelta
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import F, Q
from django.utils import timezone from django.utils import timezone
@@ -12,12 +12,16 @@ class ActiveJobMemberManager(models.Manager):
return ( return (
self.get_queryset() self.get_queryset()
.filter(job__job_group__slug=slug) .filter(job__job_group__slug=slug)
.order_by("job__slug", "job_role", "member__firstname") .order_by("job__slug", "job_role", "member__firstname", "member__surname")
) )
def get_queryset(self): def get_queryset(self):
date_today = timezone.now().date() date_today = timezone.now().date()
qs = super().get_queryset().order_by("member__firstname") qs = (
super()
.get_queryset()
.order_by("job_role", "member__firstname", "member__surname", "job_start")
)
return qs.filter( return qs.filter(
Q(member__role="A") & (Q(job_end__gt=date_today) | Q(job_end__isnull=True)) Q(member__role="A") & (Q(job_end__gt=date_today) | Q(job_end__isnull=True))
@@ -36,7 +40,11 @@ class InactiveJobMemberManager(models.Manager):
def get_queryset(self): def get_queryset(self):
date_today = timezone.now().date() date_today = timezone.now().date()
qs = super().get_queryset().order_by("member__firstname") qs = (
super()
.get_queryset()
.order_by("member__firstname", "member__surname", "-job_start")
)
return qs.filter( return qs.filter(
Q(member__role="P") Q(member__role="P")
@@ -50,15 +58,29 @@ class JobMemberManager(models.Manager):
return qs.filter(Q(member__role=role)) 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"
)
return qs
def get_active_jobs(self, member_id): def get_active_jobs(self, member_id):
date_today = timezone.now().date() date_today = timezone.now().date()
qs = self.get_queryset().filter(member__id=member_id).order_by("-job_start") 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 qs.filter(Q(job_end__gt=date_today) | Q(job_end__isnull=True))
def get_inactive_jobs(self, member_id): def get_inactive_jobs(self, member_id):
date_today = timezone.now().date() date_today = timezone.now().date()
qs = self.get_queryset().filter(member__id=member_id).order_by("-job_start") qs = (
self.get_queryset()
.filter(member__id=member_id)
.order_by("-job_start", "job__name")
)
return qs.filter( return qs.filter(
Q(job_end__lt=date_today + timedelta(days=1)) & Q(job_end__isnull=False) Q(job_end__lt=date_today + timedelta(days=1)) & Q(job_end__isnull=False)

View File

@@ -1,12 +1,11 @@
import logging import logging
from easy_thumbnails.fields import ThumbnailerImageField
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import ValidationError, validate_email from django.core.validators import ValidationError, validate_email
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from easy_thumbnails.fields import ThumbnailerImageField
from .managers import ( from .managers import (
ActiveJobMemberManager, ActiveJobMemberManager,
@@ -157,7 +156,11 @@ class Job(models.Model):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse("members:jobs", kwargs={"slug": self.job_group.slug}) + "#" + self.slug return (
reverse("members:jobs", kwargs={"slug": self.job_group.slug})
+ "#"
+ self.slug
)
def clean(self): def clean(self):
if not self.shortterm: if not self.shortterm:

View File

@@ -1,6 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Member, Job, JobGroup, JobMember from .models import Job, JobGroup, JobMember, Member
class MemberSerializer(serializers.HyperlinkedModelSerializer): class MemberSerializer(serializers.HyperlinkedModelSerializer):

View File

View File

@@ -0,0 +1,29 @@
from collections import deque
from django import template
from members.models import JobMember, JobGroup
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)
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

View File

@@ -6,7 +6,6 @@ from django.test import TestCase
from .forms import JobForm, JobGroupForm, MemberForm from .forms import JobForm, JobGroupForm, MemberForm
from .models import Job, JobGroup, Member from .models import Job, JobGroup, Member
image_path = os.path.join(os.path.dirname(__file__), "tests/files/peter.jpg") image_path = os.path.join(os.path.dirname(__file__), "tests/files/peter.jpg")

View File

@@ -1,7 +1,6 @@
from django.urls import path from django.urls import path
from . import apps from . import apps, views
from . import views
app_name = apps.MembersConfig.name app_name = apps.MembersConfig.name

View File

@@ -1,32 +1,17 @@
from collections import deque
import logging import logging
from django.http import Http404 from django.http import Http404
from django.shortcuts import render from django.shortcuts import render
from .models import Member, JobMember, JobGroup from .models import JobGroup, JobMember, Member
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def __get_job_groups():
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)
return job_groups
def index(request): def index(request):
job_groups = __get_job_groups()
members = Member.all_members.filter(role=Member.MemberRole.ACTIVE) members = Member.all_members.filter(role=Member.MemberRole.ACTIVE)
context = { context = {
"job_groups": job_groups,
"members": members, "members": members,
} }
@@ -34,8 +19,6 @@ def index(request):
def jobs(request, slug=None): def jobs(request, slug=None):
job_groups = __get_job_groups()
try: try:
description = JobGroup.objects.filter(slug=slug).values().first()["description"] description = JobGroup.objects.filter(slug=slug).values().first()["description"]
except Exception: except Exception:
@@ -46,7 +29,6 @@ def jobs(request, slug=None):
active_job_group = JobGroup.objects.filter(slug=slug).first() active_job_group = JobGroup.objects.filter(slug=slug).first()
context = { context = {
"job_groups": job_groups,
"description": description, "description": description,
"job_members": job_members, "job_members": job_members,
"active_job_group": active_job_group, "active_job_group": active_job_group,
@@ -56,8 +38,6 @@ def jobs(request, slug=None):
def members(request, role=None): def members(request, role=None):
job_groups = __get_job_groups()
for elem in Member.MemberRole: for elem in Member.MemberRole:
if role == elem.label.lower(): if role == elem.label.lower():
members = Member.all_members.filter(role=elem.value) members = Member.all_members.filter(role=elem.value)
@@ -66,7 +46,6 @@ def members(request, role=None):
members = Member.all_members.all() members = Member.all_members.all()
context = { context = {
"job_groups": job_groups,
"members": members, "members": members,
} }
@@ -74,8 +53,6 @@ def members(request, role=None):
def profile(request, member_id=None): def profile(request, member_id=None):
job_groups = __get_job_groups()
member = Member.all_members.filter(id=member_id).first() member = Member.all_members.filter(id=member_id).first()
if not member: if not member:
logger.info("Wrong member id '{}'".format(member_id)) logger.info("Wrong member id '{}'".format(member_id))
@@ -85,7 +62,6 @@ def profile(request, member_id=None):
inactive_jobs = JobMember.members.get_inactive_jobs(member_id) inactive_jobs = JobMember.members.get_inactive_jobs(member_id)
context = { context = {
"job_groups": job_groups,
"member": member, "member": member,
"active_jobs": active_jobs, "active_jobs": active_jobs,
"inactive_jobs": inactive_jobs, "inactive_jobs": inactive_jobs,

View File

@@ -1,12 +1,12 @@
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from .models import Member, JobMember, JobGroup, Job from .models import Job, JobGroup, JobMember, Member
from .serializers import ( from .serializers import (
MemberSerializer,
JobSerializer,
JobGroupSerializer, JobGroupSerializer,
JobMemberSerializer, JobMemberSerializer,
JobSerializer,
MemberSerializer,
) )

View File

@@ -1,6 +1,7 @@
from django.contrib import admin, messages from django.contrib import admin, messages
from documents.api import create_pad from documents.api import create_pad
from .forms import EventForm, FetMeetingForm, NewsForm, PostForm from .forms import EventForm, FetMeetingForm, NewsForm, PostForm
from .models import Event, FetMeeting, FileUpload, News, Post from .models import Event, FetMeeting, FileUpload, News, Post

View File

@@ -7,9 +7,8 @@ from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from .models import FetMeeting
from .mails import send_agenda_mail from .mails import send_agenda_mail
from .models import FetMeeting
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -1,12 +1,11 @@
from ckeditor_uploader.widgets import CKEditorUploadingWidget from ckeditor_uploader.widgets import CKEditorUploadingWidget
from taggit.models import Tag
from django import forms from django import forms
from django.forms.widgets import CheckboxInput from django.forms.widgets import CheckboxInput, DateTimeInput
from django.utils import timezone from django.utils import timezone
from django.utils.dates import MONTHS from django.utils.dates import MONTHS
from taggit.models import Tag
from .models import Post, Event, News, FetMeeting from .models import Event, FetMeeting, News, Post
class PostForm(forms.ModelForm): class PostForm(forms.ModelForm):
@@ -167,3 +166,94 @@ class PostSearchForm(forms.Form):
self.fields["year"].choices = year_choices self.fields["year"].choices = year_choices
except: except:
pass pass
class NewsUpdateForm(forms.ModelForm):
class Meta:
model = News
fields = [
"title",
"status",
"body",
]
labels = {
"title": "Titel",
"image": "Hintergrundbild",
"body": "Text",
}
widgets = {"body": CKEditorUploadingWidget(config_name="default")}
class EventUpdateForm(forms.ModelForm):
class Meta:
model = Event
fields = [
"title",
"status",
"event_start",
"event_end",
"event_place",
]
labels = {
"title": "Titel",
"event_start": "Start des Events",
"event_end": "Ende des Events",
"event_place": "Ort des Events",
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # to get the self.fields set
self.fields["event_start"].required = True
self.fields["event_end"].required = False
if "event_place" in self.fields:
self.fields["event_place"].required = True
class FetMeetingCreateForm(forms.ModelForm):
class Meta:
model = FetMeeting
fields = ["event_start", "event_end", "event_place", "tags"]
labels = {
"event_start": "Start der Sitzung",
"event_end": "Ende der Sitzung",
"event_place": "Ort der Sitzung",
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # to get the self.fields set
self.fields["event_start"].required = True
self.fields["event_end"].required = False
self.fields["event_place"].initial = "FET"
tags = []
tags.append(Tag())
tags[0].name = "fachschaft"
self.fields["tags"].initial = tags
class FetMeetingUpdateForm(forms.ModelForm):
class Meta:
model = FetMeeting
fields = ["event_start", "event_end", "event_place"]
labels = {
"event_start": "Start der Sitzung",
"event_end": "Ende der Sitzung",
"event_place": "Ort der Sitzung",
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # to get the self.fields set
self.fields["event_start"].required = True
self.fields["event_end"].required = False
self.fields["event_place"].initial = "FET"

View File

@@ -1,10 +1,8 @@
import logging import logging
from html2text import html2text
from django.conf import settings from django.conf import settings
from django.core.mail import send_mail from django.core.mail import send_mail
from html2text import html2text
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -1,11 +1,11 @@
from django.db import models
from django.db.models import Case, Manager, Q, When
from django.utils import timezone
from datetime import timedelta from datetime import timedelta
from django.db import models
from django.db.models import Case, Q, When
from django.utils import timezone
class PublishedManager(Manager):
class PublishedManager(models.Manager):
def published(self, public=True): def published(self, public=True):
""" """
publish all posts with status 'PUBLIC' publish all posts with status 'PUBLIC'
@@ -27,7 +27,7 @@ class PublishedManager(Manager):
return qs return qs
class PostManager(PublishedManager, Manager): class PostManager(PublishedManager, models.Manager):
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.annotate( qs = qs.annotate(
@@ -61,15 +61,8 @@ class PostManager(PublishedManager, Manager):
return self.published(public).filter(qs_filter) return self.published(public).filter(qs_filter)
# use for finding tags at homepage - TODO: delete when new design published
def get_last_months_posts(self, public=True):
date_today = timezone.now().date()
return self.published(public).filter(
public_date__gt=date_today - timedelta(days=365)
)
class ArticleManager(PublishedManager, models.Manager):
class ArticleManager(PublishedManager, Manager):
""" """
Provide a query set only for "Article" Provide a query set only for "Article"
regular fet meetings should not be contained in the news stream regular fet meetings should not be contained in the news stream
@@ -92,7 +85,7 @@ class ArticleManager(PublishedManager, Manager):
return self.published(public).filter(is_pinned=True).first() return self.published(public).filter(is_pinned=True).first()
class NewsManager(PublishedManager, Manager): class NewsManager(PublishedManager, models.Manager):
""" """
Provide a query set only for "News" Provide a query set only for "News"
""" """
@@ -107,7 +100,7 @@ class NewsManager(PublishedManager, Manager):
return qs.order_by("-date") return qs.order_by("-date")
class AllEventManager(PublishedManager, Manager): class AllEventManager(PublishedManager, models.Manager):
""" """
Provide a query set for all events ("Event" and "Fet Meeting") Provide a query set for all events ("Event" and "Fet Meeting")
""" """
@@ -128,7 +121,7 @@ class AllEventManager(PublishedManager, Manager):
return qs.reverse() return qs.reverse()
class EventManager(PublishedManager, Manager): class EventManager(PublishedManager, models.Manager):
""" """
Provide a query set only for "Events" Provide a query set only for "Events"
regular fet meetings should not be contained in the news stream regular fet meetings should not be contained in the news stream
@@ -154,7 +147,7 @@ class EventManager(PublishedManager, Manager):
return qs return qs
class FetMeetingManager(PublishedManager, Manager): class FetMeetingManager(PublishedManager, models.Manager):
""" """
Provide a query set only for "Fet Meeting" Provide a query set only for "Fet Meeting"
""" """

View File

@@ -2,8 +2,6 @@ import logging
import re import re
from datetime import timedelta from datetime import timedelta
from taggit.managers import TaggableManager
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import ValidationError from django.core.validators import ValidationError
@@ -13,17 +11,19 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from taggit.managers import TaggableManager
from core.models import CustomFlatPage from core.models import CustomFlatPage
from documents import create_pad, get_pad_html, set_pad_html from documents import create_pad, get_pad_html, set_pad_html
from documents.api import get_pad_link from documents.api import get_pad_link
from .managers import ( from .managers import (
PostManager,
ArticleManager,
NewsManager,
AllEventManager, AllEventManager,
ArticleManager,
EventManager, EventManager,
FetMeetingManager, FetMeetingManager,
NewsManager,
PostManager,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -138,7 +138,7 @@ class Post(models.Model):
) )
def get_absolute_url(self): def get_absolute_url(self):
return reverse("posts:show", kwargs={"id": self.slug}) return reverse("posts:post", kwargs={"slug": self.slug})
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# save the post with some defaults # save the post with some defaults
@@ -150,13 +150,6 @@ class Post(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
self.tags.set(
[
*re.findall(r"\#([\d\w-]+)", str(self.subtitle)),
*re.findall(r"\#([\d\w-]+)", str(self.title)),
]
)
@property @property
def agenda_html(self): def agenda_html(self):
"Agenda HTML from Etherpad Pad" "Agenda HTML from Etherpad Pad"
@@ -256,6 +249,10 @@ class Post(models.Model):
def three_tag_names(self): def three_tag_names(self):
return self.tags.names()[:3] return self.tags.names()[:3]
@property
def tag_names(self):
return [t for t in self.tags.names()]
@property @property
def imageurl(self): def imageurl(self):
""" """

View File

@@ -1,7 +1,6 @@
from django.urls import path, re_path from django.urls import path, re_path
from . import apps from . import apps, views
from . import views
from .utils import slug_calc, tag_complete from .utils import slug_calc, tag_complete
app_name = apps.PostsConfig.name app_name = apps.PostsConfig.name
@@ -10,7 +9,13 @@ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
# fet calendar (path have to be ahead show) # fet calendar (path have to be ahead show)
path("fet_calendar.ics", views.calendar, name="calendar"), path("fet_calendar.ics", views.calendar, name="calendar"),
path("<str:id>", views.show, name="show"), path(
"create-fetmeeting/",
views.FetMeetingCreateView.as_view(),
name="fetmeeting_create",
),
path("<slug:slug>/", views.PostDetailView.as_view(), name="post"),
path("<slug:slug>/update/", views.PostUpdateView.as_view(), name="post_update"),
re_path( re_path(
r"^(?P<id>[-\w]+)/agenda.pdf$", r"^(?P<id>[-\w]+)/agenda.pdf$",
views.show_pdf_agenda, views.show_pdf_agenda,

View File

@@ -1,11 +1,10 @@
from io import BytesIO from io import BytesIO
from taggit.models import Tag from django.http import HttpResponse, HttpResponseServerError, JsonResponse
from xhtml2pdf import pisa
from django.http import HttpResponse, JsonResponse, HttpResponseServerError
from django.utils import timezone from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from taggit.models import Tag
from xhtml2pdf import pisa
def render_to_pdf(html): def render_to_pdf(html):

View File

@@ -1,18 +1,28 @@
import logging import logging
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, Http404 from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404, HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView
from authentications.decorators import authenticated_user from authentications.decorators import authenticated_user
from documents.etherpadlib import add_ep_cookie from documents.etherpadlib import add_ep_cookie
from fet2020.utils import add_log_action
from members.models import Member from members.models import Member
from .forms import PostSearchForm
from .models import Event, FileUpload, Post
from .utils import render_to_pdf
from .forms import (
EventUpdateForm,
FetMeetingCreateForm,
FetMeetingUpdateForm,
NewsUpdateForm,
PostSearchForm,
)
from .models import Event, FetMeeting, FileUpload, Post
from .utils import render_to_pdf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -88,62 +98,197 @@ def tags(request, tag=""):
return render(request, "posts/tag.html", context) return render(request, "posts/tag.html", context)
def __get_post_object(id=None, public=True): class PostDetailView(DetailView):
post = None model = Post
try: def get(self, request, *args, **kwargs):
if id.isdigit() or id is int: response = super().get(request, *args, **kwargs)
post = Post.objects.published(public).get(id=int(id))
elif id != "" and id is not None:
post = Post.objects.published(public).get(slug=id)
except Exception:
logger.info("Wrong id '{}'".format(id))
raise Http404("wrong post id")
return post # check if etherpad server works
if self.object.agenda_link or self.object.protocol_link:
try:
response = add_ep_cookie(request, response)
except Exception as e:
logger.info("Etherpad Server doesn't work. Error: %s", e)
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# files
files = FileUpload.objects.filter(post=self.object)
# author
author = None
author_image = None
post_author = Member.all_members.filter(username=self.object.author).first()
if post_author:
author = post_author
author_image = post_author.image["avatar"].url
related_posts = self.object.tags.similar_objects()
# list of non 'is_hidden' posts for related_posts
for obj in related_posts:
if not obj.published:
related_posts.remove(obj)
context = {
"post": self.object,
"files": files,
"author": author,
"author_image": author_image,
"next": self.post_next(),
"previous": self.post_previous(),
"related_posts": related_posts[:4],
}
return context
def get_queryset(self):
self.public_only = not self.request.user.is_authenticated
return Post.objects.published(self.public_only)
def get_template_names(self):
template_name = "posts/news/detail.html"
if self.object.post_type == "E":
template_name = "posts/event/detail.html"
elif self.object.post_type == "F":
template_name = "posts/fetmeeting/detail.html"
return template_name
def post_next(self):
"""
Helper function for getting next post
"""
posts = Post.objects.date_sorted_list(self.public_only).filter(
post_type=self.object.post_type
)
qs = posts.filter(date__lt=self.object.date)
if not qs:
qs = posts[:1]
return qs.first().slug
def post_previous(self):
"""
Helper function for getting previous post
"""
posts = (
Post.objects.date_sorted_list(self.public_only)
.filter(post_type=self.object.post_type)
.reverse()
)
qs = posts.filter(date__gt=self.object.date)
if not qs:
qs = posts[:1]
return qs.first().slug
def show(request, id=None): class PostUpdateView(LoginRequiredMixin, UpdateView):
public_only = not request.user.is_authenticated model = Post
post = __get_post_object(id, public_only)
# files def form_valid(self, form):
files = FileUpload.objects.filter(post=post) model = "news"
if self.object.post_type == "E":
model = "event"
elif self.object.post_type == "F":
model = "fetmeeting"
add_log_action(self.request, form, "posts", model, False)
# author return super().form_valid(form)
author = None
author_image = None
post_author = Member.all_members.filter(username=post.author).first()
if post_author:
author = post_author
author_image = post_author.image["avatar"].url
related_posts = post.tags.similar_objects() def get_form_class(self):
# list of non 'is_hidden' posts for related_posts form_class = NewsUpdateForm
for obj in related_posts: if self.object.post_type == "E":
if not obj.published: form_class = EventUpdateForm
related_posts.remove(obj) elif self.object.post_type == "F":
form_class = FetMeetingUpdateForm
context = { return form_class
"post": post,
"files": files,
"author": author,
"author_image": author_image,
"next": __next(post, public_only),
"previous": __previous(post, public_only),
"related_posts": related_posts[:4],
}
response = render(request, "posts/show.html", context) def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
# check if etherpad server works
if post.agenda_link or post.protocol_link:
try: try:
response = add_ep_cookie(request, response) q = kwargs["data"]
except Exception as e:
logger.info("Etherpad Server doesn't work. Error: %s", e)
return response _mutable = q._mutable
q._mutable = True
event_start_0 = q.pop("event_start_0")[0]
event_start_1 = q.pop("event_start_1")[0]
q.update({"event_start": f"{event_start_0} {event_start_1}"})
event_end_0 = q.pop("event_end_0")[0]
event_end_1 = q.pop("event_end_1")[0]
q.update({"event_end": f"{event_end_0} {event_end_1}"})
q._mutable = _mutable
except Exception as e:
pass
return kwargs
def get_template_names(self):
template_name = "posts/news/update.html"
if self.object.post_type == "E":
template_name = "posts/event/update.html"
elif self.object.post_type == "F":
template_name = "posts/fetmeeting/update.html"
return template_name
class FetMeetingCreateView(LoginRequiredMixin, CreateView):
model = FetMeeting
template_name = "posts/fetmeeting/create.html"
form_class = FetMeetingCreateForm
def form_valid(self, form):
form.instance.author = self.request.user
add_log_action(self.request, form, "posts", "fetmeeting", True)
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
try:
q = kwargs["data"]
_mutable = q._mutable
q._mutable = True
event_start_0 = q.pop("event_start_0")[0]
event_start_1 = q.pop("event_start_1")[0]
q.update({"event_start": f"{event_start_0} {event_start_1}"})
event_end_0 = q.pop("event_end_0")[0]
event_end_1 = q.pop("event_end_1")[0]
q.update({"event_end": f"{event_end_0} {event_end_1}"})
q._mutable = _mutable
except Exception as e:
pass
return kwargs
def show_pdf_agenda(request, id):
post = Post.objects.published(True).get(slug=id)
html = post.agenda_html
return show_pdf(request, html, post.slug + "-agenda")
@authenticated_user
def show_pdf_protocol(request, id):
post = Post.objects.published(True).get(slug=id)
html = post.protocol_html
return show_pdf(request, html, post.slug + "-protokoll")
def show_pdf(request, html, filename): def show_pdf(request, html, filename):
@@ -170,62 +315,3 @@ def show_pdf(request, html, filename):
response["Content-Disposition"] = content response["Content-Disposition"] = content
return response return response
def show_pdf_agenda(request, id):
post = __get_post_object(id)
html = post.agenda_html
return show_pdf(request, html, post.slug + "-agenda")
@authenticated_user
def show_pdf_protocol(request, id):
post = __get_post_object(id)
html = post.protocol_html
return show_pdf(request, html, post.slug + "-protokoll")
def __next(post=None, public=True):
"""
Helper function for getting next post
"""
posts = None
d = post.slug
if post:
posts = Post.objects.date_sorted_list(public).filter(post_type=post.post_type)
if posts:
for k, v in enumerate(posts):
if post.slug == v.slug:
if (k + 1) < len(posts):
d = posts[k + 1].slug
else:
d = posts[0].slug
break
return d
def __previous(post=None, public=True):
"""
Helper function for getting previous post
"""
posts = None
d = post.slug
if post:
posts = Post.objects.date_sorted_list(public).filter(post_type=post.post_type)
if posts:
for k, v in enumerate(posts):
if post.slug == v.slug:
if k < 1:
d = posts[len(posts) - 1].slug
else:
d = posts[k - 1].slug
break
return d

View File

@@ -1,7 +1,7 @@
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from .models import Post, FetMeeting from .models import FetMeeting, Post
from .serializers import PostSerializer from .serializers import PostSerializer

View File

@@ -1,7 +1,6 @@
from django.urls import path, re_path from django.urls import path, re_path
from . import apps from . import apps, views
from . import views
app_name = apps.SearchConfig.name app_name = apps.SearchConfig.name

View File

@@ -1,10 +1,10 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from haystack.generic_views import SearchView from haystack.generic_views import SearchView
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from authentications.decorators import authenticated_user from authentications.decorators import authenticated_user
from .forms import FetUserSearchForm, NonUserSearchForm from .forms import FetUserSearchForm, NonUserSearchForm

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,4 @@
from ckeditor.widgets import CKEditorWidget from ckeditor.widgets import CKEditorWidget
from django import forms from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import User from django.contrib.auth.models import User
@@ -7,7 +6,7 @@ from django.core.validators import ValidationError
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from django.utils import timezone from django.utils import timezone
from .models import Task, TaskList, Document from .models import Document, Task, TaskList
class DateInput(forms.DateInput): class DateInput(forms.DateInput):

View File

@@ -13,6 +13,7 @@ from django.utils.text import slugify
from documents import create_pad from documents import create_pad
from documents.api import get_pad_link from documents.api import get_pad_link
from fet2020.utils import create_random_id from fet2020.utils import create_random_id
from .managers import TaskManager from .managers import TaskManager
@@ -91,7 +92,7 @@ class Task(models.Model):
return self.title return self.title
def get_absolute_url(self): def get_absolute_url(self):
return reverse("tasks:task-detail", kwargs={"slug": self.slug}) return reverse("tasks:task", kwargs={"slug": self.slug})
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:

View File

@@ -1,16 +1,16 @@
from django.urls import path from django.urls import path
from . import apps from . import apps, views
from . import views from .views import DocumentCreateView, TaskCreateView, TaskDetailView, TaskUpdateView
from .views import TaskCreateView, TaskDetailView, TaskUpdateView, DocumentCreateView
app_name = apps.TasksConfig.name app_name = apps.TasksConfig.name
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("add/", TaskCreateView.as_view(), name="task-create"), path("create-task/", TaskCreateView.as_view(), name="task_create"),
path("<slug:slug>/", TaskDetailView.as_view(), name="task-detail"), path("<slug:slug>/", TaskDetailView.as_view(), name="task"),
path("<slug:slug>/update/", TaskUpdateView.as_view(), name="task-update"), path("<slug:slug>/update/", TaskUpdateView.as_view(), name="task_update"),
path("<slug:slug>/add/", DocumentCreateView.as_view(), name="docu-create"), path(
"<slug:slug>/create-document/", DocumentCreateView.as_view(), name="docu_create"
),
] ]

View File

@@ -1,21 +1,21 @@
import logging import logging
from collections import deque from collections import deque
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy, reverse
from django.utils import timezone
from authentications.decorators import authenticated_user from authentications.decorators import authenticated_user
from documents.api import get_pad_link from documents.api import get_pad_link
from documents.etherpadlib import add_ep_cookie from documents.etherpadlib import add_ep_cookie
from fet2020.utils import add_log_action from fet2020.utils import add_log_action
from intern.models import Topic from intern.models import Topic
from .forms import DocumentCreateForm, TaskCreateForm, TaskUpdateForm from .forms import DocumentCreateForm, TaskCreateForm, TaskUpdateForm
from .models import Document, Task, TaskList from .models import Document, Task, TaskList
@@ -146,4 +146,4 @@ class DocumentCreateView(LoginRequiredMixin, CreateView):
return context return context
def get_success_url(self): def get_success_url(self):
return reverse("tasks:task-detail", kwargs=self.kwargs) return reverse("tasks:task", kwargs=self.kwargs)

View File

@@ -12,6 +12,7 @@
{% if site_url %} {% if site_url %}
<a class="button" href="{{ site_url }}">Zurück zur FET Homepage</a> <a class="button" href="{{ site_url }}">Zurück zur FET Homepage</a>
{% endif %} {% endif %}
<a class="button" href="{% url 'authentications:password_change' %}">Passwort ändern</a>
<a class="button" href="{% url 'admin:logout' %}">{% translate 'Log out' %}</a> <a class="button" href="{% url 'admin:logout' %}">{% translate 'Log out' %}</a>
{% endblock %} {% endblock %}
</div> </div>

View File

@@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% block title %}LDAP Passwort ändern{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-grow flex flex-col">
<h1 class="page-title">LDAP Passwort ändern</h1>
<div class="max-w-4xl mx-auto">
<span class="text-gray-700 dark:text-gray-200">Aus Sicherheitsgründen bitte zuerst das alte Passwort und darunter dann zweimal das neue Passwort eingeben, um sicherzustellen, dass es es korrekt eingegeben wurde.</span>
</div>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="{% url 'authentications:password_change' %}" method="POST" class="sm:p-4 sm:w-3/5 md:w-1/2 lg:w-2/5 xl:w-1/3 2xl:w-1/4 grid grid-cols-1 gap-3 sm:gap-6">
{% csrf_token %}
{% include "baseform/non_field_errors.html" %}
{% include "baseform/password.html" with field=form.old_password %}
{% include "baseform/password.html" with field=form.new_password1 %}
{% include "baseform/password.html" with field=form.new_password2 %}
<input type="submit" class="block btn btn-primary" value="Passwort ändern">
</form>
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% block title %}LDAP Passwort erfolgreich geändert{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-grow flex flex-col">
<h1 class="page-title">LDAP Passwort erfolgreich geändert</h1>
<div class="max-w-4xl mx-auto">
<span class="text-gray-700 dark:text-gray-200">Ihr Passwort wurde geändert.</span>
</div>
</main>
{% endblock %}

View File

@@ -7,9 +7,11 @@
<main class="container mx-auto w-full px-4 my-8 flex-grow flex flex-col"> <main class="container mx-auto w-full px-4 my-8 flex-grow flex flex-col">
<h1 class="page-title">Anmeldung für FET-Mitarbeiter</h1> <h1 class="page-title">Anmeldung für FET-Mitarbeiter</h1>
<div class="w-full h-full flex-1 flex justify-center items-center"> <div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="sm:p-4 sm:w-3/5 md:w-1/2 lg:w-2/5 xl:w-1/3 2xl:w-1/4 grid grid-cols-1 gap-3 sm:gap-6"> <form action="{% url 'authentications:login' %}" method="POST" class="sm:p-4 sm:w-3/5 md:w-1/2 lg:w-2/5 xl:w-1/3 2xl:w-1/4 grid grid-cols-1 gap-3 sm:gap-6">
{% csrf_token %} {% csrf_token %}
{% include "baseform/non_field_errors.html" %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-danger"> <div class="alert alert-danger">
<i class="alert-icon fa-solid fa-check-circle"></i> <i class="alert-icon fa-solid fa-check-circle"></i>
@@ -18,15 +20,11 @@
</div> </div>
{% endfor %} {% endfor %}
<label class="block"> {% include "baseform/text.html" with field=form.username %}
<span class="text-gray-700 dark:text-gray-200">Benutzername</span> {% include "baseform/password.html" with field=form.password %}
<input type="text" name="username" class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50" required="required">
</label>
<label class="block">
<span class="text-gray-700 dark:text-gray-200">Passwort</span>
<input type="password" name="password" class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50" required="required">
</label>
<input type="submit" class="block btn btn-primary" value="Anmelden"> <input type="submit" class="block btn btn-primary" value="Anmelden">
<input type="hidden" name="next" value="{{ next }}">
</form> </form>
</div> </div>
</main> </main>

View File

@@ -1,38 +1,91 @@
{% extends 'head.html' %}
{% load flatpages %} {% load flatpages %}
{% load static %} {% load static %}
{% load version %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FET: {% block title %}Startseite{% endblock %}</title>
<meta name="author" content="Fachschaft Elektrotechnik (FET)">
<meta name="description" content="Die Fachschaft Elektrotechnik besteht aus ET Studierenden, die sich um die Anliegen der Studenten und Studentinnen kümmern.">
<meta property="og:image" content="#"> <!--og:... = Facebook metadata-->
<meta property="og:description" content="Die Fachschaft Elektrotechnik besteht aus ET Studierenden, die sich um die Anliegen der Studenten und Studentinnen kümmern.">
<meta property="og:title" content="Fachschaft Elektrotechnik (FET)">
<meta name="twitter:title" content="Fachschaft Elektrotechnik (FET)">
<meta name="theme-color" content="#006599">
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'img/favicons/apple-touch-icon.png' %}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'img/favicons/favicon-32x32.png' %}">
<!--<link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/favicons/favicon-16x16.png' %}">-->
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/fet_logo_white.png' %}">
<link rel="manifest" href="{% static 'img/favicons/site.webmanifest' %}">
<link rel="mask-icon" href="{% static 'img/favicons/safari-pinned-tab.svg' %}" color="#000000">
<link rel="shortcut icon" href="{% static 'img/fet_logo_white.png' %}">
<meta name="apple-mobile-web-app-title" content="FET - Fachschaft Elektrotechnik">
<meta name="application-name" content="FET - Fachschaft Elektrotechnik">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{% static 'css/flowbite@1.5.5.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
<!-- FontAwesome Kit -->
<!--<script src="https://kit.fontawesome.com/fb26f70535.js" crossorigin="anonymous"></script>-->
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/regular.css' %}" rel="stylesheet" type="text/css">
<!-- Prism.js Theme -->
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prism-themes@1/themes/prism-one-dark.min.css"> -->
<link rel="stylesheet" href="{% static 'css/prism-one-dark.min.css' %}">
<!-- Fonts -->
<link rel="stylesheet" href="{% static 'fonts/Inter-3.19/inter.css' %}">
<!-- <link rel="stylesheet" href="{% static 'fonts/Poppins-4.003/poppins.css' %}"> -->
<link rel="stylesheet" href="{% static 'fonts/Besley-2.0/besley.css' %}">
<link rel="stylesheet" href="{% static 'fonts/Fira_Code-6.2/fira_code.css' %}">
{% block galleryheader %}
{% endblock %}
{% block extraheader %}
{% endblock %}
</head>
{% block body %}
<body x-data="search" x-ref="overflow" @keyup.escape="closeShowSearch"> <body x-data="search" x-ref="overflow" @keyup.escape="closeShowSearch">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<!-- SEARCH-BAR --> <!-- SEARCH-BAR -->
<div class="fixed w-screen h-screen z-30 backdrop-blur-sm backdrop-saturate-50" <div class="fixed w-screen h-screen z-30 backdrop-blur-sm backdrop-saturate-50"
x-show="showSearch"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform backdrop-blur-none backdrop-saturate-100"
x-transition:enter-end="transform backdrop-blur-sm backdrop-saturate-50"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform backdrop-blur-sm backdrop-saturate-50"
x-transition:leave-end="transform backdrop-blur-none backdrop-saturate-100"
>
<form action="{% url 'search:index' %}" class="flex items-center opacity-90 gap-x-4 mt-[33vh] sm:max-w-md lg:max-w-lg xl:max-w-xl mx-4 sm:mx-auto py-2 px-4 shadow-lg dark:shadow-none bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg dark:border-2 dark:border-gray-700"
@click.outside="closeShowSearch"
x-show="showSearch" x-show="showSearch"
x-transition:enter="transition transform ease-out duration-300" x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="scale-0 opacity-0" x-transition:enter-start="transform backdrop-blur-none backdrop-saturate-100"
x-transition:enter-end="scale-100 opacity-90" x-transition:enter-end="transform backdrop-blur-sm backdrop-saturate-50"
x-transition:leave="transition transform ease-in duration-150" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="scale-100 opacity-90" x-transition:leave-start="transform backdrop-blur-sm backdrop-saturate-50"
x-transition:leave-end="scale-0 opacity-0" x-transition:leave-end="transform backdrop-blur-none backdrop-saturate-100"
> >
<input class="flex-grow bg-inherit text-inherit h-10 p-0 border-0 focus:outline-none focus:border-transparent focus:ring-0" type="search" name="q" placeholder="Nach Person, Artikel oder Fotoalbum suchen..."> <form action="{% url 'search:index' %}" class="flex items-center opacity-90 gap-x-4 mt-[33vh] sm:max-w-md lg:max-w-lg xl:max-w-xl mx-4 sm:mx-auto py-2 px-4 shadow-lg dark:shadow-none bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg dark:border-2 dark:border-gray-700"
<button type="submit" class="flex-none"> @click.outside="closeShowSearch"
<i class="fa-solid fa-magnifying-glass text-gray-500 dark:text-gray-600"></i> x-show="showSearch"
</button> x-transition:enter="transition transform ease-out duration-300"
</form> x-transition:enter-start="scale-0 opacity-0"
</div> x-transition:enter-end="scale-100 opacity-90"
x-transition:leave="transition transform ease-in duration-150"
x-transition:leave-start="scale-100 opacity-90"
x-transition:leave-end="scale-0 opacity-0"
>
<input class="flex-grow bg-inherit text-inherit h-10 p-0 border-0 focus:outline-none focus:border-transparent focus:ring-0" type="search" name="q" placeholder="Nach Person, Artikel oder Fotoalbum suchen...">
<button type="submit" class="flex-none">
<i class="fa-solid fa-magnifying-glass text-gray-500 dark:text-gray-600"></i>
</button>
</form>
</div>
{% endif %} {% endif %}
<!-- NAVBAR --> <!-- NAVBAR -->
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
<nav class="navBar-md" x-data="myNavBar"> <nav class="navBar-md" x-data="myNavBar">
@@ -159,7 +212,7 @@
</div> </div>
{% endif %} {% endif %}
<hr class="legal-divider"> <hr class="legal-divider">
<p class="copyright">© {% now 'Y' %} FET - Alle Rechte vorbehalten.</p> <p class="copyright">© {% now 'Y' %} FET - Alle Rechte vorbehalten. {% version %}.</p>
</footer> </footer>
<div class="super-duper-awesome-signature" x-data="counter"> <div class="super-duper-awesome-signature" x-data="counter">
<span x-ref="countFour">Handcrafted </span> <span x-ref="countFour">Handcrafted </span>
@@ -171,6 +224,7 @@
<script src="{% static 'js/alpine-csp.js' %}"></script> <script src="{% static 'js/alpine-csp.js' %}"></script>
<script src="{% static 'js/dark-mode.js' %}"></script> <script src="{% static 'js/dark-mode.js' %}"></script>
<script src="{% static 'js/flowbite@1.5.5.js' %}"></script>
<script src="{% static 'js/gumshoe@5.1.1.js' %}"></script> <script src="{% static 'js/gumshoe@5.1.1.js' %}"></script>
<script src="{% static 'js/smooth-scroll@16.1.2.js' %}"></script> <script src="{% static 'js/smooth-scroll@16.1.2.js' %}"></script>
@@ -180,4 +234,4 @@
<script src="{% static 'js/prism-core@1.25.0.js' %}"></script> <script src="{% static 'js/prism-core@1.25.0.js' %}"></script>
<script src="{% static 'js/prism-autoloader@1.25.0.js' %}"></script> <script src="{% static 'js/prism-autoloader@1.25.0.js' %}"></script>
</body> </body>
{% endblock %} </html>

View File

@@ -0,0 +1,10 @@
<label class="block">
<span class="text-gray-700 dark:text-gray-200">{{ field.label }}</span>
{% if field.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ field.errors }}</div>
</div>
{% endif %}
{{ media }}
{{ field }}
</label>

View File

@@ -0,0 +1,13 @@
<label>
<span class="text-gray-700 dark:text-gray-200">{{ field.label }}</span>
{% if field.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ field.errors }}</div>
</div>
{% endif %}
<input type="date" id="id_{{ field.name }}_0" name="{{ field.name }}_0" value="{{ field.value|date:"Y-m-d" }}" {% if field.required %}required{% endif %} class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<input type="time" id="id_{{ field.name }}_1" name="{{ field.name }}_1" value="{{ field.value|time }}" {% if field.required %}required{% endif %} class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
{% if field.help_text %}
<span class="text-gray-700 dark:text-gray-200">{{ field.help_text }}</span>
{% endif %}
</label>

View File

@@ -0,0 +1,7 @@
{% if form.non_field_errors %}
<div class="alert alert-danger">
<i class="alert-icon fa-solid fa-check-circle"></i>
<h2 class="alert-title">Fehler:</h2>
<div class="alert-body">{{ form.non_field_errors }}</div>
</div>
{% endif %}

View File

@@ -0,0 +1,12 @@
<label class="block">
<span class="text-gray-700 dark:text-gray-200">{{ field.label }}</span>
{% if field.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ field.errors }}</div>
</div>
{% endif %}
<input type="password" id="id_{{ field.name }}" name="{{ field.name }}" {% if field.required %}required{% endif %} class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
{% if field.help_text %}
<span class="text-gray-700 dark:text-gray-200">{{ field.help_text }}</span>
{% endif %}
</label>

View File

@@ -0,0 +1,13 @@
<label>
<span class="text-gray-700 dark:text-gray-200">{{ field.label }}</span>
{% if field.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ field.errors }}</div>
</div>
{% endif %}
<select id="id_{{ field.name }}" name="{{ field.name }}" {% if field.required %}required{% endif %} class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
{% for elem in field %}
{{ elem }}
{% endfor %}
</select>
</label>

View File

@@ -0,0 +1,12 @@
<label class="block">
<span class="text-gray-700 dark:text-gray-200">{{ field.label }}</span>
{% if field.errors %}
<div class="alert alert-danger">
<div class="alert-body">{{ field.errors }}</div>
</div>
{% endif %}
<input type="text" id="id_{{ field.name }}" name="{{ field.name }}" {% if field.value %}value="{{ field.value }}"{% endif %} {% if field.required %}required{% endif %} class="mt-1 block w-full rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
{% if field.help_text %}
<span class="text-gray-700 dark:text-gray-200">{{ field.help_text }}</span>
{% endif %}
</label>

View File

@@ -16,7 +16,7 @@
<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"> <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 %} {% for album in albums %}
{% if request.user.is_authenticated and album.status == album.DRAFT %} {% if request.user.is_authenticated and album.status == album.DRAFT %}
<a href="{% url 'gallery:draft-album' album.slug %}" class="block max-w-xs sm:max-w-none"> <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.thumbnail }}" class="rounded">
<h2 class="px-2 text-proprietary-dark dark:text-sky-300">{{ album.title }}</h2> <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> <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>

View File

@@ -1,58 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FET: {% block title %}Startseite{% endblock %}</title>
<meta name="author" content="Fachschaft Elektrotechnik (FET)">
<meta name="description" content="Die Fachschaft Elektrotechnik besteht aus ET Studierenden, die sich um die Anliegen der Studenten und Studentinnen kümmern.">
<meta property="og:image" content="#"> <!--og:... = Facebook metadata-->
<meta property="og:description" content="Die Fachschaft Elektrotechnik besteht aus ET Studierenden, die sich um die Anliegen der Studenten und Studentinnen kümmern.">
<meta property="og:title" content="Fachschaft Elektrotechnik (FET)">
<meta name="twitter:title" content="Fachschaft Elektrotechnik (FET)">
<meta name="theme-color" content="#006599">
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'img/favicons/apple-touch-icon.png' %}">
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'img/favicons/favicon-32x32.png' %}">
<!--<link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/favicons/favicon-16x16.png' %}">-->
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/fet_logo_white.png' %}">
<link rel="manifest" href="{% static 'img/favicons/site.webmanifest' %}">
<link rel="mask-icon" href="{% static 'img/favicons/safari-pinned-tab.svg' %}" color="#000000">
<link rel="shortcut icon" href="{% static 'img/fet_logo_white.png' %}">
<meta name="apple-mobile-web-app-title" content="FET - Fachschaft Elektrotechnik">
<meta name="application-name" content="FET - Fachschaft Elektrotechnik">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
<!-- FontAwesome Kit -->
<!--<script src="https://kit.fontawesome.com/fb26f70535.js" crossorigin="anonymous"></script>-->
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/regular.css' %}" rel="stylesheet" type="text/css">
<!-- Prism.js Theme -->
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prism-themes@1/themes/prism-one-dark.min.css"> -->
<link rel="stylesheet" href="{% static 'css/prism-one-dark.min.css' %}">
<!-- Fonts -->
<link rel="stylesheet" href="{% static 'fonts/Inter-3.19/inter.css' %}">
<!-- <link rel="stylesheet" href="{% static 'fonts/Poppins-4.003/poppins.css' %}"> -->
<link rel="stylesheet" href="{% static 'fonts/Besley-2.0/besley.css' %}">
<link rel="stylesheet" href="{% static 'fonts/Fira_Code-6.2/fira_code.css' %}">
{% block galleryheader %}
{% endblock %}
{% block extraheader %}
{% endblock %}
</head>
{% block body %}
{% endblock %}
</html>

View File

@@ -15,7 +15,6 @@
<div class="hidden sm:block flex-none w-2/5 lg:w-1/3 bg-white dark:bg-gray-800 p-2 lg:p-4 rounded shadow-xl dark:border-2 dark:border-gray-700"> <div class="hidden sm:block flex-none w-2/5 lg:w-1/3 bg-white dark:bg-gray-800 p-2 lg:p-4 rounded shadow-xl dark:border-2 dark:border-gray-700">
<h2 class="section-title sm:text-left"><i class="fa-solid fa-comments text-gray-300 dark:text-gray-400 mr-2"></i>Events</h2> <h2 class="section-title sm:text-left"><i class="fa-solid fa-comments text-gray-300 dark:text-gray-400 mr-2"></i>Events</h2>
<div class="-mb-2 text-gray-700 dark:text-gray-200 text-sm md:text-base"> <div class="-mb-2 text-gray-700 dark:text-gray-200 text-sm md:text-base">
{% if featured_event %} {% if featured_event %}
{% with post=featured_event %} {% with post=featured_event %}
{% include 'posts/partials/_meeting_row.html' %} {% include 'posts/partials/_meeting_row.html' %}
@@ -26,7 +25,6 @@
{% include 'posts/partials/_meeting_row.html' %} {% include 'posts/partials/_meeting_row.html' %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
@@ -34,7 +32,25 @@
<!-- Main Content --> <!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1"> <main class="container mx-auto w-full px-4 my-8 flex-1">
<section class="sm:hidden"> {% if request.user.is_authenticated %}
<div data-dial-init class="fixed bottom-6 right-6 group">
<div id="speed-dial-menu-dropdown" class="flex hidden flex-col justify-end py-1 mb-4 space-y-2 bg-white rounded-lg border border-gray-100 shadow-sm dark:border-gray-600 dark:bg-gray-700">
<ul class="text-sm text-gray-500 dark:text-gray-300">
<li>
<a href="{% url 'posts:fetmeeting_create' %}" class="flex items-center py-2 px-5 hover:bg-gray-100 dark:hover:bg-gray-600 hover:text-gray-900 dark:hover:text-white">
<i class="fa-solid fa-plus mr-2"></i>
<span class="text-sm font-medium">Neue Fachschaftssitzung</span>
</a>
</li>
</ul>
</div>
<button type="button" data-dial-toggle="speed-dial-menu-dropdown" aria-controls="speed-dial-menu-dropdown" aria-expanded="false" class="flex justify-center items-center ml-auto w-14 h-14 text-white bg-blue-700 rounded-full hover:bg-blue-800 dark:bg-blue-600 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 focus:outline-none dark:focus:ring-blue-800">
<i class="fa-solid fa-pen-to-square"></i>
</button>
</div>
{% endif %}
<section class="sm:hidden">
<h2 class="section-title section-title-margins">Events</h2> <h2 class="section-title section-title-margins">Events</h2>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
@@ -63,7 +79,7 @@
<a href="{% url 'posts:calendar' %}" class="btn btn-secondary block w-full"><i class="fa-solid fa-calendar-days mr-2"></i>Kalender abonnieren</a> <a href="{% url 'posts:calendar' %}" class="btn btn-secondary block w-full"><i class="fa-solid fa-calendar-days mr-2"></i>Kalender abonnieren</a>
</div> </div>
</div> </div>
</aside> </aside>
<section class="my-8 sm:my-0 sm:w-3/5 xl:w-2/5 flex flex-col gap-4"> <section class="my-8 sm:my-0 sm:w-3/5 xl:w-2/5 flex flex-col gap-4">
<h2 class="section-title section-title-margins">Zuletzt veröffentlicht</h2> <h2 class="section-title section-title-margins">Zuletzt veröffentlicht</h2>
@@ -73,7 +89,6 @@
{% include 'posts/partials/_posts_pinned.html' %} {% include 'posts/partials/_posts_pinned.html' %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
{% for post in posts %} {% for post in posts %}
{% include 'posts/partials/_posts_hero.html' %} {% include 'posts/partials/_posts_hero.html' %}
{% endfor %} {% endfor %}
@@ -82,6 +97,7 @@
</section> </section>
</div> </div>
<!--
<div id="infoBox" class="sticky bottom-4 mt-8 p-4 rounded-lg shadow-lg bg-gray-600 dark:bg-gray-800 text-gray-200 dark:text-gray-300 flex gap-x-4 items-center leading-none dark:border-2 dark:border-gray-700" <div id="infoBox" class="sticky bottom-4 mt-8 p-4 rounded-lg shadow-lg bg-gray-600 dark:bg-gray-800 text-gray-200 dark:text-gray-300 flex gap-x-4 items-center leading-none dark:border-2 dark:border-gray-700"
x-data="infoBox" x-data="infoBox"
x-show="consent" x-show="consent"
@@ -104,5 +120,6 @@
@click="closeBox" @click="closeBox"
><i class="fa-solid fa-xmark text-gray-300 dark:text-gray-500"></i></button> ><i class="fa-solid fa-xmark text-gray-300 dark:text-gray-500"></i></button>
</div> </div>
-->
</main> </main>
{% endblock %} {% endblock %}

View File

@@ -29,7 +29,7 @@
</a> --> </a> -->
</div> </div>
<div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600"> <div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600">
<a href="{% url 'intern:etherpad-create' attachment.topic.topic_group.slug attachment.topic.slug attachment.slug %}" class="flex justify-between"> <a href="{% url 'intern:etherpad_create' attachment.topic.topic_group.slug attachment.topic.slug attachment.slug %}" class="flex justify-between">
<h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Etherpad erstellen</h2> <h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Etherpad erstellen</h2>
</a> </a>
@@ -45,7 +45,7 @@
<section> <section>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Dokumente:</h2> <h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Dokumente:</h2>
<div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600"> <div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600">
<a href="{% url 'intern:file-create' attachment.topic.topic_group.slug attachment.topic.slug attachment.slug %}" class="flex justify-between"> <a href="{% url 'intern:file_create' attachment.topic.topic_group.slug attachment.topic.slug attachment.slug %}" class="flex justify-between">
<h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Dokument hochladen</h2> <h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Dokument hochladen</h2>
</a> </a>
@@ -58,7 +58,7 @@
</div> </div>
</section> </section>
<a href="{% url 'intern:attachment-update' attachment.topic.topic_group.slug attachment.topic.slug attachment.slug %}" class="btn btn-primary block place-self-end"><i class="fa-solid fa-pen-to-square mr-2"></i>Beschreibung bearbeiten</a> <a href="{% url 'intern:attachment_update' attachment.topic.topic_group.slug attachment.topic.slug attachment.slug %}" class="btn btn-primary block place-self-end"><i class="fa-solid fa-pen-to-square mr-2"></i>Beschreibung bearbeiten</a>
</div> </div>
</main> </main>
{% endblock %} {% endblock %}

View File

@@ -25,14 +25,14 @@
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-top opacity-100 translate-x-0" x-transition:leave-start="transform origin-top opacity-100 translate-x-0"
x-transition:leave-end="transform origin-top opacity-0 -translate-x-6" x-transition:leave-end="transform origin-top opacity-0 -translate-x-6"
><a href="{% url 'intern:topic-create' topic_group.grouper.slug %}"><i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen</a></button> ><a href="{% url 'intern:topic_create' topic_group.grouper.slug %}"><i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen</a></button>
</div> </div>
<ul class="ml-7 w-fit" x-show="getExpandList" x-collapse> <ul class="ml-7 w-fit" x-show="getExpandList" x-collapse>
{% for topic in topic_group.list %} {% for topic in topic_group.list %}
<li><a href="{% url 'intern:topic' topic.topic_group.slug topic.slug %}" class="w-full py-1 inline-block">{{ topic.title }}</a></li> <li><a href="{% url 'intern:topic' topic.topic_group.slug topic.slug %}" class="w-full py-1 inline-block">{{ topic.title }}</a></li>
{% endfor %} {% endfor %}
<li class="py-1"> <li class="py-1">
<a href="{% url 'intern:topic-create' topic_group.grouper.slug %}" class="border border-gray-700 dark:border-gray-300 rounded px-1.5 py-1 text-sm sm:hidden"> <a href="{% url 'intern:topic_create' topic_group.grouper.slug %}" class="border border-gray-700 dark:border-gray-300 rounded px-1.5 py-1 text-sm sm:hidden">
<i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen <i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen
</a> </a>
</li> </li>
@@ -57,11 +57,11 @@
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-top opacity-100 translate-x-0" x-transition:leave-start="transform origin-top opacity-100 translate-x-0"
x-transition:leave-end="transform origin-top opacity-0 -translate-x-6" x-transition:leave-end="transform origin-top opacity-0 -translate-x-6"
><a href="{% url 'intern:topic-create' topic_group.slug %}"><i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen</a></button> ><a href="{% url 'intern:topic_create' topic_group.slug %}"><i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen</a></button>
</div> </div>
<ul class="ml-7 w-fit" x-show="getExpandList" x-collapse> <ul class="ml-7 w-fit" x-show="getExpandList" x-collapse>
<li class="py-1"> <li class="py-1">
<a href="{% url 'intern:topic-create' topic_group.slug %}" class="border border-gray-700 dark:border-gray-300 rounded px-1.5 py-1 text-sm sm:hidden"> <a href="{% url 'intern:topic_create' topic_group.slug %}" class="border border-gray-700 dark:border-gray-300 rounded px-1.5 py-1 text-sm sm:hidden">
<i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen <i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen
</a> </a>
</li> </li>

View File

@@ -24,7 +24,7 @@
<ul class="flex flex-col gap-1 max-w-fit"> <ul class="flex flex-col gap-1 max-w-fit">
{% for task in tasks %} {% for task in tasks %}
<li> <li>
<span class="ml-2">{{ task.title|truncatechars:45 }} <a href="{% url 'tasks:task-detail' task.slug %}" class="inline-block text-proprietary dark:text-proprietary-lighter">Link <i class="fa-solid fa-link"></i></a></span> <span class="ml-2">{{ task.title|truncatechars:45 }} <a href="{% url 'tasks:task' task.slug %}" class="inline-block text-proprietary dark:text-proprietary-lighter">Link <i class="fa-solid fa-link"></i></a></span>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -36,16 +36,16 @@
{% endfor %} {% endfor %}
<li class="mt-2"> <li class="mt-2">
<a href="{% url 'intern:attachment-create' topic.topic_group.slug topic.slug %}" class="border border-gray-700 dark:border-gray-300 rounded px-1.5 py-1"> <a href="{% url 'intern:attachment_create' topic.topic_group.slug topic.slug %}" class="border border-gray-700 dark:border-gray-300 rounded px-1.5 py-1">
<i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen <i class="fa-solid fa-plus mr-1"></i>Eintrag hinzufügen
</a> </a>
</li> </li>
</ul> </ul>
<section class="flex flex-col sm:flex-row justify-end gap-4"> <section class="flex flex-col sm:flex-row justify-end gap-4">
<a href="{% url 'intern:topic-update' topic.topic_group.slug topic.slug %}" class="btn btn-primary block"><i class="fa-solid fa-pen-to-square mr-2"></i>Thema bearbeiten</a> <a href="{% url 'intern:topic_update' topic.topic_group.slug topic.slug %}" class="btn btn-primary block"><i class="fa-solid fa-pen-to-square mr-2"></i>Thema bearbeiten</a>
{% if topic.task_list %} {% if topic.task_list %}
<a href="{% url 'intern:task-create' topic.topic_group.slug topic.slug %}" class="btn btn-primary block"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a> <a href="{% url 'intern:task_create' topic.topic_group.slug topic.slug %}" class="btn btn-primary block"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a>
{% endif %} {% endif %}
</section> </section>
</div> </div>

View File

@@ -1,6 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load flatpages %} {% load flatpages %}
{% load job_groups %}
{% load softhyphen_tags %} {% load softhyphen_tags %}
{% load static %} {% load static %}
@@ -41,9 +42,7 @@
</div> </div>
</div> </div>
<ul class="sideBarNav"> <ul class="sideBarNav">
{% for job in job_groups %} {% get_jobs_sidebar request.resolver_match.kwargs.slug %}
{% include 'members/partials/_jobs_side_bar.html' %}
{% endfor %}
<hr class=""> <hr class="">

View File

@@ -1,15 +0,0 @@
<li class="{% if job.slug in request.path %}active{% endif %}">
<a href="{% url 'members:jobs' job.slug %}">{{ job.name }}</a>
</li>
{% if job.slug == active_job_group.slug %}
{% regroup job_members by job.name as all_jobmem_list %}
<ul class="scrollSpyNav">
{% for jobmem in all_jobmem_list %}
<li class="{% if jobmem.grouper %}active{% endif %}">
<a href="#{{ jobmem.list.0.job.slug }}">{{ jobmem.grouper }}</a>
</li>
{% endfor %}
</ul>
{% endif %}

View File

@@ -0,0 +1,17 @@
{% for job in job_groups %}
<li class="{% if job.slug == active_job_group.slug %}active{% endif %}">
<a href="{% url 'members:jobs' job.slug %}">{{ job.name }}</a>
</li>
{% if job.slug == active_job_group.slug %}
{% regroup job_members by job.name as all_jobmem_list %}
<ul class="scrollSpyNav">
{% for jobmem in all_jobmem_list %}
<li>
<a href="#{{ jobmem.list.0.job.slug }}">{{ jobmem.grouper }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}

View File

@@ -0,0 +1,107 @@
{% extends 'posts/show.html' %}
{% load post_helpers %}
{% block title %}{{ post.title }}{% endblock %}
{% block prev_text_big %}Vorheriges<br>Event{% endblock %}
{% block next_text_big %}Nächstes<br>Event{% endblock %}
{% block prev_text %}Vorheriges Event{% endblock %}
{% block next_text %}Nächstes Event{% endblock %}
{% block update_button_desktop %}
{% if request.user.is_authenticated %}
<a href="{% url 'posts:post_update' post.slug %}" class="hidden sm:block btn-small btn-primary">
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a>
{% endif %}
{% endblock %}
{% block event_details_desktop %}
<div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-900 rounded-bl p-2 bg-opacity-80 dark:bg-opacity-70 gap-2">
<div class="items-center lg:flex gap-2">
<i class="flex-none fa-solid fa-calendar-day fa-fw text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr<br>
Event-Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr
</span>
</div>
{% if post.event_place %}
<div class="items-center lg:flex gap-2">
<i class="flex-none fa-solid fa-location-dot fa-fw text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Ort: {{ post.event_place }}
</span>
</div>
{% endif %}
</div>
<div class="hidden absolute top-0 right-0 bg-white dark:bg-gray-900 rounded-bl p-2 bg-opacity-80 dark:bg-opacity-70 items-center gap-2">
<i class="flex-none fa-solid fa-calendar-day text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr<br>
Event-Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr<br>
{% if post.event_place %}
Event-Ort: {{ post.event_place }}
{% endif %}
</span>
</div>
{% endblock %}
{% block post_body %}
{% if post.body %}
{{ post.body|safe|tags_to_url }}
{% endif %}
{% endblock %}
{% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<div class="lg:hidden">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-calendar-days mr-2 text-gray-400 dark:text-gray-500"></i>Termindetails:</h2>
<ul class="text-base text-gray-700 dark:text-gray-300 my-1">
<li>Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr</li>
<li>Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr</li>
{% if post.event_place %}
<li>Ort: {{ post.event_place }}</li>
{% endif %}
</ul>
</div>
{% endblock %}
{% block files_buttons %}
{% for file in files %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">{{ file.title }}</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ file.file_field.url }}" class="inline-flex items-center px-2 py-1" target="_blank">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
</ul>
</div>
</div>
{% endfor %}
{% endblock %}
{% block update_button_mobile %}
{% if request.user.is_authenticated %}
<a href="{% url 'posts:post_update' post.slug %}" class="sm:hidden block w-full btn btn-primary mt-4">
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ object.title }} bearbeiten{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">{{ object.title }} von {{ object.date }} bearbeiten</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% include "baseform/non_field_errors.html" %}
{% include "baseform/text.html" with field=form.title %}
{% include "baseform/select.html" with field=form.status %}
{% include "baseform/date_time.html" with field=form.event_start %}
{% include "baseform/date_time.html" with field=form.event_end %}
{% include "baseform/text.html" with field=form.event_place %}
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
<a href="{% url 'admin:posts_event_change' object.id %}" class="block btn btn-secondary-proprietary">Event im Admin bearbeiten</a>
<input type="submit" class="block btn btn-primary" value="Bearbeiten">
</div>
</form>
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Fachschaftssitzung erstellen{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">Fachschaftssitzung erstellen</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% include "baseform/non_field_errors.html" %}
{% include "baseform/date_time.html" with field=form.event_start %}
{% include "baseform/date_time.html" with field=form.event_end %}
{% include "baseform/text.html" with field=form.event_place %}
<input type="hidden" class="ui-autocomplete-input" name="tags" value="{% for elem in form.tags.value %}{{ elem.name }}, {% endfor %}" id="id_tags" autocomplete="off">
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
<input type="submit" class="block btn btn-primary" value="Hinzufügen">
</div>
</form>
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,222 @@
{% extends 'posts/show.html' %}
{% load flatpages %}
{% block title %}{{ post.title }} vom {{ post.event_start|date }}{% endblock %}
{% block prev_text_big %}Vorherige<br>Sitzung{% endblock %}
{% block next_text_big %}Nächste<br>Sitzung{% endblock %}
{% block prev_text %}Vorherige Sitzung{% endblock %}
{% block next_text %}Nächste Sitzung{% endblock %}
{% block update_button_desktop %}
{% if request.user.is_authenticated %}
<a href="{% url 'posts:post_update' post.slug %}" class="hidden sm:block btn-small btn-primary">
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a>
{% endif %}
{% endblock %}
{% block event_details_desktop %}
<div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-900 rounded-bl p-2 bg-opacity-80 dark:bg-opacity-70 gap-2">
<div class="items-center lg:flex gap-2">
<i class="flex-none fa-solid fa-calendar-day fa-fw text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr<br>
Event-Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr
</span>
</div>
{% if post.event_place %}
<div class="items-center lg:flex gap-2">
<i class="flex-none fa-solid fa-location-dot fa-fw text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Ort: {{ post.event_place }}
</span>
</div>
{% endif %}
</div>
<div class="hidden absolute top-0 right-0 bg-white dark:bg-gray-900 rounded-bl p-2 bg-opacity-80 dark:bg-opacity-70 items-center gap-2">
<i class="flex-none fa-solid fa-calendar-day text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr<br>
Event-Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr<br>
{% if post.event_place %}
Event-Ort: {{ post.event_place }}
{% endif %}
</span>
</div>
{% endblock %}
{% block post_body %}
{% if post.has_agenda %}
<h2>Agenda</h2>
{{ post.agenda_html|safe }}
{% endif %}
{% if request.user.is_authenticated and post.has_protocol %}
<hr>
<h2>Protokoll</h2>
{{ post.protocol_html|safe }}
{% endif %}
{% endblock %}
{% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<div class="lg:hidden">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-calendar-days mr-2 text-gray-400 dark:text-gray-500"></i>Termindetails:</h2>
<ul class="text-base text-gray-700 dark:text-gray-300 my-1">
<li>Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr</li>
<li>Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr</li>
{% if post.event_place %}
<li>Ort: {{ post.event_place }}</li>
{% endif %}
</ul>
</div>
{% endblock %}
{% block docu_buttons %}
{% if request.user.is_authenticated %}
{% if post.has_agenda or post.has_protocol %}
<hr class="-mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-inbox mr-2 text-gray-400 dark:text-gray-500"></i>Dokument(e):</h2>
{% endif %}
{% if post.has_agenda %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">Agenda</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ post.agenda_link }}" class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-signature fa-fw text-proprietary dark:text-proprietary-light md:text-inherit group-hover:text-proprietary dark:group-hover:text-proprietary-light"></i>
<span class="ml-2 sm:ml-1">Bearbeiten</span>
</a>
</li>
{% if post.filename_agenda %}
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{% url 'posts:show_pdf_agenda' post.slug %}" class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% if post.has_protocol %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">Protokoll</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ post.protocol_link }}" class="inline-flex items-center px-2 py-1"><i class="fa-solid fa-file-signature fa-fw text-proprietary dark:text-proprietary-light md:text-inherit group-hover:text-proprietary dark:group-hover:text-proprietary-light"></i>
<span class="ml-2 sm:ml-1">Bearbeiten</span>
</a>
</li>
{% if post.filename_protocol %}
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{% url 'posts:show_pdf_protocol' post.slug %}" class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% get_flatpages '/bs/' for user as pages %}
{% if pages %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">{{ pages.first.title }}</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ pages.first.url }}" class="inline-flex items-center px-2 py-1"><i class="fa-solid fa-file-lines fa-fw text-proprietary dark:text-proprietary-light md:text-inherit group-hover:text-proprietary dark:group-hover:text-proprietary-light"></i>
<span class="ml-2 sm:ml-1">Übersicht</span>
</a>
</li>
</ul>
</div>
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block files_buttons %}
{% for file in files %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">{{ file.title }}</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ file.file_field.url }}" class="inline-flex items-center px-2 py-1" target="_blank">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
</ul>
</div>
</div>
{% endfor %}
{% endblock %}
{% block update_button_mobile %}
{% if request.user.is_authenticated %}
<a href="{% url 'posts:post_update' post.slug %}" class="sm:hidden block w-full btn btn-primary mt-4">
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ object.title }} bearbeiten{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">{{ object.title }} von {{ object.date }} bearbeiten</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% include "baseform/non_field_errors.html" %}
{% include "baseform/date_time.html" with field=form.event_start %}
{% include "baseform/date_time.html" with field=form.event_end %}
{% include "baseform/text.html" with field=form.event_place %}
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
<a href="{% url 'admin:posts_fetmeeting_change' object.id %}" class="block btn btn-secondary-proprietary">Fachschaftssitzung im Admin bearbeiten</a>
<input type="submit" class="block btn btn-primary" value="Bearbeiten">
</div>
</form>
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,62 @@
{% extends 'posts/show.html' %}
{% load post_helpers %}
{% block title %}{{ post.title }}{% endblock %}
{% block update_button_desktop %}
{% if request.user.is_authenticated %}
<a href="{% url 'posts:post_update' post.slug %}" class="hidden sm:block btn-small btn-primary">
<i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten
</a>
{% endif %}
{% endblock %}
{% block post_body %}
{% if post.body %}
{{ post.body|safe|tags_to_url }}
{% endif %}
{% endblock %}
{% block files_buttons %}
{% if files %}
<hr class="-mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-inbox mr-2 text-gray-400 dark:text-gray-500"></i>Dokument(e):</h2>
{% for file in files %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">{{ file.title }}</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ file.file_field.url }}" class="inline-flex items-center px-2 py-1" target="_blank">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
</ul>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}
{% block update_button_mobile %}
{% if request.user.is_authenticated %}
<a href="{% url 'posts:post_update' post.slug %}" class="sm:hidden block w-full btn btn-primary mt-4">
<i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten
</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% block title %}{{ object.title }} bearbeiten{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1">
<h1 class="page-title">News '{{ object.title }}' bearbeiten</h1>
<div class="w-full h-full flex-1 flex justify-center items-center">
<form action="" method="POST" class="w-full max-w-prose sm:px-28 sm:py-4 grid grid-cols-1 gap-y-3 sm:gap-y-6 text-gray-900">
{% csrf_token %}
{% include "baseform/non_field_errors.html" %}
{% include "baseform/text.html" with field=form.title %}
{% include "baseform/select.html" with field=form.status %}
{% include "baseform/body.html" with field=form.body media=form.media %}
<div class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4 sm:pt-0">
<a href="{% url 'admin:posts_news_change' object.id %}" class="block btn btn-secondary-proprietary">News im Admin bearbeiten</a>
<input type="submit" class="block btn btn-primary" value="Bearbeiten">
</div>
</form>
</div>
</main>
{% endblock %}

View File

@@ -1,10 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load flatpages %}
{% load post_helpers %} {% load post_helpers %}
{% block title %}News{% endblock %}
{% block extraheader %} {% block extraheader %}
<meta content="{{ post.imageurl }}" property="og:image"> <meta content="{{ post.imageurl }}" property="og:image">
<meta content="{{ post.title }}" property="og:title"> <meta content="{{ post.title }}" property="og:title">
@@ -15,37 +12,37 @@
{% block content %} {% block content %}
<!-- Main Content --> <!-- Main Content -->
<main class="container mx-auto w-full flex-1 my-8 sm:flex flex-col sm:px-4"> <main class="container mx-auto w-full flex-1 my-8 sm:flex flex-col sm:px-4">
<a href="{% url 'posts:show' previous %}" class="hidden z-20 fixed left-0 top-1/2 -mt-8 p-2 xl:flex items-center text-gray-400 dark:text-gray-300 rounded-md" <a href="{% url 'posts:post' previous %}" class="hidden z-20 fixed left-0 top-1/2 -mt-8 p-2 xl:flex items-center text-gray-400 dark:text-gray-300 rounded-md"
x-data="prevArticleButton" x-data="prevArticleButton"
@mouseleave="closeShowPrevArticleButton" @mouseleave="closeShowPrevArticleButton"
@mouseover="openShowPrevArticleButton" @mouseover="openShowPrevArticleButton"
> >
<i class="fa-solid fa-chevron-left text-5xl -m-2 p-2 bg-gray-100 dark:bg-gray-700 rounded-md"></i> <i class="fa-solid fa-chevron-left text-5xl -m-2 p-2 bg-gray-100 dark:bg-gray-700 rounded-md"></i>
<span class="text-gray-600 dark:text-gray-300 font-medium bg-gray-100 dark:bg-gray-700 -m-2 p-2 rounded-r-md origin-left" <span class="text-gray-600 dark:text-gray-300 font-medium bg-gray-100 dark:bg-gray-700 -m-2 p-2 rounded-r-md origin-left"
x-show="getShowPrevArticleButton" x-show="getShowPrevArticleButton"
x-transition:enter="transition ease-out duration-300" x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 bg-opacity-0 transform scale-90" x-transition:enter-start="opacity-0 bg-opacity-0 transform scale-90"
x-transition:enter-end="opacity-100 transform scale-100" x-transition:enter-end="opacity-100 transform scale-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-start="opacity-100 transform scale-100"
x-transition:leave-end="opacity-0 bg-opacity-0 transform scale-100" x-transition:leave-end="opacity-0 bg-opacity-0 transform scale-100"
>Vorheriger<br>Artikel</span> >{% block prev_text_big %}Vorheriger<br>Artikel{% endblock %}</span>
</a> </a>
<a href="{% url 'posts:show' next %}" class="hidden z-20 fixed right-0 top-1/2 -mt-8 p-2 xl:flex items-center text-gray-400 dark:text-gray-300 rounded-md" <a href="{% url 'posts:post' next %}" class="hidden z-20 fixed right-0 top-1/2 -mt-8 p-2 xl:flex items-center text-gray-400 dark:text-gray-300 rounded-md"
x-data="nextArticleButton" x-data="nextArticleButton"
@mouseleave="closeShowNextArticleButton" @mouseleave="closeShowNextArticleButton"
@mouseover="openShowNextArticleButton" @mouseover="openShowNextArticleButton"
> >
<span class="z-30 text-gray-600 dark:text-gray-300 font-medium bg-gray-100 dark:bg-gray-700 -m-2 p-2 rounded-l-md text-right origin-right" <span class="z-30 text-gray-600 dark:text-gray-300 font-medium bg-gray-100 dark:bg-gray-700 -m-2 p-2 rounded-l-md text-right origin-right"
x-show="getShowNextArticleButton" x-show="getShowNextArticleButton"
x-transition:enter="transition ease-out duration-300" x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 bg-opacity-0 transform scale-90" x-transition:enter-start="opacity-0 bg-opacity-0 transform scale-90"
x-transition:enter-end="opacity-100 transform scale-100" x-transition:enter-end="opacity-100 transform scale-100"
x-transition:leave="transition ease-in duration-150" x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-start="opacity-100 transform scale-100"
x-transition:leave-end="opacity-0 bg-opacity-0 transform scale-100" x-transition:leave-end="opacity-0 bg-opacity-0 transform scale-100"
>Nächster<br>Artikel</span> >{% block next_text_big %}Nächster<br>Artikel{% endblock %}</span>
<i class="fa-solid fa-chevron-right text-5xl -m-2 p-2 bg-gray-100 dark:bg-gray-700 rounded-md"></i> <i class="fa-solid fa-chevron-right text-5xl -m-2 p-2 bg-gray-100 dark:bg-gray-700 rounded-md"></i>
</a> </a>
<section> <section>
<div class="mb-4 flex flex-col sm:flex-col gap-2 mx-auto"> <div class="mb-4 flex flex-col sm:flex-col gap-2 mx-auto">
@@ -54,300 +51,75 @@
<li class="inline-block py-1 px-2 bg-sky-100 dark:bg-sky-900 rounded-full"><a href="{% url 'posts:tags' t %}">#{{ t }}</a></li> <li class="inline-block py-1 px-2 bg-sky-100 dark:bg-sky-900 rounded-full"><a href="{% url 'posts:tags' t %}">#{{ t }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
<h1 class="px-4 sm:px-0 text-lg sm:text-xl lg:text-3xl text-center sm:text-left font-medium text-gray-900 dark:text-gray-100 font-serif tracking-wider leading-normal" style="line-height: 1.5;">{{ post.title|tags_to_url }}</h1> <h1 class="px-4 sm:px-0 text-lg sm:text-xl lg:text-3xl text-center sm:text-left font-medium text-gray-900 dark:text-gray-100 font-serif tracking-wider leading-normal" style="line-height: 1.5;">{{ post.title }}</h1>
<div class="mx-auto max-w-max sm:mx-0 sm:max-w-none sm:flex justify-between items-center"> <div class="mx-auto max-w-max sm:mx-0 sm:max-w-none sm:flex justify-between items-center">
<div class="max-w-max flex flex-row justify-center sm:justify-start gap-2 self-center md:self-start"> <div class="max-w-max flex flex-row justify-center sm:justify-start gap-2 self-center md:self-start">
{% if author_image and author %} {% if author_image and author %}
<img class="hidden sm:block w-12 rounded-full" src="{{ author_image }}" alt="Portraitfoto von {{ author.firstname }}"> <img class="hidden sm:block w-12 rounded-full" src="{{ author_image }}" alt="Portraitfoto von {{ author.firstname }}">
<div class="sm:flex flex-col justify-evenly text-gray-600 dark:text-gray-300 text-sm sm:text-base"> <div class="sm:flex flex-col justify-evenly text-gray-600 dark:text-gray-300 text-sm sm:text-base">
<a href="{% url 'members:member' author.id %}" class="underline">{{ author.firstname }}</a> <a href="{% url 'members:member' author.id %}" class="underline">{{ author.firstname }}</a>
<span class="sm:hidden"> am </span> <span class="sm:hidden"> am </span>
<span>{{ post.date|date:"d. F Y" }}</span> <span>{{ post.date|date:"d. F Y" }}</span>
</div> </div>
{% elif post.author %} {% elif post.author %}
<div class="sm:flex flex-col justify-evenly text-gray-600 dark:text-gray-300 text-sm sm:text-base"> <div class="sm:flex flex-col justify-evenly text-gray-600 dark:text-gray-300 text-sm sm:text-base">
<a class="underline">{{ post.author|capfirst }}</a> <a class="underline">{{ post.author|capfirst }}</a>
<span class="sm:hidden"> am </span> <span class="sm:hidden"> am </span>
<span>{{ post.date|date:"d. F Y" }}</span> <span>{{ post.date|date:"d. F Y" }}</span>
</div> </div>
{% else %} {% else %}
<div class="sm:flex flex-col justify-evenly text-gray-600 dark:text-gray-300 text-sm sm:text-base"> <div class="sm:flex flex-col justify-evenly text-gray-600 dark:text-gray-300 text-sm sm:text-base">
<a class="underline">fet.at Redaktion</a> <a class="underline">fet.at Redaktion</a>
<span class="sm:hidden"> am </span> <span class="sm:hidden"> am </span>
<span>{{ post.date|date:"d. F Y" }}</span> <span>{{ post.date|date:"d. F Y" }}</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if request.user.is_authenticated %} {% block update_button_desktop %}
{% if post.post_type == 'N' %} {% endblock %}
<a href="{% url 'admin:posts_news_change' post.id %}" class="hidden sm:block btn-small btn-primary">
<i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten
</a>
{% elif post.post_type == 'E' %}
<a href="{% url 'admin:posts_event_change' post.id %}" class="hidden sm:block btn-small btn-primary">
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a>
{% elif post.post_type == 'F' %}
<a href="{% url 'admin:posts_fetmeeting_change' post.id %}" class="hidden sm:block btn-small btn-primary">
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a>
{% endif %}
{% endif %}
</div> </div>
</div> </div>
<!-- <img src="img/article-cover-3.jpg" alt="" class="h-44 sm:h-56 lg:h-64 xl:h-80 w-full object-cover sm:rounded-md max-w-5xl mx-auto"> -->
<div class="relative w-full h-44 sm:h-56 md:h-60 lg:h-64 xl:h-96 bg-center bg-no-repeat bg-cover sm:rounded-md mx-auto" style="background-image: url('{{ post.imageurl }}');"> <div class="relative w-full h-44 sm:h-56 md:h-60 lg:h-64 xl:h-96 bg-center bg-no-repeat bg-cover sm:rounded-md mx-auto" style="background-image: url('{{ post.imageurl }}');">
{% if post.post_type != 'N' %} {% block event_details_desktop %}
<div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-900 rounded-bl p-2 bg-opacity-80 dark:bg-opacity-70 gap-2"> {% endblock %}
<div class="items-center lg:flex gap-2">
<i class="flex-none fa-solid fa-calendar-day fa-fw text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr<br>
Event-Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr
</span>
</div>
{% if post.event_place %}
<div class="items-center lg:flex gap-2">
<i class="flex-none fa-solid fa-location-dot fa-fw text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Ort: {{ post.event_place }}
</span>
</div>
{% endif %}
</div>
<div class="hidden absolute top-0 right-0 bg-white dark:bg-gray-900 rounded-bl p-2 bg-opacity-80 dark:bg-opacity-70 items-center gap-2">
<i class="flex-none fa-solid fa-calendar-day text-gray-800 dark:text-gray-200"></i>
<span class="flex-1 text-sm text-gray-800 dark:text-gray-200">
Event-Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr<br>
Event-Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr<br>
{% if post.event_place %}
Event-Ort: {{ post.event_place }}
{% endif %}
</span>
</div>
{% endif %}
</div> </div>
</section> </section>
<section class="mx-4 z-10"> <section class="mx-4 z-10">
<article class="p-4 mt-4 sm:-mt-16 xl:-mt-24 w-full max-w-prose mx-auto bg-white dark:bg-gray-900 rounded dark:border-2 dark:border-gray-800"> <article class="p-4 mt-4 sm:-mt-16 xl:-mt-24 w-full max-w-prose mx-auto bg-white dark:bg-gray-900 rounded dark:border-2 dark:border-gray-800">
<!-- <div class="w-full flex justify-end">
<div class="hidden lg:block max-w-max text-sm text-gray-600">
Event-Start: 23. August 2021 um 18:00 Uhr<br>
Event-Ende: 23. August 2021 um 20:00 Uhr
</div>
</div> -->
<div class="db-page-content-left big-first-letter"> <div class="db-page-content-left big-first-letter">
<!-- Content from DB here: --> <!-- Content from DB here: -->
{% if post.has_agenda %} {% block post_body %}
<h2>Agenda</h2> {% endblock %}
{{ post.agenda_html|safe }}
{% elif post.body %}
{{ post.body|safe|tags_to_url }}
{% endif %}
{% if request.user.is_authenticated and post.has_protocol %}
<hr>
<h2>Protokoll</h2>
{{ post.protocol_html|safe }}
{% endif %}
</div> </div>
{% if post.post_type != 'N' %} {% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4"> {% endblock %}
<div class="lg:hidden">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-calendar-days mr-2 text-gray-400 dark:text-gray-500"></i>Termindetails:</h2>
<ul class="text-base text-gray-700 dark:text-gray-300 my-1">
<li>Start: {{ post.event_start|date }} um {{ post.event_start|time }} Uhr</li>
<li>Ende: {{ post.event_end|date }} um {{ post.event_end|time }} Uhr</li>
{% if post.event_place %}
<li>Ort: {{ post.event_place }}</li>
{% endif %}
</ul>
</div>
{% endif %}
{% if post.has_agenda or post.has_protocol %} {% block docu_buttons %}
{% if request.user.is_authenticated %} {% endblock %}
<hr class="-mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-inbox mr-2 text-gray-400 dark:text-gray-500"></i>Dokument(e):</h2>
{% endif %}
{% elif files %}
<hr class="-mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<h2 class="text-gray-800 dark:text-gray-200 font-medium"><i class="fa-solid fa-inbox mr-2 text-gray-400 dark:text-gray-500"></i>Dokument(e):</h2>
{% endif %}
{% if request.user.is_authenticated %} {% block files_buttons %}
{% if post.has_agenda %} {% endblock %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">Agenda</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ post.agenda_link }}" class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-signature fa-fw text-proprietary dark:text-proprietary-light md:text-inherit group-hover:text-proprietary dark:group-hover:text-proprietary-light"></i>
<span class="ml-2 sm:ml-1">Bearbeiten</span>
</a>
</li>
{% if post.filename_agenda %}
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{% url 'posts:show_pdf_agenda' post.slug %}" class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% if post.has_protocol %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">Protokoll</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ post.protocol_link }}" class="inline-flex items-center px-2 py-1"><i class="fa-solid fa-file-signature fa-fw text-proprietary dark:text-proprietary-light md:text-inherit group-hover:text-proprietary dark:group-hover:text-proprietary-light"></i>
<span class="ml-2 sm:ml-1">Bearbeiten</span>
</a>
</li>
{% if post.filename_protocol %}
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{% url 'posts:show_pdf_protocol' post.slug %}" class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
{% endif %}
{% if post.post_type == 'F' %}
{% get_flatpages '/bs/' for user as pages %}
{% if pages %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">{{ pages.first.title }}</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ pages.first.url }}" class="inline-flex items-center px-2 py-1"><i class="fa-solid fa-file-lines fa-fw text-proprietary dark:text-proprietary-light md:text-inherit group-hover:text-proprietary dark:group-hover:text-proprietary-light"></i>
<span class="ml-2 sm:ml-1">Übersicht</span>
</a>
</li>
</ul>
</div>
</div>
{% endif %}
{% endif %}
{% endif %}
{% for file in files %}
<div class="w-full my-2 flex items-center gap-4 text-gray-700 dark:text-gray-300" x-data="options">
<span class="flex-1">{{ file.title }}</span>
<div class="relative">
<button class="sm:hidden px-2 py-1 border border-gray-300 dark:border-gray-700 rounded" @click="openShowOptions">
<i class="fa-solid fa-ellipsis-vertical fa-fw"></i>
</button>
<ul class="z-10 absolute top-0 right-0 sm:flex flex-row sm:static flex-none border dark:border-2 border-gray-300 dark:border-gray-700 rounded divide-y-2 sm:divide-y-0 sm:divide-x divide-gray-300 dark:divide-gray-700 bg-gray-100 dark:bg-gray-800 shadow sm:bg-transparent sm:shadow-none"
@click.outside="closeShowOptions"
x-show="getShowOptions"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform origin-right opacity-0 scale-95"
x-transition:enter-end="transform origin-right opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="transform origin-right opacity-100 scale-100"
x-transition:leave-end="transform origin-right opacity-0 scale-95"
>
<li class="block sm:inline-block group hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-800 dark:hover:text-gray-200">
<a href="{{ file.file_field.url }}" class="inline-flex items-center px-2 py-1" target="_blank">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1">Download</span>
</a>
</li>
</ul>
</div>
</div>
{% endfor %}
<hr class="-mx-4 border-gray-200 dark:border-gray-800 dark:border my-4"> <hr class="-mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
<div class="-m-4 flex divide-x divide-gray-200 dark:divide-gray-800 dark:divide-x-2 text-sm sm:text-base"> <div class="-m-4 flex divide-x divide-gray-200 dark:divide-gray-800 dark:divide-x-2 text-sm sm:text-base">
<a href="{% url 'posts:show' previous %}" class="w-1/2 p-4 flex items-center gap-2"> <a href="{% url 'posts:post' previous %}" class="w-1/2 p-4 flex items-center gap-2">
<i class="fa-solid fa-chevron-left text-gray-600 dark:text-gray-400"></i> <i class="fa-solid fa-chevron-left text-gray-600 dark:text-gray-400"></i>
<span class="text-gray-700 dark:text-gray-300 font-medium">Vorheriger Artikel</span> <span class="text-gray-700 dark:text-gray-300 font-medium">{% block prev_text %}Vorheriger Artikel{% endblock %}</span>
</a> </a>
<a href="{% url 'posts:show' next %}" class="w-1/2 p-4 flex flex-row-reverse items-center gap-2"> <a href="{% url 'posts:post' next %}" class="w-1/2 p-4 flex flex-row-reverse items-center gap-2">
<i class="fa-solid fa-chevron-right text-gray-600 dark:text-gray-400"></i> <i class="fa-solid fa-chevron-right text-gray-600 dark:text-gray-400"></i>
<span class="text-gray-700 dark:text-gray-300 font-medium">Nächster Artikel</span> <span class="text-gray-700 dark:text-gray-300 font-medium">{% block next_text %}Nächster Artikel{% endblock %}</span>
</a> </a>
</div> </div>
</article> </article>
{% if request.user.is_authenticated %} {% block update_button_mobile %}
{% if post.post_type == 'N' %} {% endblock %}
<a href="{% url 'admin:posts_news_change' post.id %}" class="sm:hidden block w-full btn btn-primary mt-4">
<i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten
</a>
{% elif post.post_type == 'E' %}
<a href="{% url 'admin:posts_event_change' post.id %}" class="sm:hidden block w-full btn btn-primary mt-4">
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a>
{% elif post.post_type == 'F' %}
<a href="{% url 'admin:posts_fetmeeting_change' post.id %}" class="sm:hidden block w-full btn btn-primary mt-4">
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a>
{% endif %}
{% endif %}
</section> </section>
{% if related_posts %} {% if related_posts and related_posts|length > 1 %}
<section class="mx-auto w-full px-4"> <section class="mx-auto w-full px-4">
<h2 class="my-4 sm:my-8 text-proprietary dark:text-proprietary-lighter text-xl text-center uppercase tracking-wider">Weiterlesen</h2> <h2 class="my-4 sm:my-8 text-proprietary dark:text-proprietary-lighter text-xl text-center uppercase tracking-wider">Weiterlesen</h2>
<div class="flex justify-evenly flex-wrap gap-4"> <div class="flex justify-evenly flex-wrap gap-4">

View File

@@ -38,7 +38,7 @@
<i class="fa-solid fa-xmark"></i> <i class="fa-solid fa-xmark"></i>
</div> </div>
</div> </div>
<form action="" method="GET" class="grid grid-cols-1 gap-2 sm:gap-4"> <form action="" method="post" class="grid grid-cols-1 gap-2 sm:gap-4">
<label class="block"> <label class="block">
<span class="text-gray-700 dark:text-gray-200">Task-Gruppe</span> <span class="text-gray-700 dark:text-gray-200">Task-Gruppe</span>
<select id="id_tasklist" name="tasklist" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50"> <select id="id_tasklist" name="tasklist" class="block w-full mt-1 rounded-md border-gray-300 dark:border-none shadow-sm focus:border-none focus:ring focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
@@ -95,7 +95,7 @@
<label class="mb-2 flex justify-between items-start"> <label class="mb-2 flex justify-between items-start">
<div class="inline-flex items-baseline mr-2"> <div class="inline-flex items-baseline mr-2">
<input type="checkbox" name="checkbox" value="{{ task.id }}" {% if task.completed %}checked{% endif %} class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50"> <input type="checkbox" name="checkbox" value="{{ task.id }}" {% if task.completed %}checked{% endif %} class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50">
<span class="ml-2">{{ task.title|truncatechars:45 }} <a href="{% url 'tasks:task-detail' task.slug %}" class="inline-block text-proprietary dark:text-proprietary-lighter">Link <i class="fa-solid fa-link"></i></a></span> <span class="ml-2">{{ task.title|truncatechars:45 }} <a href="{% url 'tasks:task' task.slug %}" class="inline-block text-proprietary dark:text-proprietary-lighter">Link <i class="fa-solid fa-link"></i></a></span>
</div> </div>
{% if task.due_date %} {% if task.due_date %}
<div class="inline-flex gap-2 flex-shrink-0"> <div class="inline-flex gap-2 flex-shrink-0">
@@ -110,7 +110,7 @@
<div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4"> <div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4">
<input type="submit" name="btn_checkbox" value="Tasks abschließen" class="btn btn-success block md:flex-grow lg:flex-grow-0"> <input type="submit" name="btn_checkbox" value="Tasks abschließen" class="btn btn-success block md:flex-grow lg:flex-grow-0">
<a href="{% url 'tasks:task-create' %}" class="btn btn-primary block md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a> <a href="{% url 'tasks:task_create' %}" class="btn btn-primary block md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a>
</div> </div>
</form> </form>
{% else %} {% else %}
@@ -118,7 +118,7 @@
<h2 class="mb-1 text-gray-700 dark:text-gray-200">Keine Tasks in dieser Liste für dich.</h2> <h2 class="mb-1 text-gray-700 dark:text-gray-200">Keine Tasks in dieser Liste für dich.</h2>
<div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4"> <div class="flex flex-col md:flex-row gap-y-2 md:gap-y-0 md:gap-x-2 lg:justify-end mt-4">
<a href="{% url 'tasks:task-create' %}" class="btn btn-primary block md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a> <a href="{% url 'tasks:task_create' %}" class="btn btn-primary block md:flex-grow lg:flex-grow-0"><i class="fa-solid fa-plus-square mr-2"></i>Task hinzufügen</a>
</div> </div>
</section> </section>
{% endif %} {% endif %}

View File

@@ -26,7 +26,7 @@
<section> <section>
<div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600"> <div class="documentList rounded divide-y divide-gray-300 dark:divide-gray-600">
<a href="{% url 'tasks:docu-create' task.slug %}" class="flex justify-between"> <a href="{% url 'tasks:docu_create' task.slug %}" class="flex justify-between">
<h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Etherpad erstellen</h2> <h3 class="text-gray-800 dark:text-gray-200"><i class="fa-solid fa-plus fa-fw mr-1"></i>Neues Etherpad erstellen</h2>
</a> </a>
@@ -40,7 +40,7 @@
</div> </div>
</section> </section>
<a href="{% url 'tasks:task-update' task.slug %}" class="btn btn-primary block place-self-end"><i class="fa-solid fa-pen-to-square mr-2"></i>Task bearbeiten</a> <a href="{% url 'tasks:task_update' task.slug %}" class="btn btn-primary block place-self-end"><i class="fa-solid fa-pen-to-square mr-2"></i>Task bearbeiten</a>
</div> </div>
</main> </main>
{% endblock %} {% endblock %}

View File

@@ -1,8 +1,10 @@
import pytest
from django.urls import reverse
from django.core.validators import ValidationError
import datetime import datetime
from documents.api import ep_client, createPadifNotExists
import pytest
from django.core.validators import ValidationError
from django.urls import reverse
from documents.api import createPadifNotExists, ep_client
class TestEtherpad: class TestEtherpad:

View File

@@ -1,51 +1,61 @@
# pylint: skip-file # pylint: skip-file
import pytest
from posts.models import News, Post, Event, FetMeeting
from django.urls import reverse
from django.core.validators import ValidationError
import datetime import datetime
from urllib.request import URLError
import random import random
import string import string
from urllib.request import URLError
import pytest
from django.core.validators import ValidationError
from django.urls import reverse
from posts.models import Event, FetMeeting, News, Post
def get_random_string(size): def get_random_string(size):
chars = string.ascii_lowercase+string.ascii_uppercase+string.digits chars = string.ascii_lowercase + string.ascii_uppercase + string.digits
return ''.join(random.choice(chars) for _ in range(size)) return "".join(random.choice(chars) for _ in range(size))
@pytest.fixture @pytest.fixture
def mock_createpad(mocker): def mock_createpad(mocker):
create_pad=mocker.patch('posts.models.createPadifNotExists',autospec=True, return_value="slug") create_pad = mocker.patch(
"posts.models.createPadifNotExists", autospec=True, return_value="slug"
)
return create_pad return create_pad
@pytest.fixture @pytest.fixture
def post(db): def post(db):
return Post(title="asdf"+get_random_string(20), post_type="N") return Post(title="asdf" + get_random_string(20), post_type="N")
@pytest.fixture @pytest.fixture
def post_saved(post, mock_createpad): def post_saved(post, mock_createpad):
post.save() post.save()
return post return post
@pytest.fixture @pytest.fixture
def fetmeeting(db): def fetmeeting(db):
return FetMeeting(event_start = datetime.datetime(2020,1,1,18,0)) return FetMeeting(event_start=datetime.datetime(2020, 1, 1, 18, 0))
@pytest.fixture @pytest.fixture
def fetmeeting_saved(fetmeeting): def fetmeeting_saved(fetmeeting):
fetmeeting.save() fetmeeting.save()
return fetmeeting return fetmeeting
def test_true(): def test_true():
assert True assert True
class TestPostModel: class TestPostModel:
def test_model(self, post): def test_model(self, post):
assert isinstance(post,Post) assert isinstance(post, Post)
def test_model_id(self, post_saved): def test_model_id(self, post_saved):
assert post_saved.id==1 assert post_saved.id == 1
def test_model_notnews(self, post): def test_model_notnews(self, post):
assert not isinstance(post, News) assert not isinstance(post, News)
@@ -57,35 +67,34 @@ class TestPostModel:
assert reverse("show", kwargs={"id": post_saved.slug}) == post_saved.url assert reverse("show", kwargs={"id": post_saved.slug}) == post_saved.url
def test_default_hidden(self, post_saved): def test_default_hidden(self, post_saved):
assert post_saved.is_hidden==False assert post_saved.is_hidden == False
def test_default_pinned(self, post_saved): def test_default_pinned(self, post_saved):
assert post_saved.is_pinned==False assert post_saved.is_pinned == False
def test_validate_title(self, post): def test_validate_title(self, post):
post.title="" post.title = ""
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
post.full_clean() post.full_clean()
def test_validate_eventdate(self, post): def test_validate_eventdate(self, post):
post.post_type="E" post.post_type = "E"
post.event_start=datetime.datetime(2020,10,1,18,0) post.event_start = datetime.datetime(2020, 10, 1, 18, 0)
post.event_end=datetime.datetime(2020,10,1,19,0) post.event_end = datetime.datetime(2020, 10, 1, 19, 0)
post.full_clean() post.full_clean()
assert True assert True
def test_validate_dates(self, post):
def test_validate_dates(self,post): post.post_type = "E"
post.post_type="E" post.event_start = datetime.datetime(2020, 10, 1, 19, 0)
post.event_start=datetime.datetime(2020,10,1,19,0) post.event_end = datetime.datetime(2020, 10, 1, 18, 0)
post.event_end=datetime.datetime(2020,10,1,18,0)
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
post.full_clean() post.full_clean()
def test_validate_date2(self,post): def test_validate_date2(self, post):
post.post_type="N" post.post_type = "N"
post.event_start=datetime.datetime(2020,10,1,18,0) post.event_start = datetime.datetime(2020, 10, 1, 18, 0)
post.event_end=datetime.datetime(2020,10,1,19,0) post.event_end = datetime.datetime(2020, 10, 1, 19, 0)
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
post.full_clean() post.full_clean()
@@ -94,31 +103,32 @@ class TestPostModel:
assert True assert True
class TestPostEtherpad: class TestPostEtherpad:
def setUp(self): def setUp(self):
print("running setup") print("running setup")
def test_agenda_id(self,post_saved, mock_createpad): def test_agenda_id(self, post_saved, mock_createpad):
mock_createpad.return_value=post_saved.slug+"-agenda" mock_createpad.return_value = post_saved.slug + "-agenda"
k=post_saved.get_agenda_key() k = post_saved.get_agenda_key()
mock_createpad.assert_called_with(post_saved.slug+"-agenda") mock_createpad.assert_called_with(post_saved.slug + "-agenda")
assert post_saved.slug in str(k) assert post_saved.slug in str(k)
def test_protocol_id(self,post_saved,mock_createpad): def test_protocol_id(self, post_saved, mock_createpad):
mock_createpad.return_value=post_saved.slug+"-protocol" mock_createpad.return_value = post_saved.slug + "-protocol"
k=post_saved.get_protocol_key() k = post_saved.get_protocol_key()
mock_createpad.assert_called_with(post_saved.slug+"-protocol") mock_createpad.assert_called_with(post_saved.slug + "-protocol")
assert post_saved.slug in str(k) assert post_saved.slug in str(k)
def test_catch_url_error(self,post_saved,mock_createpad): def test_catch_url_error(self, post_saved, mock_createpad):
def raise_url_error(key): def raise_url_error(key):
raise URLError("Mocked Etherpad Down") raise URLError("Mocked Etherpad Down")
mock_createpad.side_effect=raise_url_error
k=post_saved.get_protocol_key() mock_createpad.side_effect = raise_url_error
k = post_saved.get_protocol_key()
mock_createpad.assert_called() mock_createpad.assert_called()
assert k == None assert k == None
class TestFetmeetingModel: class TestFetmeetingModel:
def test_clean_fetmeeting(self, fetmeeting, mock_createpad): def test_clean_fetmeeting(self, fetmeeting, mock_createpad):
fetmeeting.full_clean() fetmeeting.full_clean()
@@ -126,31 +136,31 @@ class TestFetmeetingModel:
assert True assert True
def test_clean_fetmeeting(self, fetmeeting): def test_clean_fetmeeting(self, fetmeeting):
fetmeeting.event_start=None fetmeeting.event_start = None
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
fetmeeting.clean() fetmeeting.clean()
def test_clean_fetmeeting(self, fetmeeting): def test_clean_fetmeeting(self, fetmeeting):
fetmeeting.event_start=None fetmeeting.event_start = None
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
fetmeeting.clean() fetmeeting.clean()
#Test Defaults for Fetmeeting # Test Defaults for Fetmeeting
def test_title(self, fetmeeting): def test_title(self, fetmeeting):
fetmeeting.save() fetmeeting.save()
assert fetmeeting.title=="Fachschaftssitzung" assert fetmeeting.title == "Fachschaftssitzung"
def test_slug(self, fetmeeting_saved): def test_slug(self, fetmeeting_saved):
assert "fachschaftssitzung" in fetmeeting_saved.slug assert "fachschaftssitzung" in fetmeeting_saved.slug
def test_slug_fachschaftsitzung(self,fetmeeting_saved): def test_slug_fachschaftsitzung(self, fetmeeting_saved):
assert "2020-01-01" in fetmeeting_saved.slug assert "2020-01-01" in fetmeeting_saved.slug
def test_sitzung_end(self, fetmeeting_saved): def test_sitzung_end(self, fetmeeting_saved):
assert datetime.datetime(2020,1,1,20,0)==fetmeeting_saved.event_end assert datetime.datetime(2020, 1, 1, 20, 0) == fetmeeting_saved.event_end
def test_sitzung_start(self, fetmeeting_saved): def test_sitzung_start(self, fetmeeting_saved):
assert datetime.datetime(2020,1,1,18,0)==fetmeeting_saved.event_start assert datetime.datetime(2020, 1, 1, 18, 0) == fetmeeting_saved.event_start
def test_post_type(self, fetmeeting_saved): def test_post_type(self, fetmeeting_saved):
assert fetmeeting_saved.post_type == "F" assert fetmeeting_saved.post_type == "F"
@@ -161,7 +171,8 @@ class TestFetmeetingModel:
def test_meeting_hasprotocol(self, fetmeeting_saved): def test_meeting_hasprotocol(self, fetmeeting_saved):
assert fetmeeting_saved.has_protocol == True assert fetmeeting_saved.has_protocol == True
class TestPostViews: class TestPostViews:
def test_home(self,post_saved,client,db): def test_home(self, post_saved, client, db):
res=client.get("/").content res = client.get("/").content
assert post_saved.title in str(res) assert post_saved.title in str(res)