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.utils.hashed import hashed
from members.models import Member
logger = logging.getLogger(__name__)
host = "ldap://juri.fet.htu.tuwien.ac.at"
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):
server = Server(host, port=port, use_ssl=True)
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
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.contrib.auth.forms import PasswordChangeForm
from .authentications import authentication, change_password, get_finance_perm
from .authentications import change_password
logger = logging.getLogger(__name__)
class LoginForm(AuthenticationForm):
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
# TODO: fix me
class LdapPasswordChangeForm(PasswordChangeForm):
def clean_old_password(self):
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
def save(self):

View File

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

View File

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

View File

@@ -6,7 +6,8 @@ class FETHeaderMiddleware(RemoteUserMiddleware):
def process_request(self, request):
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)

View File

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

View File

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

View File

@@ -216,7 +216,7 @@ class BillAdmin(admin.ModelAdmin):
"fields": (
"bill_creator",
"resolution",
)
),
},
),
(
@@ -227,7 +227,7 @@ class BillAdmin(admin.ModelAdmin):
"bankdata",
("get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic"),
"get_qrcode",
)
),
},
),
(
@@ -241,7 +241,7 @@ class BillAdmin(admin.ModelAdmin):
"amount",
"only_digital",
"file_field",
)
),
},
),
(
@@ -251,7 +251,7 @@ class BillAdmin(admin.ModelAdmin):
"comment",
"wiref",
"status",
)
),
},
),
)
@@ -308,7 +308,7 @@ class BillAdmin(admin.ModelAdmin):
@admin.display(description="QR Code")
def get_qrcode(self, obj):
if obj.status != "C":
if obj.status != Bill.Status.CLEARED:
return "-"
try:
@@ -329,24 +329,26 @@ class BillAdmin(admin.ModelAdmin):
return "Daten für QR Code ungültig"
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")
def status_colored(self, obj):
# TODO: if there is a status without color, set nothing.
colors = {
"S": "red",
"C": "darkorange",
"F": "green",
"I": "blue",
Bill.Status.SUBMITTED: "red",
Bill.Status.CLEARED: "darkorange",
Bill.Status.FINISHED: "green",
Bill.Status.INCOMPLETED: "blue",
}
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.")
def make_cleared(self, request, queryset):
updated = queryset.update(status="C")
updated = queryset.update(status=Bill.Status.CLEARED)
self.message_user(
request,
ngettext(
@@ -360,7 +362,7 @@ class BillAdmin(admin.ModelAdmin):
@admin.action(description="Als 'Abgeschlossen' markieren.")
def make_finished(self, request, queryset):
updated = queryset.update(status="F")
updated = queryset.update(status=Bill.Status.FINISHED)
self.message_user(
request,
ngettext(
@@ -403,7 +405,7 @@ class ResolutionAdmin(admin.ModelAdmin):
"date",
"option",
"is_visible",
)
),
},
),
(
@@ -413,7 +415,7 @@ class ResolutionAdmin(admin.ModelAdmin):
"budget",
"total",
"budget_remaining",
)
),
},
),
(
@@ -463,7 +465,7 @@ class ResolutionAdmin(admin.ModelAdmin):
if fetmeeting is not None:
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("-")

View File

@@ -29,7 +29,7 @@ def get_cleaned_data(cleaned_data):
if resolution != "":
try:
cleaned_data["resolution"] = Resolution.objects.get(
Q(id=resolution) | Q(name=resolution)
Q(id=resolution) | Q(name=resolution),
)
except Exception:
raise ValidationError({"resolution_text": "Es gibt keinen Beschluss mit dieser ID."})
@@ -68,7 +68,10 @@ class BillCreateForm(forms.ModelForm):
# Resolution
resolution_text = forms.CharField(
required=False, label="Beschlussnummer", initial="", max_length=128
required=False,
label="Beschlussnummer",
initial="",
max_length=128,
)
class Meta:
@@ -122,7 +125,7 @@ class BillCreateForm(forms.ModelForm):
self.fields["payer"].autofocus = True
bank_data = BankData.objects.filter(
Q(bankdata_creator=member) & Q(is_disabled=False)
Q(bankdata_creator=member) & Q(is_disabled=False),
).first()
if bank_data is not None:
self.fields["name_text"].initial = bank_data.name
@@ -147,12 +150,17 @@ class BillUpdateForm(forms.ModelForm):
# only digital bill
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_text = forms.CharField(
required=False, label="Beschlussnummer", initial="", max_length=128
required=False,
label="Beschlussnummer",
initial="",
max_length=128,
)
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_delete_related = False
# delete wiref id from list if there are already 8 bills in wiref form.
qs = self.fields["wiref"].queryset.annotate(num_bills=Count("bill")).filter(num_bills__lt=8)
# 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=10)
)
# delete wiref id from if status is not 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")
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:
@@ -155,7 +158,9 @@ class Bill(models.Model):
REPRESENTATION = "R", "Bundesvertretung"
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):

