Compare commits
10 Commits
dc263ee28c
...
aef9a0871b
| Author | SHA1 | Date | |
|---|---|---|---|
| aef9a0871b | |||
| 14617e5492 | |||
| 4ee9b3eaa5 | |||
| faad8635e5 | |||
| ed9d115361 | |||
| b24a17b3b6 | |||
| c0fe059e78 | |||
| 4c669c0fb6 | |||
| 3db68f4bf4 | |||
| 77d18cf58c |
@@ -14,9 +14,14 @@ class RentalAdmin(admin.ModelAdmin):
|
||||
"firstname",
|
||||
"surname",
|
||||
"status",
|
||||
"total_disposit",
|
||||
"date_start",
|
||||
"date_end",
|
||||
]
|
||||
ordering = ["-id"]
|
||||
|
||||
readonly_fields = ["total_disposit"]
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
"Persönliche Daten",
|
||||
@@ -35,6 +40,7 @@ class RentalAdmin(admin.ModelAdmin):
|
||||
("date_start", "date_end"),
|
||||
"reason",
|
||||
"rentalitems",
|
||||
"total_disposit",
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -63,12 +69,22 @@ class RentalAdmin(admin.ModelAdmin):
|
||||
obj.author = request.user
|
||||
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)
|
||||
class RentalItemAdmin(admin.ModelAdmin):
|
||||
form = RentalItemAdminForm
|
||||
model = RentalItem
|
||||
|
||||
ordering = ["name"]
|
||||
|
||||
def add_view(self, request, form_url="", extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
|
||||
|
||||
@@ -12,14 +12,7 @@ class RentalCreateForm(forms.ModelForm):
|
||||
# Conformation
|
||||
conformation = forms.BooleanField(
|
||||
required=True,
|
||||
label=(
|
||||
"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. "
|
||||
),
|
||||
label=("Ich habe die Verleihregeln gelesen und akzeptiere sie."),
|
||||
initial=False,
|
||||
)
|
||||
|
||||
|
||||
39
fet2020/rental/mails.py
Normal file
39
fet2020/rental/mails.py
Normal 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)
|
||||
@@ -1,6 +1,7 @@
|
||||
from django.db import models
|
||||
from django.forms import ValidationError
|
||||
|
||||
from .mails import send_mail_approved, send_mail_rejected
|
||||
from .validators import PhoneNumberValidator
|
||||
|
||||
|
||||
@@ -10,6 +11,16 @@ class RentalItem(models.Model):
|
||||
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:
|
||||
verbose_name = "Verleihgegenstand"
|
||||
verbose_name_plural = "Verleihgegenstände"
|
||||
@@ -63,6 +74,24 @@ class Rental(models.Model):
|
||||
def __str__(self):
|
||||
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):
|
||||
if not self.date_end:
|
||||
self.date_end = self.date_start
|
||||
|
||||
BIN
fet2020/rental/static/rental/Verleihregeln.pdf
Normal file
BIN
fet2020/rental/static/rental/Verleihregeln.pdf
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import apps
|
||||
from .views import RentalCreateDoneView, RentalCreateView, RentalListView
|
||||
from .views import RentalCreateDoneView, RentalCreateView, RentalItemDetailView, RentalListView
|
||||
|
||||
app_name = apps.RentalConfig.name
|
||||
|
||||
@@ -13,4 +13,5 @@ urlpatterns = [
|
||||
RentalCreateDoneView.as_view(),
|
||||
name="rental_create_done",
|
||||
),
|
||||
path("rentalitems/<int:pk>/", RentalItemDetailView.as_view(), name="rentalitem_detail"),
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@ import datetime
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse
|
||||
from django.views.generic import ListView, TemplateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic.edit import CreateView
|
||||
|
||||
from .forms import RentalCreateForm
|
||||
@@ -150,3 +151,22 @@ class RentalCreateView(CreateView):
|
||||
|
||||
class RentalCreateDoneView(TemplateView):
|
||||
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"
|
||||
|
||||
Binary file not shown.
@@ -99,6 +99,7 @@
|
||||
</a>
|
||||
<button class="navbar-toggle"
|
||||
@click="toggleNav"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
@@ -207,22 +208,22 @@
|
||||
<footer>
|
||||
<ul class="icon-list">
|
||||
<li>
|
||||
<a href="{% url 'facebook' %}"><i class="fa-brands fa-facebook"></i></a>
|
||||
<a href="{% url 'facebook' %}" aria-label="Facebook"><i class="fa-brands fa-facebook"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'whatsapp' %}"><i class="fa-brands fa-whatsapp"></i></a>
|
||||
<a href="{% url 'whatsapp' %}" aria-label="WhatsApp"><i class="fa-brands fa-whatsapp"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'instagram' %}"><i class="fa-brands fa-instagram"></i></a>
|
||||
<a href="{% url 'instagram' %}" aria-label="Instagram"><i class="fa-brands fa-instagram"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'discord' %}"><i class="fa-brands fa-discord"></i></a>
|
||||
<a href="{% url 'discord' %}" aria-label="Discord"><i class="fa-brands fa-discord"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'telegram' %}"><i class="fa-brands fa-telegram"></i></a>
|
||||
<a href="{% url 'telegram' %}" aria-label="Telegram"><i class="fa-brands fa-telegram"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:service@fet.at"><i class="fa-solid fa-envelope"></i></a>
|
||||
<a href="mailto:service@fet.at" aria-label="Email"><i class="fa-solid fa-envelope"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
{% get_flatpages '/impressum/' as pages %}
|
||||
@@ -238,7 +239,7 @@
|
||||
{% endif %}
|
||||
</footer>
|
||||
<div class="super-duper-awesome-signature font-normal" x-data="footerCounter">
|
||||
<span x-bind="footerFirst">Handcrafted </span><span x-bind="footerSecond">with </span><i class="fa-solid fa-heart" aria-label="love" @click="increase" x-bind="footerThird"></i><span x-bind="footerFourth"> by</span><span x-bind="footerFifth"> fet</span>
|
||||
<span x-bind="footerFirst">Handcrafted </span><span x-bind="footerSecond">with </span><i class="fa-solid fa-heart" role="img" aria-label="love" @click="increase" x-bind="footerThird"></i><span x-bind="footerFourth"> by</span><span x-bind="footerFifth"> fet</span>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'js/flowbite@2.2.1.js' %}"></script>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Verleih Anfrage{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -13,7 +15,7 @@
|
||||
|
||||
<section>
|
||||
<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="sm:col-span-3">
|
||||
@@ -39,7 +41,7 @@
|
||||
|
||||
<section>
|
||||
<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">
|
||||
{% if form.rentalitems.errors %}
|
||||
@@ -60,12 +62,15 @@
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
|
||||
{% for item in rentalitems_addinfo %}
|
||||
{% if item.name == elem.choice_label and item.description %}
|
||||
<p class="text-xs text-gray-700 dark:text-gray-200">{{ item.description }}</p>
|
||||
{% if item.name == elem.choice_label and item.induction %}
|
||||
<p class="text-xs text-gray-700 dark:text-gray-200">Einschulung erforderlich!</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -87,7 +92,7 @@
|
||||
|
||||
<section>
|
||||
<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="col-span-full">
|
||||
@@ -98,9 +103,13 @@
|
||||
|
||||
<section>
|
||||
<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 %}
|
||||
</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>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
{% block content %}
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto w-full px-4 my-8 flex-1">
|
||||
<section class="w-full text-center">
|
||||
<p class="mt-6 text-gray-900 dark:text-gray-100">
|
||||
Die Verleihanfrage mit der Nummer #{{ pk }} wurde erfolgreich eingereicht.
|
||||
<main class="container mx-auto w-full px-4 my-8 flex-1 max-w-2xl">
|
||||
<section class="block w-full">
|
||||
<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.
|
||||
</p>
|
||||
<div class="mt-10 flex items-center justify-center">
|
||||
<a href="{% url 'home' %}" type="submit" class="block btn btn-primary">Zur Startseite</a>
|
||||
|
||||
54
fet2020/templates/rental/rentalitem_detail.html
Normal file
54
fet2020/templates/rental/rentalitem_detail.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user