add detailview, mail sending and new db values

This commit is contained in:
2025-03-05 21:11:03 +01:00
parent dc263ee28c
commit 77d18cf58c
10 changed files with 182 additions and 21 deletions

View File

@@ -14,9 +14,14 @@ class RentalAdmin(admin.ModelAdmin):
"firstname", "firstname",
"surname", "surname",
"status", "status",
"total_disposit",
"date_start", "date_start",
"date_end", "date_end",
] ]
ordering = ["-id"]
readonly_fields = ["total_disposit"]
fieldsets = ( fieldsets = (
( (
"Persönliche Daten", "Persönliche Daten",
@@ -35,6 +40,7 @@ class RentalAdmin(admin.ModelAdmin):
("date_start", "date_end"), ("date_start", "date_end"),
"reason", "reason",
"rentalitems", "rentalitems",
"total_disposit",
), ),
}, },
), ),
@@ -63,12 +69,22 @@ class RentalAdmin(admin.ModelAdmin):
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)")
def total_disposit(self, obj):
total_disposit = 0
for elem in obj.rentalitems.all():
total_disposit += elem.deposit
return f"{total_disposit}"
@admin.register(RentalItem) @admin.register(RentalItem)
class RentalItemAdmin(admin.ModelAdmin): class RentalItemAdmin(admin.ModelAdmin):
form = RentalItemAdminForm form = RentalItemAdminForm
model = RentalItem model = RentalItem
ordering = ["name"]
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."

View File

@@ -12,14 +12,7 @@ class RentalCreateForm(forms.ModelForm):
# Conformation # Conformation
conformation = forms.BooleanField( conformation = forms.BooleanField(
required=True, required=True,
label=( label=("Ich habe die Verleihregeln gelesen und akzeptiere sie."),
"1. Fachschaft Elektrotechnik (FET) hat stets Vorrecht. 2. Bereits bestätigte Objekte "
"können storniert werden, falls FET sie selber braucht. 3. Sachen dürfen nur über das "
"Verleihsystem verborgt werden. 4. Objekte müssen pünktlich und gereinigt retouniert "
"werden. 5. Verleihpersonen entscheiden über Kaution (lediglich für Pünktlichkeit und "
"Reinheit) 6. Geht was kaputt, muss dies umgehend mitgeteilt werden und finanziell "
"dafür aufgekommen werden. "
),
initial=False, initial=False,
) )

39
fet2020/rental/mails.py Normal file
View File

@@ -0,0 +1,39 @@
import logging
from django.core.mail import send_mail
RENTAL_EMAIL = "patrick@fet.at"
logger = logging.getLogger(__name__)
def send_mail_approved(obj):
subject = f"Verleih #{obj.id}: {obj.get_status_display()}"
total_deposit = 0
for rentalitem in obj.rentalitems.all():
total_deposit += rentalitem.deposit
message = (
f"Deine Verleihanfrage mit der Nummer #{obj.id} wurde erfolgreich genehmigt. Die "
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.\nLiebe Grüße,\ndas Verleih-Team"
)
try:
send_mail(subject, message, RENTAL_EMAIL, [f"{obj.email}", RENTAL_EMAIL])
except Exception as exc:
logger.error("Failed to send approval email for rental #%s. Error: %s", obj.id, exc)
def send_mail_rejected(obj):
subject = f"Verleih #{obj.id}: {obj.get_status_display()}"
message = (
f"Deine Verleihanfrage mit der Nummer #{obj.id} wurde abgelehnt.\nLiebe Grüße,\ndas "
"Verleih-Team"
)
try:
send_mail(subject, message, RENTAL_EMAIL, [f"{obj.email}", RENTAL_EMAIL])
except Exception as exc:
logger.error("Failed to send rejection email for rental #%s. Error: %s", obj.id, exc)

View File