Binary file not shown.

Binary file not shown.

View File

@@ -4,25 +4,33 @@ import os
from django.core.files import File
from pypdf import PdfReader, PdfWriter
from pypdf.constants import FieldDictionaryAttributes as FA
from .models import Bill, 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:
return False
bills = Bill.objects.filter(wiref=wiref).order_by("date")
# Get data for pdf
data = {}
data_invoice = {} # Own dict for fixing text to multiline
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,
f"DatumRow{count + 1}": str(elem.date.strftime("%d.%m.%Y")),
f"VerwendungszweckRow{count + 1}": elem.purpose,
# Replace decimal separator from '.' to ','
f"Betrag.{count}": str(elem.amount).replace(".", ","),
}
f"BetragRow{count + 1}": str(elem.amount).replace(".", ","),
},
)
data_invoice.update(
{
f"RechnungsaustellerinRow{count + 1}": elem.invoice,
},
)
# Get budget year
@@ -39,15 +47,15 @@ def generate_pdf(wiref):
data.update(
{
"Laufende Nummer": str(wiref.wiref_id),
"Rechnungsnummer": str(wiref.wiref_id),
"Budgetjahr": budget_year,
# Replace decimal separator from '.' to ','
"Summe": str(total).replace(".", ","),
}
},
)
# Write data in pdf
pdf_path = os.path.join(os.path.dirname(__file__), "static/Vorlage.pdf")
pdf_path = os.path.join(os.path.dirname(__file__), "static/wiref/Vorlage.pdf")
reader = PdfReader(pdf_path)
writer = PdfWriter()
writer.append(reader)
@@ -57,6 +65,13 @@ def generate_pdf(wiref):
data,
)
# 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)
@@ -65,5 +80,3 @@ def generate_pdf(wiref):
wiref.file_field.save(wiref_name, File(bytes_stream, wiref_name))
return True
return False

View File

@@ -34,7 +34,7 @@ def set_bankdata(creator, name, iban, bic, saving):
if saving is True:
# Disable old bank data.
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)
@@ -114,11 +114,7 @@ class BillUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
# Call bill if it's only yours.
def test_func(self):
if self.get_object().bill_creator.username == self.request.user.username:
return True
# Call handle_no_permissions method.
return False
return self.get_object().bill_creator.username == self.request.user.username
def handle_no_permission(self):
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)
event_date = models.DateField(
verbose_name="Event Datum", null=True, blank=True, default=timezone.now
verbose_name="Event Datum",
null=True,
blank=True,
default=timezone.now,
)
event_place = models.CharField(max_length=200, blank=True)
photographer = models.CharField(
verbose_name="Fotograph(en)", max_length=200, null=True, blank=True
verbose_name="Fotograph(en)",
max_length=200,
null=True,
blank=True,
)
DRAFT = "10"

View File

@@ -34,7 +34,8 @@ def get_image_list(folder_name):
img_dict = {
"title": img,
"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,
}

View File

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

View File

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

View File

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

View File

@@ -27,7 +27,9 @@ logger = logging.getLogger(__name__)
@authenticated_user
def index(request):
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)
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_slug = self.kwargs.get("slug")
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
@@ -143,7 +145,7 @@ class AttachmentDetailView(LoginRequiredMixin, DetailView):
topic_group_slug = self.kwargs.get("topic_group_slug")
topic_slug = self.kwargs.get("topic_slug")
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_slug = self.kwargs.get("topic_slug")
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")
slug = self.kwargs.get("slug")
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
return context
@@ -211,7 +213,7 @@ class FileUploadCreateView(LoginRequiredMixin, CreateView):
topic_slug = self.kwargs.get("topic_slug")
slug = self.kwargs.get("slug")
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
return context

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ class ActiveJobMemberManager(models.Manager):
)
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(
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):
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

View File

@@ -19,7 +19,8 @@ def get_jobs_sidebar(slug):
job_groups.remove(elem)
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()

View File

@@ -27,7 +27,8 @@ def jobs(request, slug=None):
raise Http404("wrong job")
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()

View File

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

View File

@@ -149,10 +149,14 @@ class PostSearchForm(forms.Form):
month = forms.ChoiceField(label="Monat", choices=month_choices, required=False)
compact_view = forms.BooleanField(
label="Kompakte Ansicht", required=False, widget=CheckboxInput
label="Kompakte Ansicht",
required=False,
widget=CheckboxInput,
)
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):

