Compare commits

8 Commits

14 changed files with 377 additions and 280 deletions

View File

@@ -96,6 +96,8 @@ docker build -t django-nginx-image -f nginx/Dockerfile ./nginx
### Start docker container ### Start docker container
Add email password for 'Verleih' account to EMAIL_HOST_PASSWORD in the docker compose file!
Build the docker containers: Build the docker containers:
```bash ```bash

View File

@@ -9,7 +9,7 @@ services:
depends_on: depends_on:
- django-homepage - django-homepage
volumes: volumes:
- files-volume:/usr/src/app/files - files-volume:/usr/src/app/files
- ./gallery:/usr/src/app/files/uploads/gallery - ./gallery:/usr/src/app/files/uploads/gallery
- ./assets:/usr/src/app/assets:ro - ./assets:/usr/src/app/assets:ro
networks: networks:
@@ -24,7 +24,9 @@ services:
SECRET_KEY: "sae34sADfrFr89E!Gl#f!34hdjGR#!jopi4qFEr#4R56rT56zT2#wE1!feGp" SECRET_KEY: "sae34sADfrFr89E!Gl#f!34hdjGR#!jopi4qFEr#4R56rT56zT2#wE1!feGp"
MYSQL_USER: "user" MYSQL_USER: "user"
MYSQL_PASSWORD: "hgu" MYSQL_PASSWORD: "hgu"
ETHERPAD_GROUP: "g.snlbqn7S6ksRbom3" ETHERPAD_GROUP: "g.snlbqn7S6ksRbom3"
EMAIL_HOST_USER: "verleih@fet.at"
EMAIL_HOST_PASSWORD: ""
depends_on: depends_on:
mysql: mysql:
condition: service_healthy condition: service_healthy

View File

@@ -18,6 +18,8 @@ env = environ.Env(
ETHERPAD_GROUP=(str, ""), ETHERPAD_GROUP=(str, ""),
GALLERY_PATH=(str, "uploads/gallery"), GALLERY_PATH=(str, "uploads/gallery"),
MC_MASTERPASSWORD=(str, ""), MC_MASTERPASSWORD=(str, ""),
EMAIL_HOST_USER=(str, ""),
EMAIL_HOST_PASSWORD=(str, ""),
) )
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -103,6 +105,8 @@ else:
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "buran.htu.tuwien.ac.at" EMAIL_HOST = "buran.htu.tuwien.ac.at"
EMAIL_PORT = 587 EMAIL_PORT = 587
EMAIL_HOST_USER = env("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD")
EMAIL_USE_TLS = True EMAIL_USE_TLS = True

View File