@@ -1,6 +1,7 @@
from django.db import models 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 .validators import PhoneNumberValidator from .validators import PhoneNumberValidator
@@ -10,6 +11,16 @@ class RentalItem(models.Model):
verbose_name="Beschreibung", max_length=128, blank=True, default="" verbose_name="Beschreibung", max_length=128, blank=True, default=""
) )
deposit = models.DecimalField(
verbose_name="Kaution (EUR)", max_digits=6, decimal_places=2, default=0
)
image = models.ImageField(verbose_name="Bild", upload_to="rentalitems", blank=True, null=True)
induction = models.BooleanField(verbose_name="Einschulung notwendig", default=False)
location = models.CharField(verbose_name="Standort", max_length=128, blank=True, default="")
class Meta: class Meta:
verbose_name = "Verleihgegenstand" verbose_name = "Verleihgegenstand"
verbose_name_plural = "Verleihgegenstände" verbose_name_plural = "Verleihgegenstände"
@@ -63,6 +74,24 @@ class Rental(models.Model):
def __str__(self): def __str__(self):
return f"Verleih #{self.id}: {self.firstname} {self.surname}" return f"Verleih #{self.id}: {self.firstname} {self.surname}"
def save(self, *args, **kwargs):
pre_obj = Rental.objects.filter(pk=self.pk).first()
super().save(*args, **kwargs)
if (
pre_obj
and pre_obj.status != self.Status.APPROVED
and self.status == self.Status.APPROVED
):
send_mail_approved(self)
elif (
pre_obj
and pre_obj.status != self.Status.REJECTED
and self.status == self.Status.REJECTED
):
send_mail_rejected(self)
def clean(self): def clean(self):
if not self.date_end: if not self.date_end:
self.date_end = self.date_start self.date_end = self.date_start

Binary file not shown.

View File

@@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from . import apps from . import apps
from .views import RentalCreateDoneView, RentalCreateView, RentalListView from .views import RentalCreateDoneView, RentalCreateView, RentalItemDetailView, RentalListView
app_name = apps.RentalConfig.name app_name = apps.RentalConfig.name
@@ -13,4 +13,5 @@ urlpatterns = [
RentalCreateDoneView.as_view(), RentalCreateDoneView.as_view(),
name="rental_create_done", name="rental_create_done",
), ),
path("rentalitems/<int:pk>/", RentalItemDetailView.as_view(), name="rentalitem_detail"),
] ]

View File

@@ -4,6 +4,7 @@ import datetime
from django.db.models import Q from django.db.models import Q
from django.urls import reverse from django.urls import reverse
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from .forms import RentalCreateForm from .forms import RentalCreateForm
@@ -150,3 +151,22 @@ class RentalCreateView(CreateView):
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):
model = RentalItem
template_name = "rental/rentalitem_detail.html"

View File