View File

@@ -10,20 +10,14 @@ class PublishedManager(models.Manager):
"""
publish all posts with status 'PUBLIC'
"""
if public:
qs = self.get_queryset().filter(status="20")
else:
qs = self.get_queryset()
qs = self.get_queryset().filter(status="20") if public else self.get_queryset()
return qs
def published_all(self, public=True):
"""
publish all posts with status 'PUBLIC' and 'ONLY_INTERN'
"""
if public:
qs = self.get_queryset().filter(~Q(status="10"))
else:
qs = self.get_queryset()
qs = self.get_queryset().filter(~Q(status="10")) if public else self.get_queryset()
return qs
@@ -35,7 +29,7 @@ class PostManager(PublishedManager, models.Manager):
When(post_type="N", then="public_date"),
When(post_type="E", then="event_start__date"),
When(post_type="F", then="event_start__date"),
)
),
)
return qs.order_by("-date", "-id")
@@ -74,7 +68,7 @@ class ArticleManager(PublishedManager, models.Manager):
date=Case(
When(post_type="N", then="public_date"),
When(post_type="E", then="event_start__date"),
)
),
)
return qs.order_by("-date", "-id")
@@ -87,7 +81,7 @@ class ArticleManager(PublishedManager, models.Manager):
__month = post_date.month
__year = post_date.year
if __month != 0:
if __month != 1:
__month -= 1
else:
# 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(
(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()
)
@@ -120,7 +114,7 @@ class NewsManager(PublishedManager, models.Manager):
qs = qs.annotate(
date=Case(
When(post_type="N", then="public_date"),
)
),
)
return qs.order_by("-date")
@@ -136,7 +130,7 @@ class AllEventManager(PublishedManager, models.Manager):
date=Case(
When(post_type="E", then="event_start__date"),
When(post_type="F", then="event_start__date"),
)
),
)
return qs.order_by("-date")
@@ -157,7 +151,7 @@ class EventManager(PublishedManager, models.Manager):
qs = qs.annotate(
date=Case(
When(post_type="E", then="event_start__date"),
)
),
)
return qs.order_by("-date")
@@ -182,7 +176,7 @@ class FetMeetingManager(PublishedManager, models.Manager):
qs = qs.annotate(
date=Case(
When(post_type="F", then="event_start__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)
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"))]
@@ -137,9 +140,6 @@ class Post(models.Model):
self.title,
)
def get_absolute_url(self):
return reverse("posts:post", kwargs={"slug": self.slug})
def save(self, *args, **kwargs):
# save the post with some defaults
if not self.public_date:
@@ -150,6 +150,9 @@ class Post(models.Model):
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("posts:post", kwargs={"slug": self.slug})
@property
def agenda_html(self) -> str | None:
"Agenda HTML from Etherpad Pad"
@@ -188,7 +191,9 @@ class Post(models.Model):
ep_set_html(self.protocol_key, value)
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
@@ -302,10 +307,7 @@ class Post(models.Model):
@property
def published(self):
if self.status == self.Status.PUBLIC:
return True
return False
return self.status == self.Status.PUBLIC
class News(Post):
@@ -328,16 +330,16 @@ class Event(Post):
only_events = EventManager()
all_events = AllEventManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "E"
class Meta:
proxy = True
verbose_name = "Event"
verbose_name_plural = "Events"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "E"
def save(self, *args, **kwargs):
if not self.post_type:
self.post_type = "E"
@@ -355,16 +357,16 @@ class Event(Post):
class FetMeeting(Event):
objects = FetMeetingManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "F"
class Meta:
proxy = True
verbose_name = "Fet Sitzung"
verbose_name_plural = "Fet Sitzungen"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.post_type = "F"
def save(self, *args, **kwargs):
self.title = "Fachschaftssitzung"
if not self.slug:
@@ -398,8 +400,7 @@ class FetMeeting(Event):
def __get_slug(self) -> str:
slug = slugify(self.event_start.date()) + "-" + slugify("Fachschaftssitzung")
if Post.objects.filter(slug=slug).exists():
if Post.objects.get(slug=slug).id != self.id:
if Post.objects.filter(slug=slug).exists() and Post.objects.get(slug=slug).id != self.id:
raise ValidationError("Es existiert bereits eine Sitzung mit demselben Datum.")
return slug

View File

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

View File

@@ -146,13 +146,13 @@ class PostDetailView(DetailView):
Helper function for getting previous post
"""
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(
# Get posts which are in the future.
Q(date__lt=self.object.date)
# 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 there are any prev posts, then take the latest one.
@@ -173,7 +173,7 @@ class PostDetailView(DetailView):
# Get posts which are in the past.
Q(date__gt=self.object.date)
# 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 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")
content = "inline; filename=%s" % filename
content = f"inline; filename={filename}"
response["Content-Disposition"] = content
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 = sqs_gallery.filter(
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)
@@ -31,7 +31,7 @@ class FetUserSearchForm(SearchForm):
sqs_member = self.searchqueryset.models(Member)
sqs_member = sqs_member.filter(
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)
@@ -39,7 +39,7 @@ class FetUserSearchForm(SearchForm):
SQ(title__icontains=self.cleaned_data["q"])
| SQ(body__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([])
@@ -82,20 +82,20 @@ class NonUserSearchForm(SearchForm):
sqs_gallery = self.searchqueryset.models(Album).filter(status="20")
sqs_gallery = sqs_gallery.filter(
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 = sqs_member.filter(
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 = sqs_post.filter(
SQ(title__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([])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
{% load flatpages %}
{% load static %}
{% load version %}
{% load flatpages static version %}
<!DOCTYPE html>
<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' %}">
{% block galleryheader %}
{% endblock %}
{% endblock galleryheader %}
{% block extraheader %}
{% endblock %}
{% endblock extraheader %}
</head>
<body x-bind="documentBody">
@@ -203,7 +201,7 @@
</nav>
{% block content %}
{% endblock %}
{% endblock content %}
<footer>
<ul class="icon-list">

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
{% extends "documents/base.html" %}
{% block title %}{{ pad.name }}{% endblock %}
{% block wrapper %}
@@ -31,4 +30,4 @@
</script>
{% endif %}
{% endblock %}
{% endblock wrapper %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,4 +20,4 @@
</form>
</div>
</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>
</div>
</main>
{% endblock %}
{% endblock content %}

View File

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

View File

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

View File

@@ -20,4 +20,4 @@
</form>
</div>
</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>
</div>
</main>
{% endblock %}
{% endblock content %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
<meta content="{{ member.firstname }}" property="og:profile:first_name">
<meta content="{{ member.surname }}" property="og:profile:last_name">
<meta content="profile" property="og:type">
{% endblock %}
{% endblock extraheader %}
{% block member_content %}
<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>
{% endif %}
</section>
{% endblock %}
{% endblock member_content %}

View File

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

View File

@@ -5,9 +5,11 @@
{% 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 %}
@@ -16,7 +18,7 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a>
{% endif %}
{% endblock %}
{% endblock update_button_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">
@@ -46,13 +48,13 @@
{% endif %}
</span>
</div>
{% endblock %}
{% endblock event_details_desktop %}
{% block post_body %}
{% if post.body %}
{{ post.body|safe|tags_to_url }}
{% endif %}
{% endblock %}
{% endblock post_body %}
{% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
@@ -66,7 +68,7 @@
{% endif %}
</ul>
</div>
{% endblock %}
{% endblock event_details_mobile %}
{% block update_button_mobile %}
{% if request.user.is_authenticated %}
@@ -74,4 +76,4 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>Event bearbeiten
</a>
{% endif %}
{% endblock %}
{% endblock update_button_mobile %}

View File

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

View File

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

View File

@@ -5,9 +5,11 @@
{% 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 %}
@@ -16,7 +18,7 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a>
{% endif %}
{% endblock %}
{% endblock update_button_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">
@@ -46,7 +48,7 @@
{% endif %}
</span>
</div>
{% endblock %}
{% endblock event_details_desktop %}
{% block post_body %}
{% if post.has_agenda and post.agenda_html %}
@@ -59,7 +61,7 @@
<h2>Protokoll</h2>
{{ post.protocol_html|safe }}
{% endif %}
{% endblock %}
{% endblock post_body %}
{% block event_details_mobile %}
<hr class="lg:hidden -mx-4 border-gray-200 dark:border-gray-800 dark:border my-4">
@@ -73,7 +75,7 @@
{% endif %}
</ul>
</div>
{% endblock %}
{% endblock event_details_mobile %}
{% block docu_buttons %}
{% if request.user.is_authenticated %}
@@ -176,7 +178,7 @@
</div>
</div>
{% endif %}
{% endblock %}
{% endblock docu_buttons %}
{% block update_button_mobile %}
{% if request.user.is_authenticated %}
@@ -184,4 +186,4 @@
<i class="fa-solid fa-pen-to-square mr-1"></i>FET Sitzung bearbeiten
</a>
{% endif %}
{% endblock %}
{% endblock update_button_mobile %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,4 +45,4 @@
{% endif %}
</section>
</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