Compare commits

14 Commits

89 changed files with 511 additions and 700 deletions

View File

@@ -1,6 +0,0 @@
DEBUG=FALSE
HOST_NAME=uat1.2020.fet.at
SECRET_KEY=235t3groing43qo4ntgigon43qgi3q4gong
MYSQL_USER=user
MYSQL_PASSWORD=hgu
MYSQL_PORT=3306

View File

@@ -1,17 +0,0 @@
{
"folders": [
{
"path": "fet2020"
}
],
"settings": {
"launch": {},
"python.linting.flake8Enabled": true,
"python.linting.banditEnabled": true,
"python.testing.pytestEnabled": true,
"python.linting.flake8Args": [
"--max-line-length=100"
]
}
}

View File

@@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "manage.py",
"console": "integratedTerminal",
"args": [
"runserver",
"0.0.0.0:8000"
],
"django": true
}
]
}

View File

@@ -1,13 +0,0 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"python.pythonPath": "/usr/local/bin/python3",
"python.linting.pylintEnabled": true,
"python.linting.flake8Enabled": true,
"python.linting.banditEnabled": true,
"python.linting.enabled": true
}

View File

@@ -4,116 +4,11 @@ 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 from ldap3.utils.hashed import hashed
from members.models import Member
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
host = "ldap://juri.fet.htu.tuwien.ac.at" host = "ldap://juri.fet.htu.tuwien.ac.at"
port = 389 port = 389
def authentication(username, password):
# no empty passwords
if password is None or password.strip() == "":
return None
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
firstname = ""
surname = ""
mail = ""
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
if not c.extend.standard.who_am_i():
logger.info("Username '%s' is not in the list.", username)
return None
# get member infos from ldap
c.search(
search_base=userdn,
search_filter=f"(uid={username})",
search_scope="SUBTREE",
attributes=["givenName", "sn", "mail"],
)
firstname = c.response[0]["attributes"]["givenName"][0]
surname = c.response[0]["attributes"]["sn"][0]
mail = c.response[0]["attributes"]["mail"][0]
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return None
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return None
# get member or if not then create a new member
try:
member = Member.objects.get(mailaccount=mail)
# set username if not equal
if member.username != username:
member.username = username
logger.info("User '%s' saved.", username)
member.save()
except Member.DoesNotExist:
member = Member()
member.firstname = firstname
member.surname = surname
member.username = username
member.nickname = username
member.mailaccount = mail
logger.info("Member '%s' created.", username)
member.save()
logger.info("User '%s' logged in.", username)
return username
def get_finance_perm(username, password):
# no empty passwords
if password is None or password.strip() == "":
return None
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
finance_perm = None
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
if not c.extend.standard.who_am_i():
logger.info("Username '%s' is not in the list.", username)
return None
# check if member has finance permission
c.search(
search_base="CN=finance,OU=Groups,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at",
search_filter="(objectClass=posixGroup)",
search_scope="SUBTREE",
attributes=["memberUid"],
)
if username in c.response[0]["attributes"]["memberUid"]:
logger.info("User '%s' has finance permission.", username)
finance_perm = True
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return None
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return None
return finance_perm
def change_password(username, old_password, new_password): def change_password(username, old_password, new_password):
server = Server(host, port=port, use_ssl=True) server = Server(host, port=port, 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"

View File

@@ -0,0 +1,161 @@
import logging
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Group, User
from ldap3 import Connection, Server
from ldap3.core.exceptions import LDAPBindError
from members.models import Member
logger = logging.getLogger(__name__)
host = "ldap://juri.fet.htu.tuwien.ac.at"
port = 389
class LdapBackend(ModelBackend):
def _check_ldap_user(self, username: str, password: str) -> bool:
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
if not c.extend.standard.who_am_i():
logger.info("Username '%s' is not in the list.", username)
return False
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return False
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return False
return True
def _check_fet_member(self, username: str, password: str) -> bool:
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
# Get member infos from ldap
c.search(
search_base=userdn,
search_filter=f"(uid={username})",
search_scope="SUBTREE",
attributes=["givenName", "sn", "mail"],
)
firstname = c.response[0]["attributes"]["givenName"][0]
surname = c.response[0]["attributes"]["sn"][0]
mail = c.response[0]["attributes"]["mail"][0]
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return False
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return False
# Try to get the member. If it not exists, then create a new member.
try:
member = Member.objects.get(mailaccount=mail)
# Set username if not equal
if member.username != username:
member.username = username
logger.info("Member '%s' saved.", username)
member.save()
except Member.DoesNotExist:
member = Member()
member.firstname = firstname
member.surname = surname
member.username = username
member.nickname = username
member.mailaccount = mail
logger.info("Member '%s' created.", username)
member.save()
return True
def _check_finance_perm(self, username: str, password: str) -> bool:
server = Server(host, port=port)
userdn = f"uid={username},ou=user,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at"
try:
c = Connection(server, user=userdn, password=password, auto_bind=True)
# Check if member has finance permission
c.search(
search_base="CN=finance,OU=Groups,dc=fet,dc=htu,dc=tuwien,dc=ac,dc=at",
search_filter="(objectClass=posixGroup)",
search_scope="SUBTREE",
attributes=["memberUid"],
)
if username in c.response[0]["attributes"]["memberUid"]:
logger.info("User '%s' has finance permission.", username)
return True
except LDAPBindError as e:
logger.info("LDAP Bind error from username '%s'. Error: %s", username, e)
return False
except Exception as e:
logger.info("Auth exception from username '%s'. Error: %s", username, e)
return False
return False
def authenticate(self, request, username=None, password=None):
if username is None or password is None:
return
# Set username to lower because fet2020 can only work with lowercase letters.
username = username.lower()
if not self._check_ldap_user(username, password):
return
if not self._check_fet_member(username, password):
return
try:
user = User.objects.get(username)
except User.DoesNotExist:
user = User.objects.create_user(username)
if not self.user_can_authenticate(user):
logger.info("User '%s' is inactive.", user.get_username())
return
# Add user to all groups
for elem in Group.objects.all():
elem.user_set.add(user)
# Delete finance group if no permission
if not self._check_finance_perm(username, password):
finance_group = Group.objects.get(name="finance")
finance_group.user_set.remove(user)
return user
class DebugBackend(ModelBackend):
def authenticate(self, request, username, password, **kwargs):
if (user := super().authenticate(request, username, password, **kwargs)) is None:
logger.info("User '%s' can't login. The user may not be a superuser.", username)
return None
# Try to get the member. If it not exists, then create a new member.
try:
member = Member.objects.get(mailaccount=user.email)
except Member.DoesNotExist:
member = Member()
member.username = username
member.nickname = username
member.mailaccount = user.email
logger.info("Member '%s' created.", username)
member.save()
return user

View File

@@ -1,54 +1,16 @@
import logging import logging
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from .authentications import authentication, change_password, get_finance_perm from .authentications import change_password
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class LoginForm(AuthenticationForm): # TODO: fix me
def clean(self):
username = self.cleaned_data.get("username").lower()
password = self.cleaned_data.get("password")
if username is not None and password:
if (auth_user := authentication(username, password)) is None:
raise ValidationError(
"Bitte Benutzername und Passwort korrekt eingeben.",
code="invalid_login",
)
try:
self.user_cache = User.objects.get(username=auth_user.lower())
except User.DoesNotExist:
self.user_cache = User.objects.create_user(auth_user.lower())
finally:
self.confirm_login_allowed(self.user_cache)
# add user to all groups
for elem in Group.objects.all():
elem.user_set.add(self.user_cache)
# delete finance group if no permission
if not get_finance_perm(username, password):
finance_group = Group.objects.get(name="finance")
finance_group.user_set.remove(self.user_cache)
return self.cleaned_data
class LdapPasswordChangeForm(PasswordChangeForm): class LdapPasswordChangeForm(PasswordChangeForm):
def clean_old_password(self): def clean_old_password(self):
old_password = self.cleaned_data.get("old_password") old_password = self.cleaned_data.get("old_password")
if not authentication(self.user, old_password):
raise ValidationError(
self.error_messages["password_incorrect"],
code="password_incorrect",
)
return old_password return old_password
def save(self): def save(self):

View File

@@ -10,11 +10,10 @@ from django.urls import reverse_lazy
from documents.etherpadlib import del_ep_cookie from documents.etherpadlib import del_ep_cookie
from .decorators import authenticated_user from .decorators import authenticated_user
from .forms import LdapPasswordChangeForm, LoginForm from .forms import LdapPasswordChangeForm
class AuthLoginView(LoginView): class AuthLoginView(LoginView):
authentication_form = LoginForm
redirect_authenticated_user = True redirect_authenticated_user = True
template_name = "authentications/login.html" template_name = "authentications/login.html"

View File

@@ -21,7 +21,7 @@ class CustomFlatPageAdmin(FlatPageAdmin):
"url", "url",
"title", "title",
"content", "content",
) ),
}, },
), ),
( (

View File

@@ -6,7 +6,8 @@ class FETHeaderMiddleware(RemoteUserMiddleware):
def process_request(self, request): def process_request(self, request):
request.META[self.header] = request.META.get( request.META[self.header] = request.META.get(
self.header, request.headers.get(self.header, None) self.header,
request.headers.get(self.header, None),
) )
super().process_request(request) super().process_request(request)

View File

@@ -5,7 +5,8 @@ import environ
env = environ.Env( env = environ.Env(
# set casting, default value # set casting, default value
DEBUG=(bool, True), DEBUG=(str, "True"),
LDAP=(str, "False"),
MYSQL_HOST=(str, "mysql"), MYSQL_HOST=(str, "mysql"),
MYSQL_PORT=(int, 3306), MYSQL_PORT=(int, 3306),
MYSQL_DATABASE=(str, "fet2020db"), MYSQL_DATABASE=(str, "fet2020db"),
@@ -23,7 +24,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# DEBUGGING # DEBUGGING
DEBUG = env("DEBUG") DEBUG = env("DEBUG").lower() == "true"
LDAP = env("LDAP").lower() == "true"
# MODELS # MODELS
@@ -63,9 +65,14 @@ INSTALLED_APPS = [
# AUTHENTICATIONS # AUTHENTICATIONS
AUTHENTICATION_BACKENDS = [ if not DEBUG and LDAP:
"django.contrib.auth.backends.ModelBackend", AUTHENTICATION_BACKENDS = [
] "authentications.backends.LdapBackend",
]
else:
AUTHENTICATION_BACKENDS = [
"authentications.backends.DebugBackend",
]
LOGIN_REDIRECT_URL = "home" LOGIN_REDIRECT_URL = "home"
LOGIN_URL = "/auth/login" LOGIN_URL = "/auth/login"
@@ -77,7 +84,7 @@ if DEBUG:
"default": { "default": {
"ENGINE": "django.db.backends.sqlite3", "ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
} },
} }
else: else:
DATABASES = { DATABASES = {
@@ -88,7 +95,7 @@ else:
"PASSWORD": env("MYSQL_PASSWORD"), "PASSWORD": env("MYSQL_PASSWORD"),
"HOST": env("MYSQL_HOST"), "HOST": env("MYSQL_HOST"),
"PORT": env("MYSQL_PORT"), "PORT": env("MYSQL_PORT"),
} },
} }
@@ -99,9 +106,20 @@ EMAIL_PORT = 587
EMAIL_USE_TLS = True EMAIL_USE_TLS = True
# STATIC FILES
STATIC_URL = "static/" if DEBUG else "assets/"
# Use for collectstatic/production folder.
# --- Saving directory for production folder.
STATIC_ROOT = "assets/"
# --- Get files from following directory for production folder.
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
# FILE UPLOADS # FILE UPLOADS
MEDIA_ROOT = os.path.join(BASE_DIR, "files/")
MEDIA_URL = "files/" MEDIA_URL = "files/"
MEDIA_ROOT = os.path.join(BASE_DIR, "files")
# GLOBALIZATION # GLOBALIZATION
@@ -167,20 +185,6 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
SITE_ID = 1 SITE_ID = 1
# STATIC FILES
STATIC_ROOT = "assets/"
if DEBUG:
STATIC_URL = "static/"
else:
STATIC_URL = "assets/"
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "gallery/static"),
os.path.join(BASE_DIR, "static"),
]
# SECURITY # SECURITY
# TODO: Security Warning # TODO: Security Warning
# CSRF_COOKIE_SECURE = True # CSRF_COOKIE_SECURE = True
@@ -188,10 +192,7 @@ CSRF_TRUSTED_ORIGINS = [
"https://" + env("HOST_NAME"), "https://" + env("HOST_NAME"),
] ]
if DEBUG: SECRET_KEY = "r37-i7l)vrduzz2-gira+z#u!p!di9#f+%s*5-bb($hg)55@ns" if DEBUG else env("SECRET_KEY")
SECRET_KEY = "r37-i7l)vrduzz2-gira+z#u!p!di9#f+%s*5-bb($hg)55@ns"
else:
SECRET_KEY = env("SECRET_KEY")
# TEMPLATES # TEMPLATES
@@ -209,6 +210,7 @@ TEMPLATES = [
"django.contrib.auth.context_processors.auth", "django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"django.template.context_processors.i18n", "django.template.context_processors.i18n",
"django.template.context_processors.media",
], ],
}, },
}, },
@@ -350,14 +352,14 @@ if DEBUG:
ETHERPAD_CLIENT = { ETHERPAD_CLIENT = {
"url": "http://etherpad:" + env("ETHERPAD_PORT"), "url": "http://etherpad:" + env("ETHERPAD_PORT"),
"exturl": ETHERPAD_HOST, "exturl": ETHERPAD_HOST,
"apikey": "/srv/etherpad/APIKEY.txt", "apikey": "/etherpad/APIKEY.txt",
"group": env("ETHERPAD_GROUP"), "group": env("ETHERPAD_GROUP"),
} }
else: else:
ETHERPAD_CLIENT = { ETHERPAD_CLIENT = {
"url": "http://etherpad:" + env("ETHERPAD_PORT"), "url": "http://etherpad:" + env("ETHERPAD_PORT"),
"exturl": urljoin("https://" + env("HOST_NAME"), "etherpad/"), "exturl": urljoin("https://" + env("HOST_NAME"), "etherpad/"),
"apikey": "/app/etherpad/APIKEY.txt", "apikey": os.path.join(BASE_DIR, "etherpad/APIKEY.txt"),
"group": env("ETHERPAD_GROUP"), "group": env("ETHERPAD_GROUP"),
} }