@@ -1,124 +1,121 @@
from django.contrib import admin, messages from django.contrib import admin, messages
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from .forms import RentalAdminForm, RentalItemAdminForm from .forms import RentalAdminForm, RentalItemAdminForm
from .models import Rental, RentalItem from .models import Rental, RentalItem
from .utils import generate_rental_pdf from .utils import generate_rental_pdf
@admin.register(Rental) @admin.register(Rental)
class RentalAdmin(admin.ModelAdmin): class RentalAdmin(admin.ModelAdmin):
form = RentalAdminForm form = RentalAdminForm
model = Rental model = Rental
list_display = [ list_display = [
"id", "id",
"firstname", "firstname",
"surname", "surname",
"status", "status",
"total_disposit", "total_disposit",
"date_start", "date_start",
"date_end", "date_end",
] ]
ordering = ["-id"] ordering = ["-id"]
readonly_fields = ["total_disposit"] readonly_fields = ["total_disposit"]
fieldsets = ( fieldsets = (
( (
"Persönliche Daten", "Persönliche Daten",
{ {
"fields": ( "fields": (
("firstname", "surname"), ("firstname", "surname"),
("organization", "matriculation_number"), ("organization", "matriculation_number"),
("email", "phone"), ("email", "phone"),
), ),
}, },
), ),
( (
"Verleih", "Verleih",
{ {
"fields": ( "fields": (
("date_start", "date_end"), ("date_start", "date_end"),
"reason", "reason",
"rentalitems", "rentalitems",
"total_disposit", ("total_disposit", "intern"),
), ),
}, },
), ),
( (
"Sonstiges", "Sonstiges",
{ {
"fields": ( "fields": (
"comment", "comment",
"file_field", "file_field",
"status", "status",
), ),
}, },
), ),
) )
def add_view(self, request, form_url="", extra_context=None): def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {} extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().add_view(request, form_url, extra_context=extra_context) return super().add_view(request, form_url, extra_context=extra_context)
def change_view(self, request, object_id, form_url="", extra_context=None): def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {} extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
extra_context["generate_rental_pdf"] = True extra_context["generate_rental_pdf"] = True
return super().change_view(request, object_id, form_url, extra_context=extra_context) return super().change_view(request, object_id, form_url, extra_context=extra_context)
def response_change(self, request, obj): def response_change(self, request, obj):
if "_generate_rental_pdf" in request.POST: if "_generate_rental_pdf" in request.POST:
if generate_rental_pdf(obj): if generate_rental_pdf(obj):
self.message_user( self.message_user(
request, request,
"Neues Verleihformular wurde generiert.", "Neues Verleihformular wurde generiert.",
messages.SUCCESS, messages.SUCCESS,
) )
else: else:
self.message_user( self.message_user(
request, request,
( (
"Das PDF-Dokument konnte nicht generiert werden, da der Status nicht auf " "Das PDF-Dokument konnte nicht generiert werden, da der Status nicht auf "
"'Verleih genehmigt' gesetzt ist." "'Verleih genehmigt' gesetzt ist."
), ),
messages.WARNING, messages.WARNING,
) )
return HttpResponseRedirect(".") return HttpResponseRedirect(".")
return super().response_change(request, obj) return super().response_change(request, obj)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
obj.author = request.user obj.author = request.user
super().save_model(request, obj, form, change) super().save_model(request, obj, form, change)
@admin.display(description="Kaution (EUR)") @admin.display(description="Kaution insgesamt")
def total_disposit(self, obj): def total_disposit(self, obj):
total_disposit = 0 total_disposit = obj.calc_total_deposit()
for elem in obj.rentalitems.all(): return f"{total_disposit}"
total_disposit += elem.deposit
return f"{total_disposit}" @admin.register(RentalItem)
class RentalItemAdmin(admin.ModelAdmin):
form = RentalItemAdminForm
@admin.register(RentalItem) model = RentalItem
class RentalItemAdmin(admin.ModelAdmin):
form = RentalItemAdminForm ordering = ["name"]
model = RentalItem
def add_view(self, request, form_url="", extra_context=None):
ordering = ["name"] extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
def add_view(self, request, form_url="", extra_context=None): return super().add_view(request, form_url, extra_context=extra_context)
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." def change_view(self, request, object_id, form_url="", extra_context=None):
return super().add_view(request, form_url, extra_context=extra_context) extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
def change_view(self, request, object_id, form_url="", extra_context=None): return super().change_view(request, object_id, form_url, extra_context=extra_context)
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." def save_model(self, request, obj, form, change):
return super().change_view(request, object_id, form_url, extra_context=extra_context) obj.author = request.user
super().save_model(request, obj, form, change)
def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)

View File