@@ -1,5 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %}
{% block title %}Verleih Anfrage{% endblock %} {% block title %}Verleih Anfrage{% endblock %}
{% block content %} {% block content %}
@@ -13,7 +15,7 @@
<section> <section>
<h2>Persönliche Daten</h2> <h2>Persönliche Daten</h2>
<small>Bitte geben Sie Ihre persönlichen Daten ein.</small> <small>Bitte gib deine persönlichen Daten ein.</small>
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6"> <div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
<div class="sm:col-span-3"> <div class="sm:col-span-3">
@@ -39,7 +41,7 @@
<section> <section>
<h2>Verleihgegenstände</h2> <h2>Verleihgegenstände</h2>
<small>Wählen Sie die gewünschten Verleihgegenstände aus.</small> <small>Wähl deine gewünschten Verleihgegenstände aus.</small>
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6 pb-6 col-span-full"> <div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6 pb-6 col-span-full">
{% if form.rentalitems.errors %} {% if form.rentalitems.errors %}
@@ -59,13 +61,16 @@
name="{{ form.rentalitems.html_name }}" name="{{ form.rentalitems.html_name }}"
value="{{ elem.data.value }}" value="{{ elem.data.value }}"
class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50" class="rounded border-gray-300 dark:border-none text-proprietary shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 dark:focus:ring-sky-700 focus:ring-opacity-50"
> >
<span>{{ elem.choice_label }}</span> <a
href="{% url 'rental:rentalitem_detail' elem.data.value %}"
class="text-gray-700 dark:text-gray-200"
>{{ elem.choice_label }}</a>
</label> </label>
{% for item in rentalitems_addinfo %} {% for item in rentalitems_addinfo %}
{% if item.name == elem.choice_label and item.description %} {% if item.name == elem.choice_label and item.induction %}
<p class="text-xs text-gray-700 dark:text-gray-200">{{ item.description }}</p> <p class="text-xs text-gray-700 dark:text-gray-200">Einschulung erforderlich!</p>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
@@ -87,7 +92,7 @@
<section> <section>
<h2>Zusätzliche Informationen</h2> <h2>Zusätzliche Informationen</h2>
<small>Hier können Sie zusätzliche Informationen, Anliegen und Sonstiges angeben.</small> <small>Hier kannst du zusätzliche Informationen, Anliegen und Sonstiges angeben.</small>
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6"> <div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
<div class="col-span-full"> <div class="col-span-full">
@@ -98,9 +103,13 @@
<section> <section>
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6"> <div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
<div class="col-span-full"> <div class="col-span-full text-gray-700 dark:text-gray-200">
{% include "baseform/checkbox.html" with field=form.conformation %} {% include "baseform/checkbox.html" with field=form.conformation %}
</div> </div>
<a href="{% static 'rental/verleihregeln.pdf' %}" target='_blank' class="inline-flex items-center px-2 py-1">
<i class="fa-solid fa-file-pdf fa-fw text-red-800 dark:text-red-500 md:text-inherit group-hover:text-red-800 dark:group-hover:text-red-500"></i>
<span class="ml-2 sm:ml-1 text-gray-700 dark:text-gray-200">Verleihregeln</span>
</a>
</div> </div>
</section> </section>

View File

@@ -4,10 +4,10 @@
{% block content %} {% block content %}
<!-- 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 max-w-2xl">
<section class="w-full text-center"> <section class="block w-full">
<p class="mt-6 text-gray-900 dark:text-gray-100"> <p class="mt-6 text-gray-900 dark:text-gray-100 hyphens-auto" lang="de">
Die Verleihanfrage mit der Nummer #{{ pk }} wurde erfolgreich eingereicht. 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.
</p> </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>

View File

@@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% block title %}Verleihgegenstand {{ object.name }}{% endblock %}
{% block content %}
<!-- Main Content -->
<main class="container mx-auto w-full px-4 my-8 flex-1 max-w-2xl">
<h1 class="page-title">Verleihgegenstand {{ object.name }}</h1>
<section>
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
{% if object.description %}
<div class="col-span-full">
<label>
<h2 class="text-xl font-black text-gray-700 dark:text-gray-200">Beschreibung</h2>
<p class="text-gray-700 dark:text-gray-200">{{ object.description|safe }}</p>
</label>
</div>
{% endif %}
{% if object.location %}
<div class="col-span-full">
<label>
<h2 class="text-xl font-black text-gray-700 dark:text-gray-200">Standort</h2>
<p class="text-gray-700 dark:text-gray-200">{{ object.location }}</p>
</label>
</div>
{% endif %}
{% if object.image %}
<div class="col-span-full">
<label>
<h2 class="text-xl font-black text-gray-700 dark:text-gray-200">Bild</h2>
<img src="{{ object.image.url }}" alt="{{ object.name }}" class="w-full h-auto">
</label>
</div>
{% endif %}
{% if object.deposit %}
<div class="col-span-full">
<label>
<h2 class="text-xl font-black text-gray-700 dark:text-gray-200">Kaution</h2>
<p class="text-gray-700 dark:text-gray-200">{{ object.deposit }} €</p>
</label>
</div>
{% endif %}
{% if object.induction %}
<div class="col-span-full">
<label>
<h2 class="text-xl font-black text-gray-700 dark:text-gray-200">Einschulung erforderlich.</h2>
</label>
</div>
{% endif %}
</div>
</section>
</main>
{% endblock content %}