View File

@@ -11,11 +11,7 @@ def add_log_action(request, form, app_label, model, add=True):
obj = form.save() obj = form.save()
content_type = ContentType.objects.get(app_label=app_label, model=model) content_type = ContentType.objects.get(app_label=app_label, model=model)
change_message = construct_change_message(form, None, add) change_message = construct_change_message(form, None, add)
action_flag = ADDITION if add else CHANGE
if add:
action_flag = ADDITION
else:
action_flag = CHANGE
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id=request.user.pk, user_id=request.user.pk,

View File

@@ -216,7 +216,7 @@ class BillAdmin(admin.ModelAdmin):
"fields": ( "fields": (
"bill_creator", "bill_creator",
"resolution", "resolution",
) ),
}, },
), ),
( (
@@ -227,7 +227,7 @@ class BillAdmin(admin.ModelAdmin):
"bankdata", "bankdata",
("get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic"), ("get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic"),
"get_qrcode", "get_qrcode",
) ),
}, },
), ),
( (
@@ -241,7 +241,7 @@ class BillAdmin(admin.ModelAdmin):
"amount", "amount",
"only_digital", "only_digital",
"file_field", "file_field",
) ),
}, },
), ),
( (
@@ -251,7 +251,7 @@ class BillAdmin(admin.ModelAdmin):
"comment", "comment",
"wiref", "wiref",
"status", "status",
) ),
}, },
), ),
) )
@@ -308,7 +308,7 @@ class BillAdmin(admin.ModelAdmin):
@admin.display(description="QR Code") @admin.display(description="QR Code")
def get_qrcode(self, obj): def get_qrcode(self, obj):
if obj.status != "C": if obj.status != Bill.Status.CLEARED:
return "-" return "-"
try: try:
@@ -329,24 +329,26 @@ class BillAdmin(admin.ModelAdmin):
return "Daten für QR Code ungültig" return "Daten für QR Code ungültig"
uri = qrcode.png_data_uri(scale=3.0) uri = qrcode.png_data_uri(scale=3.0)
return format_html(f'<img src="{uri}">') return format_html('<img src="{}">', uri)
@admin.display(description="Status") @admin.display(description="Status")
def status_colored(self, obj): def status_colored(self, obj):
# TODO: if there is a status without color, set nothing. # TODO: if there is a status without color, set nothing.
colors = { colors = {
"S": "red", Bill.Status.SUBMITTED: "red",
"C": "darkorange", Bill.Status.CLEARED: "darkorange",
"F": "green", Bill.Status.FINISHED: "green",
"I": "blue", Bill.Status.INCOMPLETED: "blue",
} }
return format_html( return format_html(
f'<b style="background:{colors[obj.status]};">{obj.get_status_display()}</b>' '<b style="background:{color};">{status}</b>',
color=colors[obj.status],
status=obj.get_status_display(),
) )
@admin.action(description="Als 'Abgerechnet' markieren.") @admin.action(description="Als 'Abgerechnet' markieren.")
def make_cleared(self, request, queryset): def make_cleared(self, request, queryset):
updated = queryset.update(status="C") updated = queryset.update(status=Bill.Status.CLEARED)
self.message_user( self.message_user(
request, request,
ngettext( ngettext(
@@ -360,7 +362,7 @@ class BillAdmin(admin.ModelAdmin):
@admin.action(description="Als 'Abgeschlossen' markieren.") @admin.action(description="Als 'Abgeschlossen' markieren.")
def make_finished(self, request, queryset): def make_finished(self, request, queryset):
updated = queryset.update(status="F") updated = queryset.update(status=Bill.Status.FINISHED)
self.message_user( self.message_user(
request, request,
ngettext( ngettext(
@@ -403,7 +405,7 @@ class ResolutionAdmin(admin.ModelAdmin):
"date", "date",
"option", "option",
"is_visible", "is_visible",
) ),
}, },
), ),
( (
@@ -413,7 +415,7 @@ class ResolutionAdmin(admin.ModelAdmin):
"budget", "budget",
"total", "total",
"budget_remaining", "budget_remaining",
) ),
}, },
), ),
( (
@@ -463,7 +465,7 @@ class ResolutionAdmin(admin.ModelAdmin):
if fetmeeting is not None: if fetmeeting is not None:
link = f"https://{settings.HOST_NAME}/posts/{fetmeeting.slug}/" link = f"https://{settings.HOST_NAME}/posts/{fetmeeting.slug}/"
return format_html(f"<a href='{link}' target='_blank'>Link zur Fachschaftssitzung</a>") return format_html('<a href="{}" target="_blank">Link zur Fachschaftssitzung</a>', link)
return format_html("-") return format_html("-")

View File

@@ -29,7 +29,7 @@ def get_cleaned_data(cleaned_data):
if resolution != "": if resolution != "":
try: try:
cleaned_data["resolution"] = Resolution.objects.get( cleaned_data["resolution"] = Resolution.objects.get(
Q(id=resolution) | Q(name=resolution) Q(id=resolution) | Q(name=resolution),
) )
except Exception: except Exception:
raise ValidationError({"resolution_text": "Es gibt keinen Beschluss mit dieser ID."}) raise ValidationError({"resolution_text": "Es gibt keinen Beschluss mit dieser ID."})
@@ -68,7 +68,10 @@ class BillCreateForm(forms.ModelForm):
# Resolution # Resolution
resolution_text = forms.CharField( resolution_text = forms.CharField(
required=False, label="Beschlussnummer", initial="", max_length=128 required=False,
label="Beschlussnummer",
initial="",
max_length=128,
) )
class Meta: class Meta:
@@ -122,7 +125,7 @@ class BillCreateForm(forms.ModelForm):
self.fields["payer"].autofocus = True self.fields["payer"].autofocus = True
bank_data = BankData.objects.filter( bank_data = BankData.objects.filter(
Q(bankdata_creator=member) & Q(is_disabled=False) Q(bankdata_creator=member) & Q(is_disabled=False),
).first() ).first()
if bank_data is not None: if bank_data is not None:
self.fields["name_text"].initial = bank_data.name self.fields["name_text"].initial = bank_data.name
@@ -147,12 +150,17 @@ class BillUpdateForm(forms.ModelForm):
# only digital bill # only digital bill
only_digital_new = forms.BooleanField( only_digital_new = forms.BooleanField(
required=False, label="Ich habe eine neue digitale Rechnung.", initial=False required=False,
label="Ich habe eine neue digitale Rechnung.",
initial=False,
) )
# Resolution # Resolution
resolution_text = forms.CharField( resolution_text = forms.CharField(
required=False, label="Beschlussnummer", initial="", max_length=128 required=False,
label="Beschlussnummer",
initial="",
max_length=128,
) )
class Meta: class Meta:
@@ -398,8 +406,10 @@ class BillAdminForm(forms.ModelForm):
self.fields["bill_creator"].widget.can_change_related = False self.fields["bill_creator"].widget.can_change_related = False
self.fields["bill_creator"].widget.can_delete_related = False self.fields["bill_creator"].widget.can_delete_related = False
# delete wiref id from list if there are already 8 bills in wiref form. # Delete wiref id from list if there are already 10 bills in wiref form.
qs = self.fields["wiref"].queryset.annotate(num_bills=Count("bill")).filter(num_bills__lt=8) qs = (
self.fields["wiref"].queryset.annotate(num_bills=Count("bill")).filter(num_bills__lt=10)
)
# delete wiref id from if status is not opened. # delete wiref id from if status is not opened.
qs = qs.filter(status=Wiref.Status.OPENED) qs = qs.filter(status=Wiref.Status.OPENED)

View File

@@ -53,7 +53,10 @@ class Resolution(models.Model):
voting_text = models.TextField(verbose_name="Abstimmungstext") voting_text = models.TextField(verbose_name="Abstimmungstext")
budget = models.DecimalField( budget = models.DecimalField(
max_digits=7, decimal_places=2, default=0.00, verbose_name="Budget (EUR)" max_digits=7,
decimal_places=2,
default=0.00,
verbose_name="Budget (EUR)",
) )
class Meta: class Meta:
@@ -155,7 +158,9 @@ class Bill(models.Model):
REPRESENTATION = "R", "Bundesvertretung" REPRESENTATION = "R", "Bundesvertretung"
affiliation = models.CharField( affiliation = models.CharField(
max_length=1, choices=Affiliation.choices, verbose_name="Abrechnungsbudget" max_length=1,
choices=Affiliation.choices,
verbose_name="Abrechnungsbudget",
) )
class Payer(models.TextChoices): class Payer(models.TextChoices):

Binary file not shown.

Binary file not shown.

View File

@@ -4,66 +4,79 @@ import os
from django.core.files import File from django.core.files import File
from pypdf import PdfReader, PdfWriter from pypdf import PdfReader, PdfWriter
from pypdf.constants import FieldDictionaryAttributes as FA
from .models import Bill, Wiref from .models import Bill, Wiref
def generate_pdf(wiref): def generate_pdf(wiref):
if wiref is not None and wiref.status == Wiref.Status.OPENED: if wiref is None or wiref.status != Wiref.Status.OPENED:
bills = Bill.objects.filter(wiref=wiref).order_by("date") return False
# Get data for pdf bills = Bill.objects.filter(wiref=wiref).order_by("date")
data = {}
for count, elem in enumerate(bills):
data.update(
{
f"Datum.{count}": str(elem.date.strftime("%d.%m.%Y")),
f"Aussteller.{count}": elem.invoice,
f"Verwendungszweck.{count}": elem.purpose,
# Replace decimal separator from '.' to ','
f"Betrag.{count}": str(elem.amount).replace(".", ","),
}
)
# Get budget year
today = datetime.date.today()
if today.month < 7:
budget_year = f"{today.year - 1}-{today.year}"
else:
budget_year = f"{today.year}-{today.year + 1}"
# Get total of all bills of wiref form
total = 0
for elem in bills:
total += elem.amount
# Get data for pdf
data = {}
data_invoice = {} # Own dict for fixing text to multiline
for count, elem in enumerate(bills):
data.update( data.update(
{ {
"Laufende Nummer": str(wiref.wiref_id), f"DatumRow{count + 1}": str(elem.date.strftime("%d.%m.%Y")),
"Budgetjahr": budget_year, f"VerwendungszweckRow{count + 1}": elem.purpose,
# Replace decimal separator from '.' to ',' # Replace decimal separator from '.' to ','
"Summe": str(total).replace(".", ","), f"BetragRow{count + 1}": str(elem.amount).replace(".", ","),
} },
)
data_invoice.update(
{
f"RechnungsaustellerinRow{count + 1}": elem.invoice,
},
) )
# Write data in pdf # Get budget year
pdf_path = os.path.join(os.path.dirname(__file__), "static/Vorlage.pdf") today = datetime.date.today()
reader = PdfReader(pdf_path) if today.month < 7:
writer = PdfWriter() budget_year = f"{today.year - 1}-{today.year}"
writer.append(reader) else:
budget_year = f"{today.year}-{today.year + 1}"
writer.update_page_form_field_values( # Get total of all bills of wiref form
writer.pages[0], total = 0
data, for elem in bills:
) total += elem.amount
with io.BytesIO() as bytes_stream: data.update(
writer.write(bytes_stream) {
"Rechnungsnummer": str(wiref.wiref_id),
"Budgetjahr": budget_year,
# Replace decimal separator from '.' to ','
"Summe": str(total).replace(".", ","),
},
)
# Save pdf in wiref # Write data in pdf
wiref_name = f"Abrechnungsformular-{wiref.wiref_id}.pdf" pdf_path = os.path.join(os.path.dirname(__file__), "static/wiref/Vorlage.pdf")
wiref.file_field.save(wiref_name, File(bytes_stream, wiref_name)) reader = PdfReader(pdf_path)
writer = PdfWriter()
writer.append(reader)
return True writer.update_page_form_field_values(
writer.pages[0],
data,
)
return False # Add invoices and fix text to multiline
writer.update_page_form_field_values(
writer.pages[0],
data_invoice,
flags=FA.FfBits.Multiline,
)
with io.BytesIO() as bytes_stream:
writer.write(bytes_stream)
# Save pdf in wiref
wiref_name = f"Abrechnungsformular-{wiref.wiref_id}.pdf"
wiref.file_field.save(wiref_name, File(bytes_stream, wiref_name))
return True

View File

@@ -34,7 +34,7 @@ def set_bankdata(creator, name, iban, bic, saving):
if saving is True: if saving is True:
# Disable old bank data. # Disable old bank data.
qs = BankData.objects.filter( qs = BankData.objects.filter(
~Q(id=obj.id) & Q(bankdata_creator=obj.bankdata_creator) & Q(is_disabled=False) ~Q(id=obj.id) & Q(bankdata_creator=obj.bankdata_creator) & Q(is_disabled=False),
) )
qs.update(is_disabled=True) qs.update(is_disabled=True)
@@ -114,11 +114,7 @@ class BillUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
# Call bill if it's only yours. # Call bill if it's only yours.
def test_func(self): def test_func(self):
if self.get_object().bill_creator.username == self.request.user.username: return self.get_object().bill_creator.username == self.request.user.username
return True
# Call handle_no_permissions method.
return False
def handle_no_permission(self): def handle_no_permission(self):
return redirect("finance:bill_list") return redirect("finance:bill_list")

View File

@@ -14,12 +14,18 @@ class Album(models.Model):
thumbnail = models.CharField(verbose_name="Thumbnail", max_length=200, null=True, blank=True) thumbnail = models.CharField(verbose_name="Thumbnail", max_length=200, null=True, blank=True)
event_date = models.DateField( event_date = models.DateField(
verbose_name="Event Datum", null=True, blank=True, default=timezone.now verbose_name="Event Datum",
null=True,
blank=True,
default=timezone.now,
) )
event_place = models.CharField(max_length=200, blank=True) event_place = models.CharField(max_length=200, blank=True)
photographer = models.CharField( photographer = models.CharField(
verbose_name="Fotograph(en)", max_length=200, null=True, blank=True verbose_name="Fotograph(en)",
max_length=200,
null=True,
blank=True,
) )
DRAFT = "10" DRAFT = "10"

View File

@@ -34,7 +34,8 @@ def get_image_list(folder_name):
img_dict = { img_dict = {
"title": img, "title": img,
"image_url": os.path.join( "image_url": os.path.join(
settings.MEDIA_URL + gallery_path, folder_name + "/" + img settings.MEDIA_URL + gallery_path,
folder_name + "/" + img,
), ),
"thumb_url": thumb_url, "thumb_url": thumb_url,
} }

View File

@@ -27,7 +27,7 @@ def index(request):
slug=slugify(folder), slug=slugify(folder),
folder_name=folder, folder_name=folder,
event_date=None, event_date=None,
) ),
) )
else: else:
# show only PUBLIC albums. # show only PUBLIC albums.