@@ -1,53 +1,53 @@
import logging import logging
from django.core.mail import EmailMessage from django.conf import settings
from django.core.mail import EmailMessage
RENTAL_EMAIL = "verleih@fet.at"
logger = logging.getLogger(__name__) RENTAL_EMAIL = settings.EMAIL_HOST_USER
logger = logging.getLogger(__name__)
def send_mail_approved(obj):
subject = f"FET-Verleih #{obj.id}: {obj.get_status_display()}" def send_mail_approved(obj):
subject = f"FET-Verleih #{obj.id}: {obj.get_status_display()}"
total_deposit = 0 total_deposit = obj.calc_total_deposit()
for rentalitem in obj.rentalitems.all():
total_deposit += rentalitem.deposit message = (
f"Hallo {obj.firstname},\n\n"
message = ( f"deine Verleihanfrage mit der Nummer #{obj.id} wurde erfolgreich genehmigt. Die "
f"Hallo {obj.firstname},\n" f"Gegenstände können am {obj.date_start.strftime('%d.%m.%Y')} während der Beratungszeit "
f"deine Verleihanfrage mit der Nummer #{obj.id} wurde erfolgreich genehmigt. Die " "(Montag - Donnerstag: 09:00 - 14:00, Freitag: 09:00 - 12:00) abgeholt werden.\n"
f"Gegenstände können am {obj.date_start.strftime('%d.%m.%Y')} während der Beratungszeit " )
"(Montag - Donnerstag: 09:00 - 14:00, Freitag: 09:00 - 12:00) abgeholt werden. Bitte bring "
f"den Gesamtpfand in Höhe von {total_deposit} € in bar mit.\n" if total_deposit > 0:
"Liebe Grüße,\n" message += f"Bitte bring den Gesamtpfand in Höhe von {total_deposit} € in bar mit.\n"
"das Verleih-Team"
) message += "\nLiebe Grüße,\ndas Verleih-Team"
email = EmailMessage( email = EmailMessage(
subject, message, from_email=RENTAL_EMAIL, to=[obj.email], cc=[RENTAL_EMAIL] subject, message, from_email=RENTAL_EMAIL, to=[obj.email], cc=[RENTAL_EMAIL]
) )
try: try:
email.send() email.send()
except Exception as exc: except Exception as exc:
logger.error("Failed to send approval email for rental #%s. Error: %s", obj.id, exc) logger.info("Failed to send approval email for rental #%s. Error: %s", obj.id, exc)
def send_mail_rejected(obj): def send_mail_rejected(obj):
subject = f"FET-Verleih #{obj.id}: {obj.get_status_display()}" subject = f"FET-Verleih #{obj.id}: {obj.get_status_display()}"
message = ( message = (
f"Hallo {obj.firstname},\n" f"Hallo {obj.firstname},\n\n"
f"deine Verleihanfrage mit der Nummer #{obj.id} wurde abgelehnt.\n" f"deine Verleihanfrage mit der Nummer #{obj.id} wurde abgelehnt.\n\n"
"Liebe Grüße,\n" "Liebe Grüße,\n"
"das Verleih-Team" "das Verleih-Team"
) )
email = EmailMessage( email = EmailMessage(
subject, message, from_email=RENTAL_EMAIL, to=[obj.email], cc=[RENTAL_EMAIL] subject, message, from_email=RENTAL_EMAIL, to=[obj.email], cc=[RENTAL_EMAIL]
) )
try: try:
email.send() email.send()
except Exception as exc: except Exception as exc:
logger.error("Failed to send rejection email for rental #%s. Error: %s", obj.id, exc) logger.info("Failed to send rejection email for rental #%s. Error: %s", obj.id, exc)

View File

@@ -0,0 +1,6 @@
from django.db import models
class RentalItemsManager(models.Manager):
def get_queryset(self):
return super().get_queryset().order_by("name")

View File