View File

@@ -68,7 +68,7 @@ class TopicGroupAdmin(admin.ModelAdmin):
"slug", "slug",
"order", "order",
"short_description", "short_description",
) ),
}, },
), ),
) )
@@ -118,7 +118,7 @@ class TopicAdmin(admin.ModelAdmin):
"topic_group", "topic_group",
"archive", "archive",
"description", "description",
) ),
}, },
), ),
) )
@@ -163,7 +163,7 @@ class AttachmentAdmin(admin.ModelAdmin):
"title", "title",
"topic", "topic",
"description", "description",
) ),
}, },
), ),
) )

View File

@@ -23,7 +23,10 @@ class TopicGroup(models.Model):
short_description = models.TextField(null=True, blank=True) short_description = models.TextField(null=True, blank=True)
order = models.PositiveSmallIntegerField( order = models.PositiveSmallIntegerField(
verbose_name="Reihenfolge", unique=True, null=True, blank=True verbose_name="Reihenfolge",
unique=True,
null=True,
blank=True,
) )
objects = models.Manager() objects = models.Manager()
@@ -53,7 +56,9 @@ class Topic(models.Model):
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
topic_group = models.ForeignKey( topic_group = models.ForeignKey(
TopicGroup, on_delete=models.CASCADE, verbose_name="Themenbereich" TopicGroup,
on_delete=models.CASCADE,
verbose_name="Themenbereich",
) )
objects = models.Manager() objects = models.Manager()
@@ -65,7 +70,8 @@ class Topic(models.Model):
constraints = [ constraints = [
UniqueConstraint(fields=["slug", "topic_group"], name="unique_intern_slug_topic_group"), UniqueConstraint(fields=["slug", "topic_group"], name="unique_intern_slug_topic_group"),
UniqueConstraint( UniqueConstraint(
fields=["title", "topic_group"], name="unique_intern_title_topic_group" fields=["title", "topic_group"],
name="unique_intern_title_topic_group",
), ),
] ]
@@ -126,7 +132,9 @@ class Etherpad(models.Model):
date = models.DateField(default=date.today, verbose_name="Datum") date = models.DateField(default=date.today, verbose_name="Datum")
attachment = models.ForeignKey( attachment = models.ForeignKey(
Attachment, on_delete=models.CASCADE, verbose_name="Anhang Ordner" Attachment,
on_delete=models.CASCADE,
verbose_name="Anhang Ordner",
) )
objects = models.Manager() objects = models.Manager()
@@ -178,7 +186,9 @@ class FileUpload(models.Model):
date = models.DateField(default=date.today, verbose_name="Datum") date = models.DateField(default=date.today, verbose_name="Datum")
attachment = models.ForeignKey( attachment = models.ForeignKey(
Attachment, on_delete=models.CASCADE, verbose_name="Anhang Ordner" Attachment,
on_delete=models.CASCADE,
verbose_name="Anhang Ordner",
) )
objects = models.Manager() objects = models.Manager()

View File

@@ -27,7 +27,9 @@ logger = logging.getLogger(__name__)
@authenticated_user @authenticated_user
def index(request): def index(request):
topic = Topic.objects.filter(archive=False).order_by( topic = Topic.objects.filter(archive=False).order_by(
F("topic_group__order").asc(nulls_last=True), "topic_group", "title" F("topic_group__order").asc(nulls_last=True),
"topic_group",
"title",
) )
empty_topic_groups = TopicGroup.objects.filter(topic=None) empty_topic_groups = TopicGroup.objects.filter(topic=None)
archive_topic = Topic.objects.filter(archive=True) archive_topic = Topic.objects.filter(archive=True)
@@ -107,7 +109,7 @@ class AttachmentCreateView(LoginRequiredMixin, CreateView):
topic_group_slug = self.kwargs.get("topic_group_slug") topic_group_slug = self.kwargs.get("topic_group_slug")
topic_slug = self.kwargs.get("slug") topic_slug = self.kwargs.get("slug")
context["topic"] = Topic.objects.get( context["topic"] = Topic.objects.get(
Q(topic_group__slug=topic_group_slug) & Q(slug=topic_slug) Q(topic_group__slug=topic_group_slug) & Q(slug=topic_slug),
) )
return context return context
@@ -143,7 +145,7 @@ class AttachmentDetailView(LoginRequiredMixin, DetailView):
topic_group_slug = self.kwargs.get("topic_group_slug") topic_group_slug = self.kwargs.get("topic_group_slug")
topic_slug = self.kwargs.get("topic_slug") topic_slug = self.kwargs.get("topic_slug")
return Attachment.objects.filter( return Attachment.objects.filter(
Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug) Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug),
) )
@@ -166,7 +168,7 @@ class AttachmentUpdateView(LoginRequiredMixin, UpdateView):
topic_group_slug = self.kwargs.get("topic_group_slug") topic_group_slug = self.kwargs.get("topic_group_slug")
topic_slug = self.kwargs.get("topic_slug") topic_slug = self.kwargs.get("topic_slug")
return Attachment.objects.filter( return Attachment.objects.filter(
Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug) Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug),
) )
@@ -186,7 +188,7 @@ class EtherpadCreateView(LoginRequiredMixin, CreateView):
topic_slug = self.kwargs.get("topic_slug") topic_slug = self.kwargs.get("topic_slug")
slug = self.kwargs.get("slug") slug = self.kwargs.get("slug")
attachment = Attachment.objects.get( attachment = Attachment.objects.get(
Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug) & Q(slug=slug) Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug) & Q(slug=slug),
) )
context["attachment"] = attachment context["attachment"] = attachment
return context return context
@@ -211,7 +213,7 @@ class FileUploadCreateView(LoginRequiredMixin, CreateView):
topic_slug = self.kwargs.get("topic_slug") topic_slug = self.kwargs.get("topic_slug")
slug = self.kwargs.get("slug") slug = self.kwargs.get("slug")
attachment = Attachment.objects.get( attachment = Attachment.objects.get(
Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug) & Q(slug=slug) Q(topic__topic_group__slug=topic_group_slug) & Q(topic__slug=topic_slug) & Q(slug=slug),
) )
context["attachment"] = attachment context["attachment"] = attachment
return context return context

View File

@@ -86,7 +86,7 @@ class MemberAdmin(admin.ModelAdmin):
"birthday", "birthday",
"phone", "phone",
"address", "address",
) ),
}, },
), ),
) )
@@ -137,7 +137,7 @@ class JobAdmin(admin.ModelAdmin):
"fields": ( "fields": (
"name", "name",
"job_group", "job_group",
) ),
}, },
), ),
( (
@@ -146,7 +146,7 @@ class JobAdmin(admin.ModelAdmin):
"fields": ( "fields": (
"shortterm", "shortterm",
"slug", "slug",
) ),
}, },
), ),
) )
@@ -193,7 +193,7 @@ class JobGroupAdmin(admin.ModelAdmin):
"fields": ( "fields": (
"name", "name",
"description", "description",
) ),
}, },
), ),
( (
@@ -202,7 +202,7 @@ class JobGroupAdmin(admin.ModelAdmin):
"fields": ( "fields": (
"shortterm", "shortterm",
"slug", "slug",
) ),
}, },
), ),
) )

View File

@@ -46,8 +46,7 @@ class MemberForm(forms.ModelForm):
help_texts = { help_texts = {
"image": ( "image": (
"Mindestdimension: 150*150 px, maximale Größe: 10MB, erlaubtes Format: " "Mindestdimension: 150*150 px, maximale Größe: 10MB, erlaubtes Format: Bildformate."
"Bildformate."
), ),
"mailaccount": "Die Mailadresse mit '@fet.at' angeben.", "mailaccount": "Die Mailadresse mit '@fet.at' angeben.",
} }

View File

@@ -24,7 +24,7 @@ class ActiveJobMemberManager(models.Manager):
) )
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)),
) )
@@ -44,7 +44,7 @@ class InactiveJobMemberManager(models.Manager):
return qs.filter( return qs.filter(
Q(member__role="P") Q(member__role="P")
| (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)),
) )
@@ -56,7 +56,9 @@ class JobMemberManager(models.Manager):
def get_all_jobs_sorted(self): def get_all_jobs_sorted(self):
qs = self.get_queryset().order_by( qs = self.get_queryset().order_by(
F("job_end").desc(nulls_first=True), "-job_start", "job__name" F("job_end").desc(nulls_first=True),
"-job_start",
"job__name",
) )
return qs return qs

View File

@@ -19,7 +19,8 @@ def get_jobs_sidebar(slug):
job_groups.remove(elem) job_groups.remove(elem)
job_members = JobMember.active_member.get_all(slug=slug).order_by( job_members = JobMember.active_member.get_all(slug=slug).order_by(
F("job__order").asc(nulls_last=True), "job__name" F("job__order").asc(nulls_last=True),
"job__name",
) )
active_job_group = JobGroup.objects.filter(slug=slug).first() active_job_group = JobGroup.objects.filter(slug=slug).first()

View File

@@ -27,7 +27,8 @@ def jobs(request, slug=None):
raise Http404("wrong job") raise Http404("wrong job")
job_members = JobMember.active_member.get_all(slug=slug).order_by( job_members = JobMember.active_member.get_all(slug=slug).order_by(
F("job__order").asc(nulls_last=True), "job__name" F("job__order").asc(nulls_last=True),
"job__name",
) )
active_job_group = JobGroup.objects.filter(slug=slug).first() active_job_group = JobGroup.objects.filter(slug=slug).first()

View File

@@ -88,7 +88,7 @@ class PostAdmin(admin.ModelAdmin):
"all": [ "all": [
"jquery-ui/jquery-ui.min.css", "jquery-ui/jquery-ui.min.css",
"jquery-ui/ui-lightness/theme.css", "jquery-ui/ui-lightness/theme.css",
] ],
} }
js = [ js = [
"jquery-ui/jquery-ui.min.js", "jquery-ui/jquery-ui.min.js",
@@ -108,7 +108,7 @@ class NewsAdmin(PostAdmin):
"title", "title",
"subtitle", "subtitle",
"tags", "tags",
) ),
}, },
), ),
( (
@@ -117,7 +117,7 @@ class NewsAdmin(PostAdmin):
"fields": ( "fields": (
"status", "status",
"is_pinned", "is_pinned",
) ),
}, },
), ),
( (
@@ -126,7 +126,7 @@ class NewsAdmin(PostAdmin):
"fields": ( "fields": (
"image", "image",
"body", "body",
) ),
}, },
), ),
( (
@@ -136,7 +136,7 @@ class NewsAdmin(PostAdmin):
"slug", "slug",
"author", "author",
"public_date", "public_date",
) ),
}, },
), ),
) )
@@ -161,7 +161,7 @@ class EventAdmin(PostAdmin):
"title", "title",
"subtitle", "subtitle",
"tags", "tags",
) ),
}, },
), ),
( (
@@ -170,7 +170,7 @@ class EventAdmin(PostAdmin):
"fields": ( "fields": (
"status", "status",
"is_pinned", "is_pinned",
) ),
}, },
), ),
( (
@@ -180,7 +180,7 @@ class EventAdmin(PostAdmin):
"event_start", "event_start",
"event_end", "event_end",
"event_place", "event_place",
) ),
}, },
), ),
( (
@@ -189,7 +189,7 @@ class EventAdmin(PostAdmin):
"fields": ( "fields": (
"image", "image",
"body", "body",
) ),
}, },
), ),
( (
@@ -199,7 +199,7 @@ class EventAdmin(PostAdmin):
"slug", "slug",
"author", "author",
"public_date", "public_date",
) ),
}, },
), ),
) )
@@ -223,7 +223,7 @@ class FetMeetingAdmin(EventAdmin):
"event_end", "event_end",
"event_place", "event_place",
"tags", "tags",
) ),
}, },
), ),
) )

View File