@@ -0,0 +1,55 @@
# Generated by Django 5.2.7 on 2025-10-30 12:05
import django.core.validators
import rental.validators
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='RentalItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('description', models.CharField(blank=True, default='', max_length=128, verbose_name='Beschreibung')),
('deposit', models.DecimalField(decimal_places=2, default=0, max_digits=6, verbose_name='Kaution (EUR)')),
('image', models.ImageField(blank=True, null=True, upload_to='rentalitems', verbose_name='Bild')),
('induction', models.BooleanField(default=False, verbose_name='Einschulung notwendig')),
('location', models.CharField(blank=True, default='', max_length=128, verbose_name='Standort')),
],
options={
'verbose_name': 'Verleihgegenstand',
'verbose_name_plural': 'Verleihgegenstände',
},
),
migrations.CreateModel(
name='Rental',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('firstname', models.CharField(max_length=128, verbose_name='Vorname')),
('surname', models.CharField(max_length=128, verbose_name='Nachname')),
('matriculation_number', models.CharField(max_length=8, verbose_name='Matrikelnummer')),
('email', models.EmailField(max_length=254, verbose_name='E-Mail')),
('phone', models.CharField(max_length=32, validators=[rental.validators.PhoneNumberValidator()], verbose_name='Telefonnummer')),
('organization', models.CharField(max_length=128, verbose_name='Organisation')),
('date_start', models.DateField(verbose_name='Abholdatum')),
('date_end', models.DateField(verbose_name='Rückgabedatum')),
('reason', models.TextField(max_length=500, verbose_name='Grund der Ausleihe')),
('comment', models.TextField(blank=True, default='', max_length=500, verbose_name='Kommentar')),
('file_field', models.FileField(blank=True, null=True, upload_to='uploads/rental/rental/', validators=[django.core.validators.FileExtensionValidator(['pdf'])], verbose_name='Verleihformular')),
('status', models.CharField(choices=[('S', 'Eingereicht'), ('A', 'Verleih genehmigt'), ('J', 'Verleih abgelehnt'), ('I', 'Verleihgegenstände ausgegeben'), ('R', 'Verleihgegenstände zurückgegeben')], default='S', max_length=1, verbose_name='Status')),
('rentalitems', models.ManyToManyField(to='rental.rentalitem', verbose_name='Verleihgegenstände')),
],
options={
'verbose_name': 'Verleih',
'verbose_name_plural': 'Verleih',
},
),
]

View File

View File

@@ -3,6 +3,7 @@ from django.db import models
from django.forms import ValidationError from django.forms import ValidationError
from .mails import send_mail_approved, send_mail_rejected from .mails import send_mail_approved, send_mail_rejected
from .managers import RentalItemsManager
from .validators import PhoneNumberValidator from .validators import PhoneNumberValidator
@@ -22,6 +23,8 @@ class RentalItem(models.Model):
location = models.CharField(verbose_name="Standort", max_length=128, blank=True, default="") location = models.CharField(verbose_name="Standort", max_length=128, blank=True, default="")
objects = RentalItemsManager()
class Meta: class Meta:
verbose_name = "Verleihgegenstand" verbose_name = "Verleihgegenstand"
verbose_name_plural = "Verleihgegenstände" verbose_name_plural = "Verleihgegenstände"
@@ -41,6 +44,7 @@ class Rental(models.Model):
) )
organization = models.CharField(verbose_name="Organisation", max_length=128) organization = models.CharField(verbose_name="Organisation", max_length=128)
intern = models.BooleanField(verbose_name="Interner Verleih", default=False)
date_start = models.DateField(verbose_name="Abholdatum") date_start = models.DateField(verbose_name="Abholdatum")
date_end = models.DateField(verbose_name="Rückgabedatum") date_end = models.DateField(verbose_name="Rückgabedatum")
@@ -107,3 +111,12 @@ class Rental(models.Model):
if self.date_start > self.date_end: if self.date_start > self.date_end:
raise ValidationError("Das Abholdatum muss vor dem Rückgabedatum liegen.") raise ValidationError("Das Abholdatum muss vor dem Rückgabedatum liegen.")
def calc_total_deposit(self) -> int:
total_deposit = 0
if not self.intern:
for item in self.rentalitems.all():
total_deposit += item.deposit
return total_deposit

View File