@@ -149,10 +149,14 @@ class PostSearchForm(forms.Form):
month = forms.ChoiceField(label="Monat", choices=month_choices, required=False) month = forms.ChoiceField(label="Monat", choices=month_choices, required=False)
compact_view = forms.BooleanField( compact_view = forms.BooleanField(
label="Kompakte Ansicht", required=False, widget=CheckboxInput label="Kompakte Ansicht",
required=False,
widget=CheckboxInput,
) )
fet_meeting_only = forms.BooleanField( fet_meeting_only = forms.BooleanField(
label="nur FET Sitzungen", required=False, widget=CheckboxInput label="nur FET Sitzungen",
required=False,
widget=CheckboxInput,
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@@ -10,20 +10,14 @@ class PublishedManager(models.Manager):
""" """
publish all posts with status 'PUBLIC' publish all posts with status 'PUBLIC'
""" """
if public: qs = self.get_queryset().filter(status="20") if public else self.get_queryset()
qs = self.get_queryset().filter(status="20")
else:
qs = self.get_queryset()
return qs return qs
def published_all(self, public=True): def published_all(self, public=True):
""" """
publish all posts with status 'PUBLIC' and 'ONLY_INTERN' publish all posts with status 'PUBLIC' and 'ONLY_INTERN'
""" """
if public: qs = self.get_queryset().filter(~Q(status="10")) if public else self.get_queryset()
qs = self.get_queryset().filter(~Q(status="10"))
else:
qs = self.get_queryset()
return qs return qs
@@ -35,7 +29,7 @@ class PostManager(PublishedManager, models.Manager):
When(post_type="N", then="public_date"), When(post_type="N", then="public_date"),
When(post_type="E", then="event_start__date"), When(post_type="E", then="event_start__date"),
When(post_type="F", then="event_start__date"), When(post_type="F", then="event_start__date"),
) ),
) )
return qs.order_by("-date", "-id") return qs.order_by("-date", "-id")
@@ -74,7 +68,7 @@ class ArticleManager(PublishedManager, models.Manager):
date=Case( date=Case(
When(post_type="N", then="public_date"), When(post_type="N", then="public_date"),
When(post_type="E", then="event_start__date"), When(post_type="E", then="event_start__date"),
) ),
) )
return qs.order_by("-date", "-id") return qs.order_by("-date", "-id")
@@ -87,7 +81,7 @@ class ArticleManager(PublishedManager, models.Manager):
__month = post_date.month __month = post_date.month
__year = post_date.year __year = post_date.year
if __month != 0: if __month != 1:
__month -= 1 __month -= 1
else: else:
# If the current month is January, you get the date from December of previous year. # If the current month is January, you get the date from December of previous year.
@@ -104,7 +98,7 @@ class ArticleManager(PublishedManager, models.Manager):
.filter(is_pinned=True) .filter(is_pinned=True)
.filter( .filter(
(Q(post_type="N") & Q(public_date__gt=post_date)) (Q(post_type="N") & Q(public_date__gt=post_date))
| (Q(post_type="E") & Q(event_end__date__gt=event_date)) | (Q(post_type="E") & Q(event_end__date__gt=event_date)),
) )
.first() .first()
) )
@@ -120,7 +114,7 @@ class NewsManager(PublishedManager, models.Manager):
qs = qs.annotate( qs = qs.annotate(
date=Case( date=Case(
When(post_type="N", then="public_date"), When(post_type="N", then="public_date"),
) ),
) )
return qs.order_by("-date") return qs.order_by("-date")
@@ -136,7 +130,7 @@ class AllEventManager(PublishedManager, models.Manager):
date=Case( date=Case(
When(post_type="E", then="event_start__date"), When(post_type="E", then="event_start__date"),
When(post_type="F", then="event_start__date"), When(post_type="F", then="event_start__date"),
) ),
) )
return qs.order_by("-date") return qs.order_by("-date")
@@ -157,7 +151,7 @@ class EventManager(PublishedManager, models.Manager):
qs = qs.annotate( qs = qs.annotate(
date=Case( date=Case(
When(post_type="E", then="event_start__date"), When(post_type="E", then="event_start__date"),
) ),
) )
return qs.order_by("-date") return qs.order_by("-date")
@@ -182,7 +176,7 @@ class FetMeetingManager(PublishedManager, models.Manager):
qs = qs.annotate( qs = qs.annotate(
date=Case( date=Case(
When(post_type="F", then="event_start__date"), When(post_type="F", then="event_start__date"),
) ),
) )
return qs.order_by("-date") return qs.order_by("-date")

View File

@@ -86,7 +86,10 @@ class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
public_date = models.DateField( public_date = models.DateField(
verbose_name="Veröffentlichung", null=True, blank=True, default=timezone.now verbose_name="Veröffentlichung",
null=True,
blank=True,
default=timezone.now,
) )
__choices = [("N", _("News")), ("E", _("Event")), ("F", _("FetMeeting"))] __choices = [("N", _("News")), ("E", _("Event")), ("F", _("FetMeeting"))]
@@ -137,9 +140,6 @@ class Post(models.Model):
self.title, self.title,
) )
def get_absolute_url(self):
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
if not self.public_date: if not self.public_date:
@@ -150,6 +150,9 @@ class Post(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("posts:post", kwargs={"slug": self.slug})
@property @property
def agenda_html(self) -> str | None: def agenda_html(self) -> str | None:
"Agenda HTML from Etherpad Pad" "Agenda HTML from Etherpad Pad"
@@ -188,7 +191,9 @@ class Post(models.Model):
ep_set_html(self.protocol_key, value) ep_set_html(self.protocol_key, value)
request_logger.info( request_logger.info(
"Set protocol etherpad. Post: %s. Key: %s", self.slug, self.protocol_key "Set protocol etherpad. Post: %s. Key: %s",
self.slug,
self.protocol_key,
) )
return value return value
@@ -302,10 +307,7 @@ class Post(models.Model):
@property @property
def published(self): def published(self):
if self.status == self.Status.PUBLIC: return self.status == self.Status.PUBLIC
return True
return False
class News(Post): class News(Post):
@@ -328,16 +330,16 @@ class Event(Post):
only_events = EventManager() only_events = EventManager()
all_events = AllEventManager() all_events = AllEventManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "E"
class Meta: class Meta:
proxy = True proxy = True
verbose_name = "Event" verbose_name = "Event"
verbose_name_plural = "Events" verbose_name_plural = "Events"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "E"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.post_type: if not self.post_type:
self.post_type = "E" self.post_type = "E"
@@ -355,16 +357,16 @@ class Event(Post):
class FetMeeting(Event): class FetMeeting(Event):
objects = FetMeetingManager() objects = FetMeetingManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "F"
class Meta: class Meta:
proxy = True proxy = True
verbose_name = "Fet Sitzung" verbose_name = "Fet Sitzung"
verbose_name_plural = "Fet Sitzungen" verbose_name_plural = "Fet Sitzungen"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "F"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.title = "Fachschaftssitzung" self.title = "Fachschaftssitzung"
if not self.slug: if not self.slug:
@@ -398,9 +400,8 @@ class FetMeeting(Event):
def __get_slug(self) -> str: def __get_slug(self) -> str:
slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung") slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung")
if Post.objects.filter(slug=slug).exists(): if Post.objects.filter(slug=slug).exists() and Post.objects.get(slug=slug).id != self.id:
if Post.objects.get(slug=slug).id != self.id: raise ValidationError("Es existiert bereits eine Sitzung mit demselben Datum.")
raise ValidationError("Es existiert bereits eine Sitzung mit demselben Datum.")
return slug return slug

View File

@@ -22,9 +22,7 @@ class PostIndex(indexes.SearchIndex, indexes.Indexable):
def prepare_date(self, obj): def prepare_date(self, obj):
if obj.post_type == "N": if obj.post_type == "N":
return obj.public_date return obj.public_date
elif obj.post_type == "E": elif obj.post_type == "E" or obj.post_type == "F":
return obj.event_start.date()
elif obj.post_type == "F":
return obj.event_start.date() return obj.event_start.date()
def prepare_agenda(self, obj): def prepare_agenda(self, obj):

View File

@@ -146,13 +146,13 @@ class PostDetailView(DetailView):
Helper function for getting previous post Helper function for getting previous post
""" """
posts = Post.objects.date_sorted_list(self.public_only).filter( posts = Post.objects.date_sorted_list(self.public_only).filter(
post_type=self.object.post_type post_type=self.object.post_type,
) )
qs = posts.filter( qs = posts.filter(
# Get posts which are in the future. # Get posts which are in the future.
Q(date__lt=self.object.date) Q(date__lt=self.object.date)
# Get posts which have the same date but id is lower. # Get posts which have the same date but id is lower.
| (Q(date__lte=self.object.date) & Q(id__lt=self.object.id)) | (Q(date__lte=self.object.date) & Q(id__lt=self.object.id)),
) )
if not qs: if not qs:
# If there are any prev posts, then take the latest one. # If there are any prev posts, then take the latest one.
@@ -173,7 +173,7 @@ class PostDetailView(DetailView):
# Get posts which are in the past. # Get posts which are in the past.
Q(date__gt=self.object.date) Q(date__gt=self.object.date)
# Get posts which have the same date but id is greater. # Get posts which have the same date but id is greater.
| (Q(date__gte=self.object.date) & Q(id__gt=self.object.id)) | (Q(date__gte=self.object.date) & Q(id__gt=self.object.id)),
) )
if not qs: if not qs:
# If there are any next posts, then take the first one. # If there are any next posts, then take the first one.
@@ -314,7 +314,7 @@ def show_pdf(request, html, filename):
response = HttpResponse(pdf, content_type="application/pdf") response = HttpResponse(pdf, content_type="application/pdf")
content = "inline; filename=%s" % filename content = f"inline; filename={filename}"
response["Content-Disposition"] = content response["Content-Disposition"] = content
return response return response

View File

@@ -1,2 +0,0 @@
[pytest]
DJANGO_SETTINGS_MODULE = fet2020.settings

Binary file not shown.

View File

@@ -22,7 +22,7 @@ class FetUserSearchForm(SearchForm):
sqs_gallery = self.searchqueryset.models(Album) sqs_gallery = self.searchqueryset.models(Album)
sqs_gallery = sqs_gallery.filter( sqs_gallery = sqs_gallery.filter(
SQ(title__icontains=self.cleaned_data["q"]) SQ(title__icontains=self.cleaned_data["q"])
| SQ(description__icontains=self.cleaned_data["q"]) | SQ(description__icontains=self.cleaned_data["q"]),
) )
sqs_intern = self.searchqueryset.models(Etherpad) sqs_intern = self.searchqueryset.models(Etherpad)
@@ -31,7 +31,7 @@ class FetUserSearchForm(SearchForm):
sqs_member = self.searchqueryset.models(Member) sqs_member = self.searchqueryset.models(Member)
sqs_member = sqs_member.filter( sqs_member = sqs_member.filter(
SQ(firstname__icontains=self.cleaned_data["q"]) SQ(firstname__icontains=self.cleaned_data["q"])
| SQ(surname__icontains=self.cleaned_data["q"]) | SQ(surname__icontains=self.cleaned_data["q"]),
) )
sqs_post = self.searchqueryset.models(Post) sqs_post = self.searchqueryset.models(Post)
@@ -39,7 +39,7 @@ class FetUserSearchForm(SearchForm):
SQ(title__icontains=self.cleaned_data["q"]) SQ(title__icontains=self.cleaned_data["q"])
| SQ(body__icontains=self.cleaned_data["q"]) | SQ(body__icontains=self.cleaned_data["q"])
| SQ(agenda__icontains=self.cleaned_data["q"]) | SQ(agenda__icontains=self.cleaned_data["q"])
| SQ(protocol__icontains=self.cleaned_data["q"]) | SQ(protocol__icontains=self.cleaned_data["q"]),
) )
tmp_results = deque([]) tmp_results = deque([])
@@ -82,20 +82,20 @@ class NonUserSearchForm(SearchForm):
sqs_gallery = self.searchqueryset.models(Album).filter(status="20") sqs_gallery = self.searchqueryset.models(Album).filter(status="20")
sqs_gallery = sqs_gallery.filter( sqs_gallery = sqs_gallery.filter(
SQ(title__icontains=self.cleaned_data["q"]) SQ(title__icontains=self.cleaned_data["q"])
| SQ(description__icontains=self.cleaned_data["q"]) | SQ(description__icontains=self.cleaned_data["q"]),
) )
sqs_member = self.searchqueryset.models(Member) sqs_member = self.searchqueryset.models(Member)
sqs_member = sqs_member.filter( sqs_member = sqs_member.filter(
SQ(firstname__icontains=self.cleaned_data["q"]) SQ(firstname__icontains=self.cleaned_data["q"])
| SQ(surname__icontains=self.cleaned_data["q"]) | SQ(surname__icontains=self.cleaned_data["q"]),
) )
sqs_post = self.searchqueryset.models(Post).filter(status="20") sqs_post = self.searchqueryset.models(Post).filter(status="20")
sqs_post = sqs_post.filter( sqs_post = sqs_post.filter(
SQ(title__icontains=self.cleaned_data["q"]) SQ(title__icontains=self.cleaned_data["q"])
| SQ(body__icontains=self.cleaned_data["q"]) | SQ(body__icontains=self.cleaned_data["q"])
| SQ(agenda__icontains=self.cleaned_data["q"]) | SQ(agenda__icontains=self.cleaned_data["q"]),
) )
tmp_results = deque([]) tmp_results = deque([])

View File

@@ -18,4 +18,4 @@
</div> </div>
</section> </section>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -1,10 +1,9 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% load i18n admin_urls %} {% load admin_urls i18n static %}
{% load static %}
{% block extrahead %} {% block extrahead %}
<link rel="shortcut icon" type="image/png" href="{% static 'img/fet_logo_white.png' %}"/> <link rel="shortcut icon" type="image/png" href="{% static 'img/fet_logo_white.png' %}"/>
{% endblock %} {% endblock extrahead %}
{% block usertools %} {% block usertools %}
{% if has_permission %} {% if has_permission %}
@@ -12,14 +11,14 @@
{% block welcome-msg %} {% block welcome-msg %}
{% translate 'Welcome,' %} {% translate 'Welcome,' %}
<strong>{% firstof user.get_short_name user.get_username %}</strong>. <strong>{% firstof user.get_short_name user.get_username %}</strong>.
{% endblock %} {% endblock welcome-msg %}
{% block userlinks %} {% block userlinks %}
{% 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> <!-- TODO: FIXME: <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 userlinks %}
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock usertools %}

View File

@@ -1,3 +1,3 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% block title %}{{ site_title }}: {{ title }} {% endblock %} {% block title %}{{ site_title }}: {{ title }}{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "admin/change_form.html" %} {% extends "admin/change_form.html" %}
{% load i18n admin_urls %} {% load admin_urls i18n %}
{% block form_top %} {% block form_top %}
{% if help_text %}<p>{{ help_text }}</p>{% endif %} {% if help_text %}<p>{{ help_text }}</p>{% endif %}
{% endblock %} {% endblock form_top %}

View File

@@ -1,5 +1,5 @@
{% extends "admin/submit_line.html" %} {% extends "admin/submit_line.html" %}
{% load i18n admin_urls %} {% load admin_urls i18n %}
{% block submit-row %} {% block submit-row %}
{% if not show_close %} {% if not show_close %}
@@ -8,4 +8,4 @@
{% endif %} {% endif %}
{% if generate_pdf %}<input type="submit" value="PDF File generieren" class="default" name="_generate_pdf">{% endif %} {% if generate_pdf %}<input type="submit" value="PDF File generieren" class="default" name="_generate_pdf">{% endif %}
{{ block.super }} {{ block.super }}
{% endblock %} {% endblock submit-row %}

View File