@@ -10,7 +10,7 @@ urlpatterns = [
path("overview/", RentalListView.as_view(), name="index"), path("overview/", RentalListView.as_view(), name="index"),
path("request-rental/", RentalCreateView.as_view(), name="rental_create"), path("request-rental/", RentalCreateView.as_view(), name="rental_create"),
path( path(
"request-rental/<int:pk>/done/", "request-rental/done/",
RentalCreateDoneView.as_view(), RentalCreateDoneView.as_view(),
name="rental_create_done", name="rental_create_done",
), ),

View File

@@ -1,66 +1,61 @@
import io import io
from pathlib import Path
from django.contrib.staticfiles import finders
from django.core.files import File from django.core.files import File
from pypdf import PdfReader, PdfWriter from pypdf import PdfReader, PdfWriter
from .models import Rental from .models import Rental
def generate_rental_pdf(rental: Rental) -> bool: def generate_rental_pdf(rental: Rental) -> bool:
if not rental or rental.status != Rental.Status.APPROVED: if not rental or rental.status != Rental.Status.APPROVED:
return False return False
# Get data for pdf # Get data for pdf
data = {} data = {}
data.update( data.update(
{ {
"Vorname": rental.firstname, "Vorname": rental.firstname,
"Nachname": rental.surname, "Nachname": rental.surname,
"Orga": rental.organization, "Orga": rental.organization,
"Matrikelnummer": rental.matriculation_number, "Matrikelnummer": rental.matriculation_number,
"E-Mail": rental.email, "E-Mail": rental.email,
"Telefonnummer": rental.phone, "Telefonnummer": rental.phone,
# Change to the correct date format # Change to the correct date format
"Abholdatum": str(rental.date_start.strftime("%d.%m.%Y")), "Abholdatum": str(rental.date_start.strftime("%d.%m.%Y")),
"Rückgabedatum": str(rental.date_end.strftime("%d.%m.%Y")), "Rückgabedatum": str(rental.date_end.strftime("%d.%m.%Y")),
}, },
) )
total_deposit = 0 for i, item in enumerate(rental.rentalitems.all(), start=1):
data.update(
for i, item in enumerate(rental.rentalitems.all(), start=1): {
total_deposit += item.deposit f"Produkt Row{i}": item.name,
data.update( f"Menge Row{i}": "1",
{ f"Kaution Row{i}": (str(item.deposit) if not rental.intern else "0"),
f"Produkt Row{i}": item.name, },
f"Menge Row{i}": "1", )
f"Kaution Row{i}": item.deposit,
}, total_deposit = rental.calc_total_deposit()
) data.update({"Gesamtkaution": str(total_deposit)})
data.update( # Write data in pdf
{ pdf_path_str = finders.find("rental/Verleihformular.pdf")
"Gesamtkaution": total_deposit, reader = PdfReader(pdf_path_str)
}, writer = PdfWriter()
) writer.append(reader)
# Write data in pdf writer.update_page_form_field_values(
pdf_path = Path(Path(__file__).parent) / "static/rental/Verleihformular.pdf" writer.pages[0],
reader = PdfReader(pdf_path) data,
writer = PdfWriter() )
writer.append(reader)
with io.BytesIO() as bytes_stream:
writer.update_page_form_field_values( writer.write(bytes_stream)
writer.pages[0], bytes_stream.seek(0)
data,
) # Save pdf in rental
rental_name = f"Verleihformular-{str(rental.pk).zfill(4)}.pdf"
with io.BytesIO() as bytes_stream: rental.file_field.save(rental_name, File(bytes_stream, rental_name))
writer.write(bytes_stream)
return True
# Save pdf in rental
rental_name = f"Verleihformular-{str(rental.pk).zfill(4)}.pdf"
rental.file_field.save(rental_name, File(bytes_stream, rental_name))
return True

View File

@@ -3,6 +3,7 @@ import datetime
from datetime import date from datetime import date
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
@@ -12,6 +13,9 @@ from django.views.generic.edit import CreateView
from .forms import RentalCreateForm from .forms import RentalCreateForm
from .models import Rental, RentalItem from .models import Rental, RentalItem
# Maximum number of rental items per rental entry because of table size limitations in PDF file
RENTAL_ITEMS_MAX = 5
def _calc_days_from_current_month(month: date) -> list: def _calc_days_from_current_month(month: date) -> list:
last_day_of_month = month.replace(day=calendar.monthrange(month.year, month.month)[1]) last_day_of_month = month.replace(day=calendar.monthrange(month.year, month.month)[1])
@@ -185,7 +189,7 @@ class RentalListView(ListView):
if rental["rentalitems__name"] not in rental_dict[day]: if rental["rentalitems__name"] not in rental_dict[day]:
rental_dict[day].append(rental["rentalitems__name"]) rental_dict[day].append(rental["rentalitems__name"])
context["rental_dict"] = rental_dict context["rental_dict"] = {k: sorted(v, key=str.casefold) for k, v in rental_dict.items()}
return context return context
@@ -223,6 +227,35 @@ class RentalCreateView(CreateView):
model = Rental model = Rental
template_name = "rental/create.html" template_name = "rental/create.html"
def form_valid(self, form):
# Get unsaved base Rental with all form data
base_rental = form.save(commit=False)
items = list(form.cleaned_data["rentalitems"])
selected_items = sorted(items, key=lambda x: x.name.lower())
for i in range(0, len(selected_items), RENTAL_ITEMS_MAX):
batch = selected_items[i:i + RENTAL_ITEMS_MAX]
# Clone base_rental — copying all its field values
new_rental = Rental.objects.create(
firstname=base_rental.firstname,
surname=base_rental.surname,
matriculation_number=base_rental.matriculation_number,
email=base_rental.email,
phone=base_rental.phone,
organization=base_rental.organization,
date_start=base_rental.date_start,
date_end=base_rental.date_end,
reason=base_rental.reason,
comment=base_rental.comment,
status=base_rental.status,
)
# Important: Add M2M relations after the object has been saved
new_rental.rentalitems.add(*batch)
return HttpResponseRedirect(self.get_success_url())
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@@ -231,26 +264,11 @@ class RentalCreateView(CreateView):
return context return context
def get_success_url(self): def get_success_url(self):
return reverse("rental:rental_create_done", kwargs={"pk": self.object.pk}) return reverse("rental:rental_create_done")
class RentalCreateDoneView(TemplateView): class RentalCreateDoneView(TemplateView):
template_name = "rental/create_done.html" template_name = "rental/create_done.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
obj = Rental.objects.get(pk=self.kwargs["pk"])
total_deposit = 0
for elem in obj.rentalitems.all():
total_deposit += elem.deposit
context["object"] = obj
context["total_deposit"] = total_deposit
return context
class RentalItemDetailView(DetailView): class RentalItemDetailView(DetailView):
model = RentalItem model = RentalItem

View File

@@ -1,17 +1,22 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Verleihanfrage erfolgreich eingereicht{% endblock %} {% block title %}Verleihanfrage erfolgreich eingereicht{% endblock %}
{% block content %} {% block content %}
<!-- Main Content --> <!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1 max-w-2xl"> <main class="container mx-auto w-full px-4 my-8 flex-1 max-w-2xl">
<section class="block w-full"> <section class="block w-full">
<p class="mt-6 text-gray-900 dark:text-gray-100 hyphens-auto" lang="de"> <p class="mt-6 text-gray-900 dark:text-gray-100 hyphens-auto" lang="de">
Deine Verleihanfrage mit der Nummer #{{ pk }} wurde erfolgreich eingereicht. Die Gegenstände können am {{ object.date_start }} während der Beratungszeit (Montag - Donnerstag: 09:00 - 14:00, Freitag: 09:00 - 12:00) abgeholt werden. Bitte bring den Gesamtpfand in Höhe von {{ total_deposit }} € in bar mit. Deine Verleihanfrage ist eingegangen - danke dir! 🎉
</p> Wir kümmern uns jetzt darum und melden uns per E-Mail mit den nächsten Schritten.
<div class="mt-10 flex items-center justify-center"> </p>
<a href="{% url 'home' %}" type="submit" class="block btn btn-primary">Zur Startseite</a> <p class="mt-6 text-gray-900 dark:text-gray-100 hyphens-auto" lang="de">
</div> Kleiner Hinweis: Bevor du die Sachen abholen kannst, wird deine Anfrage kurz geprüft und freigegeben.
</section> Sobald alles genehmigt ist, bekommst du von uns eine Mail.
</main> </p>
{% endblock content %} <div class="mt-10 flex items-center justify-center">
<a href="{% url 'home' %}" type="submit" class="block btn btn-primary">Zur Startseite</a>
</div>
</section>
</main>
{% endblock content %}