@@ -26,4 +26,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -12,4 +12,4 @@
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -28,4 +28,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -1,6 +1,4 @@
{% load flatpages %} {% load flatpages static version %}
{% load static %}
{% load version %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de" x-data="bodyData" x-bind="documentRoot"> <html lang="de" x-data="bodyData" x-bind="documentRoot">
@@ -50,9 +48,9 @@
<link rel="stylesheet" href="{% static 'fonts/Fira_Code-6.2/fira_code.css' %}"> <link rel="stylesheet" href="{% static 'fonts/Fira_Code-6.2/fira_code.css' %}">
{% block galleryheader %} {% block galleryheader %}
{% endblock %} {% endblock galleryheader %}
{% block extraheader %} {% block extraheader %}
{% endblock %} {% endblock extraheader %}
</head> </head>
<body x-bind="documentBody"> <body x-bind="documentBody">
@@ -203,7 +201,7 @@
</nav> </nav>
{% block content %} {% block content %}
{% endblock %} {% endblock content %}
<footer> <footer>
<ul class="icon-list"> <ul class="icon-list">

View File

@@ -75,4 +75,4 @@
{% endif %} {% endif %}
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -1,4 +1,3 @@
<!DOCTYPE html> <!DOCTYPE html>
{% load i18n %} {% load i18n %}
<html lang="en"> <html lang="en">
@@ -14,10 +13,10 @@
<div id="nav"><nav> <div id="nav"><nav>
{% block nav %} {% block nav %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a class="nav" href="/accounts/profile/">{% trans "profile" %}</a> <a class="nav" href="/accounts/profile/">{% translate "profile" %}</a>
<a class="nav" href="/logout">{% trans "logout" %}</a> <a class="nav" href="/logout">{% translate "logout" %}</a>
{% endif %} {% endif %}
{% endblock %} {% endblock nav %}
</nav></div> </nav></div>
<div id="wrapper"> <div id="wrapper">
{% block wrapper %} {% block wrapper %}
@@ -31,7 +30,7 @@
<span class="mark">!</span>{{ message.text }}<span class="mark">!</span> <span class="mark">!</span>{{ message.text }}<span class="mark">!</span>
</div> </div>
{% endfor %} {% endfor %}
{% endblock %} {% endblock messages %}
</div> </div>
{% endif %} {% endif %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
@@ -42,9 +41,8 @@
<div id="footer"><footer> <div id="footer"><footer>
</footer></div> </footer></div>
</div> </div>
{% endblock %} {% endblock wrapper %}
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,18 +1,17 @@
{% extends "documents/base.html" %} {% extends "documents/base.html" %}
{% block title %}{{ pad.name }}{% endblock %}
{% block title %}{{pad.name}}{% endblock %}
{% block wrapper %} {% block wrapper %}
{% if error %} {% if error %}
<div id="main"> <div id="main">
<div id="errors"> <div id="errors">
<h2>Errors:</h2> <h2>Errors:</h2>
<p>{{error}}</p> <p>{{ error }}</p>
</div> </div>
</div> </div>
{% else %} {% else %}
<iframe src="{{link}}?userName={{uname}}" style="height: 95%; width: 100%; min-height: 500px; display: block"></iframe> <iframe src="{{ link }}?userName={{ uname }}" style="height: 95%; width: 100%; min-height: 500px; display: block"></iframe>
<script type="text/javascript"> <script type="text/javascript">
/** /**
@@ -31,4 +30,4 @@
</script> </script>
{% endif %} {% endif %}
{% endblock %} {% endblock wrapper %}

View File

@@ -103,4 +103,4 @@
</section> </section>
</form> </form>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -6,10 +6,10 @@
<!-- 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="w-full text-center"> <section class="w-full text-center">
<p class="mt-6 text-gray-900 dark:text-gray-100">Rechnung #{{pk}} wurde erfolgreich eingereicht.</p> <p class="mt-6 text-gray-900 dark:text-gray-100">Rechnung #{{ pk }} wurde erfolgreich eingereicht.</p>
<div class="mt-10 flex items-center justify-center"> <div class="mt-10 flex items-center justify-center">
<a href="{% url 'home' %}" type="submit" class="block btn btn-primary">Zur Startseite</a> <a href="{% url 'home' %}" type="submit" class="block btn btn-primary">Zur Startseite</a>
</div> </div>
</section> </section>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -178,4 +178,4 @@
{% endif %} {% endif %}
</form> </form>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -71,4 +71,4 @@
</div> </div>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -35,4 +35,4 @@
</section> </section>
</form> </form>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -57,4 +57,4 @@
</a> </a>
</section> </section>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -27,7 +27,7 @@
{% for result in object_list %} {% for result in object_list %}
<tr> <tr>
<td class="text-right">{{ result.id }}</td> <td class="text-right">{{ result.id }}</td>
<td class="text-blue-700 dark:text-blue-200 no-underline hover:underline"><a href="{%url 'finance:resolution_detail' result.id %}">{{ result.name }}</a></td> <td class="text-blue-700 dark:text-blue-200 no-underline hover:underline"><a href="{% url 'finance:resolution_detail' result.id %}">{{ result.name }}</a></td>
<!--<td class="text-left">{{ result.voting_text|truncatechars:300 }}</td>--> <!--<td class="text-left">{{ result.voting_text|truncatechars:300 }}</td>-->
<td class="text-left">{{ result.voting }}</td> <td class="text-left">{{ result.voting }}</td>
<td class="text-left">{{ result.get_option_display }}</td> <td class="text-left">{{ result.get_option_display }}</td>
@@ -65,4 +65,4 @@
{% endif %} {% endif %}
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -35,4 +35,4 @@
</section> </section>
</form> </form>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -17,4 +17,4 @@
</div> </div>
{% endif %} {% endif %}
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -77,4 +77,4 @@
</main> </main>
</div> </div>
</body> </body>
{% endblock %} {% endblock content %}

View File

@@ -5,7 +5,7 @@
{% block galleryheader %} {% block galleryheader %}
<link rel="stylesheet" href="{% static 'Gallery-3.4.0/css/blueimp-gallery.min.css' %}"> <link rel="stylesheet" href="{% static 'Gallery-3.4.0/css/blueimp-gallery.min.css' %}">
{% endblock %} {% endblock galleryheader %}
{% block content %} {% block content %}
<!-- Main Content --> <!-- Main Content -->
@@ -77,4 +77,4 @@
} }
</script> </script>
{% endblock %} {% endblock content %}

View File

@@ -31,4 +31,4 @@
{% endfor %} {% endfor %}
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -121,4 +121,4 @@
</section> </section>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -20,4 +20,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -61,4 +61,4 @@
<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 content %}

View File

@@ -20,4 +20,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -20,4 +20,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -20,4 +20,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -92,4 +92,4 @@
<a href="{% url 'admin:intern_topicgroup_add' %}" class="btn btn-primary block w-full"><i class="fa-solid fa-plus-square mr-2"></i>Neuer Themenbereich</a> <a href="{% url 'admin:intern_topicgroup_add' %}" class="btn btn-primary block w-full"><i class="fa-solid fa-plus-square mr-2"></i>Neuer Themenbereich</a>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -20,4 +20,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -36,4 +36,4 @@
</section> </section>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -24,4 +24,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -1,9 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load flatpages %} {% load flatpages job_groups softhyphen_tags static %}
{% load job_groups %}
{% load softhyphen_tags %}
{% load static %}
{% block content %} {% block content %}
<!-- Main Content --> <!-- Main Content -->
@@ -106,21 +103,21 @@
{% if member %} {% if member %}
<!-- show details of a member --> <!-- show details of a member -->
{% block member_content %} {% block member_content %}
{% endblock %} {% endblock member_content %}
{% endif %} {% endif %}
{% if members %} {% if members %}
<!-- show all, active or pension members --> <!-- show all, active or pension members -->
{% block members_content %} {% block members_content %}
{% endblock %} {% endblock members_content %}
{% endif %} {% endif %}
{% if job_members %} {% if job_members %}
<!-- show job lists in a job group --> <!-- show job lists in a job group -->
{% block jobs_content %} {% block jobs_content %}
{% endblock %} {% endblock jobs_content %}
{% endif %} {% endif %}
</section> </section>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -36,4 +36,4 @@
</article> </article>
{% endfor %} {% endfor %}
</div> </div>
{% endblock %} {% endblock jobs_content %}

View File

@@ -9,7 +9,7 @@
<meta content="{{ member.firstname }}" property="og:profile:first_name"> <meta content="{{ member.firstname }}" property="og:profile:first_name">
<meta content="{{ member.surname }}" property="og:profile:last_name"> <meta content="{{ member.surname }}" property="og:profile:last_name">
<meta content="profile" property="og:type"> <meta content="profile" property="og:type">
{% endblock %} {% endblock extraheader %}
{% block member_content %} {% block member_content %}
<section class="flex-grow w-full max-w-prose my-8 sm:my-0 text-gray-800 dark:text-gray-300"> <section class="flex-grow w-full max-w-prose my-8 sm:my-0 text-gray-800 dark:text-gray-300">
@@ -70,4 +70,4 @@
</div> </div>
{% endif %} {% endif %}
</section> </section>
{% endblock %} {% endblock member_content %}

View File

@@ -26,4 +26,4 @@
{% endfor %} {% endfor %}
</div> </div>
</article> </article>
{% endblock %} {% endblock members_content %}

View File

@@ -5,9 +5,11 @@
{% block title %}{{ post.title }}{% endblock %} {% block title %}{{ post.title }}{% endblock %}
{% block prev_text_big %}Vorheriges<br>Event{% endblock %} {% block prev_text_big %}Vorheriges<br>Event{% endblock %}
{% block next_text_big %}Nächstes<br>Event{% endblock %} {% block next_text_big %}Nächstes<br>Event{% endblock %}
{% block prev_text %}Vorheriges Event{% endblock %} {% block prev_text %}Vorheriges Event{% endblock %}
{% block next_text %}Nächstes Event{% endblock %} {% block next_text %}Nächstes Event{% endblock %}
{% block update_button_desktop %} {% block update_button_desktop %}
@@ -16,7 +18,7 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten <i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock update_button_desktop %}
{% block event_details_desktop %} {% block event_details_desktop %}
<div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-950 rounded-bl p-2 bg-opacity-60 dark:bg-opacity-60 gap-2 backdrop-blur"> <div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-950 rounded-bl p-2 bg-opacity-60 dark:bg-opacity-60 gap-2 backdrop-blur">
@@ -46,13 +48,13 @@
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% endblock %} {% endblock event_details_desktop %}
{% block post_body %} {% block post_body %}
{% if post.body %} {% if post.body %}
{{ post.body|safe|tags_to_url }} {{ post.body|safe|tags_to_url }}
{% endif %} {% endif %}
{% endblock %} {% endblock post_body %}
{% block event_details_mobile %} {% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4"> <hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
@@ -66,7 +68,7 @@
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
{% endblock %} {% endblock event_details_mobile %}
{% block update_button_mobile %} {% block update_button_mobile %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
@@ -74,4 +76,4 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten <i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock update_button_mobile %}

View File

@@ -27,4 +27,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -26,4 +26,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -5,9 +5,11 @@
{% block title %}{{ post.title }} vom {{ post.event_start|date }}{% endblock %} {% block title %}{{ post.title }} vom {{ post.event_start|date }}{% endblock %}
{% block prev_text_big %}Vorherige<br>Sitzung{% endblock %} {% block prev_text_big %}Vorherige<br>Sitzung{% endblock %}
{% block next_text_big %}Nächste<br>Sitzung{% endblock %} {% block next_text_big %}Nächste<br>Sitzung{% endblock %}
{% block prev_text %}Vorherige Sitzung{% endblock %} {% block prev_text %}Vorherige Sitzung{% endblock %}
{% block next_text %}Nächste Sitzung{% endblock %} {% block next_text %}Nächste Sitzung{% endblock %}
{% block update_button_desktop %} {% block update_button_desktop %}
@@ -16,7 +18,7 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten <i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock update_button_desktop %}
{% block event_details_desktop %} {% block event_details_desktop %}
<div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-950 rounded-bl p-2 bg-opacity-60 dark:bg-opacity-60 gap-2 backdrop-blur"> <div class="hidden lg:block absolute top-0 right-0 bg-white dark:bg-gray-950 rounded-bl p-2 bg-opacity-60 dark:bg-opacity-60 gap-2 backdrop-blur">
@@ -46,7 +48,7 @@
{% endif %} {% endif %}
</span> </span>
</div> </div>
{% endblock %} {% endblock event_details_desktop %}
{% block post_body %} {% block post_body %}
{% if post.has_agenda and post.agenda_html %} {% if post.has_agenda and post.agenda_html %}
@@ -59,7 +61,7 @@
<h2>Protokoll</h2> <h2>Protokoll</h2>
{{ post.protocol_html|safe }} {{ post.protocol_html|safe }}
{% endif %} {% endif %}
{% endblock %} {% endblock post_body %}
{% block event_details_mobile %} {% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4"> <hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
@@ -73,7 +75,7 @@
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
{% endblock %} {% endblock event_details_mobile %}
{% block docu_buttons %} {% block docu_buttons %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
@@ -176,7 +178,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock docu_buttons %}
{% block update_button_mobile %} {% block update_button_mobile %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
@@ -184,4 +186,4 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten <i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock update_button_mobile %}

View File

@@ -25,4 +25,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -116,4 +116,4 @@
</section> </section>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -10,13 +10,13 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten <i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock update_button_desktop %}
{% block post_body %} {% block post_body %}
{% if post.body %} {% if post.body %}
{{ post.body|safe|tags_to_url }} {{ post.body|safe|tags_to_url }}
{% endif %} {% endif %}
{% endblock %} {% endblock post_body %}
{% block files_buttons %} {% block files_buttons %}
{% if files %} {% if files %}
@@ -51,7 +51,7 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endblock %} {% endblock files_buttons %}
{% block update_button_mobile %} {% block update_button_mobile %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
@@ -59,4 +59,4 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten <i class="fa-solid fa-pen-to-square mr-1"></i>Artikel bearbeiten
</a> </a>
{% endif %} {% endif %}
{% endblock %} {% endblock update_button_mobile %}

View File

@@ -23,4 +23,4 @@
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -7,7 +7,7 @@
<meta content="{{ post.title }}" property="og:title"> <meta content="{{ post.title }}" property="og:title">
<meta content="article" property="og:type"> <meta content="article" property="og:type">
<meta content="" property="og:url"> <meta content="" property="og:url">
{% endblock %} {% endblock extraheader %}
{% block content %} {% block content %}
<!-- Main Content --> <!-- Main Content -->
@@ -81,12 +81,12 @@
</div> </div>
{% block update_button_desktop %} {% block update_button_desktop %}
{% endblock %} {% endblock update_button_desktop %}
</div> </div>
</div> </div>
<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 }}');">
{% block event_details_desktop %} {% block event_details_desktop %}
{% endblock %} {% endblock event_details_desktop %}
</div> </div>
</section> </section>
<section class="mx-4 z-10"> <section class="mx-4 z-10">
@@ -94,17 +94,17 @@
<div class="db-page-content-left"> <div class="db-page-content-left">
<!-- Content from DB here: --> <!-- Content from DB here: -->
{% block post_body %} {% block post_body %}
{% endblock %} {% endblock post_body %}
</div> </div>
{% block event_details_mobile %} {% block event_details_mobile %}
{% endblock %} {% endblock event_details_mobile %}
{% block docu_buttons %} {% block docu_buttons %}
{% endblock %} {% endblock docu_buttons %}
{% block files_buttons %} {% block files_buttons %}
{% endblock %} {% endblock files_buttons %}
<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">
@@ -120,7 +120,7 @@
</article> </article>
{% block update_button_mobile %} {% block update_button_mobile %}
{% endblock %} {% endblock update_button_mobile %}
</section> </section>
{% if related_posts and related_posts|length > 1 %} {% if related_posts and related_posts|length > 1 %}
@@ -146,4 +146,4 @@
</section> </section>
{% endif %} {% endif %}
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -14,4 +14,4 @@
</section> </section>
</div> </div>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -45,4 +45,4 @@
{% endif %} {% endif %}
</section> </section>
</main> </main>
{% endblock %} {% endblock content %}

View File

@@ -1,188 +0,0 @@
0***Referenzordner_SE_123.456***
0***Referenzordner_SE_123.456_Multimedia_only***
3D_Vision_UE_183.130
aa_92_Studienplan_nicht_veraendern
aaa Richtlinien für Ordnerstruktur~
aaa_Richtlinien für Ordnerstruktur~
aaa_Richtlinien für Ordnerstruktur3.txt~
aaa_Richtlinien für Ordnerstruktur.txt~
aa_Bachelor-Vertiefungen
aa_Freifach
aa_Master-Vertiefungen
aa_Skripten_und_Bücher
aa_sonstiges
Advanced_Wireless_Communications_2_VO_389.172
Advanced_Wireless_Communications_2_VO_389 .172_Multimedia_only
AKmath_VU_101.748
Algorithmen_und_Datenstrukturen_2_VU_186.866
Analoge_Integrierte_Schaltungen_VU
Anatomy_and_Histology_VO_185.330
Aufbau_hochdynamischer_Drehstromantriebe_LU_372.560
Automatisierungs_und_Steuerungssysteme_LU
Automatisierungs_und_Steuerungssysteme_LU_Multimedia_only
Automatisierungs_und_Steuerungssysteme_VO
Automatisierungs_und_Steuerungssysteme_VO_Multimedia_only
Automatisierung_VU_376.000
Automatisierung_VU_376.000_Multimedia_only
Betriebssysteme_VO_182.711
Biomedical_Sensors_and_Signals_VO_351.029
Biophysik_VO_362.111
Bitte_lies_mich.txt
Codegeneratoren_VO_185.416__Multimedia_only
Communication_Networks_1_VO_389.158
Communications_Networks_2_VU_389.165
Computerunterstütztes_Japanisch_1_VO_057.011
Datenkommunikation_VO_389.153
Deterministische_Signalverarbeitung_VU
Digital_Communications_1_VU_389.163
Digital_Communications_2_VU_389.164
Digitale_Integrierte_Schaltungen_VU_384.086
Digitale_Systeme_UE
Echtzeitsysteme_VO_182.713
Einführung_in_die_Betriebswirtschaftslehre_VO_(WU)_xxx.xxx
Einführung_in_die_Betriebswirtschaftslehre_VO_(WU)_xxx.xxx_Multimedia_only
Elektrische_Antriebe_Labor_UE
Elektrische_Antriebe_VU_370.027
Elektrische_Antriebe_VU_370.027_Mulitmedia
Elektrische_Maschinen_VO_372.025
Elektrochemische_Energieumwandlung_und_Energiespeicherung_VO_164.288
Elektrochemische_Messtechniken_und_Untersuchungsmethoden_VO_164.256
Elektrochemische_Messtechniken_und_Untersuchungsmethoden_VO_164.256_Multimedia_only
Elektrodynamik_VU_354.077
Elektrodynamik_VU_354.077_Multimedia_only
Elektronische_Bauelemente_VU_362.072
Elektrotechnik_1_UE_351.009
Elektrotechnik_1_VO_351.008
Elektrotechnik_2_UE_351.012
Elektrotechnik_2_VO_351.011
Elektrotechnik_2_VO_351.011_Multimedia_only
Embedded_Systems_in_FPGAs_VU_384.154
EMVgerechter_Schaltungsentwurf_UE_372.015
EMVgerechter_Schaltungsentwurf__VO_370.030
EMV_und_Netzrückwirkungen_VU
Energiemodelle_und_Analysen_VU
Energieoekonomie_VU_373.010
Energiesysteme_und_Netze_VO_370.021
Energieübertragung_und_Hochspannungstechnik_VO_370.028
Energieübertragung_und_Hochspannungstechnik_VO_370.028_Multimedia_only
Energieversorgung_VU_370.002
Entsorgung_und_Recycling_in_der_Elektrotechnik_VO_355.674
et-bachelor.zip
European_Union_VO_164.287
Fachvertiefung_Antriebstechnik_VU_372.750
Fachvertiefung_Automatisierung_VU_376.042
Fachvertiefung_Biophysik_VU_362.138
Fachvertiefung_Energiesysteme_VU_370.007
Fachvertiefung_Mathematik_VU_101.440
Fachvertiefung_Mikroelektronik_Bauelemente_Labor_VU_362.136
Fachvertiefung_Signale_und_Systeme_VU_389.142
Fachvertiefung_Softwareentwicklung_3VU_84.141
Fachvertiefung_Telekomunikation_VU_389.141
Gewerblicher_Rechtschutz_für_Techniker_VO_360.012
Grundlagen_der_Betriebs_und_Unternehmensführung_VO_330.001
Grundlagen_der_elektrischen_Bahnen_VO_371.816
Halbleiterelektronik_VO_362.142
Halbleiterphysik_VU_362.069
Human_Machine_Interaction_VO_384.160
Industrielle_Kommunikationstechnik_VO_384.168
Integrierte_Bauelemente_VU
Integrierte_Schaltungstechnik_VO
Introduction_into_Biophysics_VO
IT_Projektplanung_und_Vergaberecht_VO_384.107
KFZ-Technik_VO_315.282
Kraftwerke_VO_370.026
Labor_Energieversorgung_UE_370.024
Laser_in_der_Medizintechnik_VU
Leistungselektronik_und_Stromrichtertechnik_VU_372.033
Leistungselektronik_und_Stromrichtertechnik_VU_372.033_Multimedia_only
Machine_Vision_and_Cognitive_Robotics_VU
Machine_Vision_and_Cognitive_Robotics_VU_MultimediaOnly
Maschinen_und_Antriebe_VU_370.015
Mathematik_1_UE
Mathematik_1_VO
Mathematik_2_UE_101.683
Mathematik_2_VO_101.682
Mathematik_3_UE_101.686
Mathematik_3_VO_101.685
Mathematik_3_VU_(-2017)
Mathematische_Methoden_der_Modellbildung_und_Simulation_VL_101.555
Mechatronische_Systeme_LU
Mechatronische_Systeme_VO
Mechatronische_Systeme_VO_Multimediaonly
Messtechnik_Labor_LU
Messtechnik_VU
Messtechnik_VU_old stuff
Mikrocomputer_Labor_LU_384.996
Mikrocomputer_VU_384.173
Mikroelektronische_Konzepte_fuer_Biomedizinische_Interfaces_VU
Modellbildung_VU
Modellierung_Elektronischer_Bauelemente_VU
Nachhaltige_Energietraeger_VO_141.217
Network_Security_VU_389.159
Nutzung_der_Sonnenenergie_VO_372.383
Objektorientiertes_Programmieren_VU
Optimierung_VU
Optimierung_VU_Multimedia
Optische_Messtechnik_VU
Optische_Nachrichtentechnik_VO
Optische_Systeme_VO_387.028
Optoelektronische_inegrierte_Schaltungen_VO
Parameter_Estimation_Methods_VO_389.119
Photonik_1_VO_387.026
Photonik_2_VU_387.068
Physik_UE_141.A23
Physik_VO_141.A19
Privates_Wirtschaftsrecht_SoftSkill
Programmieren_1_VU
Programmieren_2_VU
Projektmanagement_VO
Prozesschemie_für_Mikro_und_Nanoelektronik_VU_362.149
Prozesse_und_Verfahren_VO
Quantenelektronik_VO_360.227
Regelungssysteme_1_VO
Regelungssysteme_1_VO_MultimediaOnly
Regelungssysteme_2_VO
Regelungssysteme_2_VO_MultimediaOnly
Regelungssysteme_Labor
Regenerative_Energiesysteme_VO_370_035
Ressourceneffizienz_VO_330.262
RF_Techniques_VU_354.058
Robotik_und_Automatisierung_in_der_KFZ-Elektronik_VO
Schaltnetzteile_1_WS
Schaltnetzteile_2_SS
Schaltungstechnik_VU_354.019
Schutztechnik_in_elektrischen_Netzen_VO_370.045
Selected_Topics_in_Energy_Economics_and_Environment_370043_VU
Sensoren_und_optoelektronische_Bauelemente_VO
Sensorik_und_Sensorsysteme_VO
Sensorik_und_Sensorsysteme_VO (copy)
Sensorik_VU
Signale_und_Systeme_1_unsortiert
Signale_und_Systeme_1_VU_387.083_neu
Signale_und_Systeme_1_VU_alt_351.015
Signale_und_Systeme_1_VU_alt_351.015_Multimedia_Only
Signale_und_Systeme_2_VU_389.055
Signal_Processing_1_VU_389.166
Signal_Processing_2_VU_389.170
Simulation_elektrischer_Maschinen_und_Antriebe_372.023
Smart_Grids_Vertiefung_VU_370.033
Smart_Grids_VO_384.146
SoC_Architektur_und_Design_VU_384.156
Software_and_System_Engineering_VO_384.165
Software_Engineering_1_VU
Systemtechnik_in_der_Automation_VU
Technik_und_Gesellschaft_VO_351.018
Technische_Elektronik_LU_362.132
Technologie_der_Funktionswerkstoffe_VO
telekom
Telekommunikation_VU_389.138
Telekommunikation_VU_389.138_Multimedia_Only
Theoretische_Informatik_und_Logik_für_Elektrotechnik_185.A84_VU
Vertiefung_Sozialkompetenz_und_Impulsalgorithmen_VU
Videoverarbeitung_VO_188.329
Wellenausbreitung_VU_389.064
Wellenausbreitung_VU_389.064_Multimedia_Only
Werkstoffe_VU
Wireless_Communications_1_VU_389.157
Wireless_OFDM_systems_VO_389.133
Wirtschaft_1_VU