add rental

This commit is contained in:
2025-02-22 15:24:13 +01:00
parent 1f3fc92f6b
commit 3adc98144f
16 changed files with 748 additions and 0 deletions

View File

84
fet2020/rental/admin.py Normal file
View File

@@ -0,0 +1,84 @@
from django.contrib import admin
from .forms import RentalAdminForm, RentalItemAdminForm
from .models import Rental, RentalItem
@admin.register(Rental)
class RentalAdmin(admin.ModelAdmin):
form = RentalAdminForm
model = Rental
list_display = [
"id",
"firstname",
"surname",
"status",
"date_start",
"date_end",
]
fieldsets = (
(
"Persönliche Daten",
{
"fields": (
("firstname", "surname"),
("organization", "matriculation_number"),
("email", "phone"),
),
},
),
(
"Verleih",
{
"fields": (
("date_start", "date_end"),
"reason",
"rentalitems",
),
},
),
(
"Sonstiges",
{
"fields": (
"comment",
"status",
),
},
),
)
def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().add_view(request, form_url, extra_context=extra_context)
def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().change_view(request, object_id, form_url, extra_context=extra_context)
def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)
@admin.register(RentalItem)
class RentalItemAdmin(admin.ModelAdmin):
form = RentalItemAdminForm
model = RentalItem
def add_view(self, request, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().add_view(request, form_url, extra_context=extra_context)
def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["help_text"] = "Fette Schriften sind Pflichtfelder."
return super().change_view(request, object_id, form_url, extra_context=extra_context)
def save_model(self, request, obj, form, change):
obj.author = request.user
super().save_model(request, obj, form, change)

12
fet2020/rental/apps.py Normal file
View File

@@ -0,0 +1,12 @@
from django.apps import AppConfig
from django.db.models.signals import post_migrate
from fet2020.utils import create_perms
class RentalConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "rental"
def ready(self):
post_migrate.connect(create_perms, sender=self)

60
fet2020/rental/forms.py Normal file
View File

@@ -0,0 +1,60 @@
from django import forms
from django.forms import DateInput
from .models import Rental, RentalItem
class DateInput(DateInput):
input_type = "date"
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. "
),
initial=False,
)
class Meta:
model = Rental
fields = [
"firstname",
"surname",
"matriculation_number",
"email",
"phone",
"organization",
"date_start",
"date_end",
"reason",
"comment",
"rentalitems",
]
widgets = {
"date_start": DateInput(format=("%Y-%m-%d")),
"date_end": DateInput(format=("%Y-%m-%d")),
}
class RentalAdminForm(forms.ModelForm):
class Meta:
model = Rental
fields = "__all__"
widgets = {"rentalitems": forms.CheckboxSelectMultiple()}
class RentalItemAdminForm(forms.ModelForm):
class Meta:
model = RentalItem
fields = "__all__"

71
fet2020/rental/models.py Normal file
View File

@@ -0,0 +1,71 @@
from django.db import models
from django.forms import ValidationError
from .validators import PhoneNumberValidator
class RentalItem(models.Model):
name = models.CharField(verbose_name="Name", max_length=128)
description = models.CharField(
verbose_name="Beschreibung", max_length=128, blank=True, default=""
)
class Meta:
verbose_name = "Verleihgegenstand"
verbose_name_plural = "Verleihgegenstände"
def __str__(self):
return self.name
class Rental(models.Model):
firstname = models.CharField(max_length=128, verbose_name="Vorname")
surname = models.CharField(max_length=128, verbose_name="Nachname")
matriculation_number = models.CharField(verbose_name="Matrikelnummer", max_length=8)
email = models.EmailField(verbose_name="E-Mail")
phone = models.CharField(
verbose_name="Telefonnummer", max_length=32, validators=[PhoneNumberValidator()]
)
organization = models.CharField(verbose_name="Organisation", max_length=128)
date_start = models.DateField(verbose_name="Abholdatum")
date_end = models.DateField(verbose_name="Rückgabedatum")
reason = models.TextField(verbose_name="Grund der Ausleihe", max_length=500)
comment = models.TextField(verbose_name="Kommentar", max_length=500, blank=True, default="")
class Status(models.TextChoices):
SUBMITTED = "S", "Eingereicht"
APPROVED = "A", "Verleih genehmigt"
REJECTED = "J", "Verleih abgelehnt"
ISSUED = "I", "Verleihgegenstände ausgegeben"
RETURNED = "R", "Verleihgegenstände zurückgegeben"
status = models.CharField(
verbose_name="Status",
max_length=1,
choices=Status.choices,
default=Status.SUBMITTED,
)
rentalitems = models.ManyToManyField(
RentalItem,
verbose_name="Verleihgegenstände",
)
class Meta:
verbose_name = "Verleih"
verbose_name_plural = "Verleih"
def __str__(self):
return f"Verleih #{self.id}: {self.firstname} {self.surname}"
def clean(self):
if not self.date_end:
self.date_end = self.date_start
if self.date_start > self.date_end:
raise ValidationError("Das Abholdatum muss vor dem Rückgabedatum liegen.")

3
fet2020/rental/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

16
fet2020/rental/urls.py Normal file
View File

@@ -0,0 +1,16 @@
from django.urls import path
from . import apps
from .views import RentalCreateDoneView, RentalCreateView, RentalListView
app_name = apps.RentalConfig.name
urlpatterns = [
path("rental/", RentalListView.as_view(), name="index"),
path("request-rental/", RentalCreateView.as_view(), name="rental_create"),
path(
"request-rental/<int:pk>/done/",
RentalCreateDoneView.as_view(),
name="rental_create_done",
),
]

View File

@@ -0,0 +1,11 @@
from django.core.validators import RegexValidator
from django.utils.deconstruct import deconstructible
@deconstructible
class PhoneNumberValidator(RegexValidator):
regex = r"^\+?1?\d{9,15}$"
message = (
"Telefonnummer muss in diesem Format +999999999999 eingegeben werden. Bis zu 15 Zahlen "
"sind erlaubt."
)

152
fet2020/rental/views.py Normal file
View File

@@ -0,0 +1,152 @@
import calendar
import datetime
from django.db.models import Q
from django.urls import reverse
from django.views.generic import ListView, TemplateView
from django.views.generic.edit import CreateView
from .forms import RentalCreateForm
from .models import Rental, RentalItem
class RentalListView(ListView):
model = Rental
template_name = "rental/calendar.html"
# Month is the month displayed in the calendar (and should be the first day of the month)
month = None
# Rental items to filter
rentalitem_filters = []
def get(self, request, *args, **kwargs):
# Get the rental items from the filter
self.rentalitem_filters = request.GET.getlist("rentalitems", [])
if not self.rentalitem_filters:
for rentalitem in RentalItem.objects.all():
self.rentalitem_filters.append(rentalitem.name)
# Get the displayed month from the request
_date_str = request.GET.get("month", "")
if _date_str:
self.month = (
datetime.datetime.strptime(_date_str, "%Y-%m").replace(tzinfo=datetime.UTC).date()
)
else:
self.month = datetime.datetime.now(tz=datetime.UTC).date().replace(day=1)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Calculate the day of the previous month from Monday
days_of_prev_month = []
if self.month.weekday() != calendar.MONDAY:
for i in range(1, 7):
day = self.month + datetime.timedelta(days=-i)
days_of_prev_month.append(day)
if day.weekday() == calendar.MONDAY:
break
# Calculate the days of the next month until Sunday
last_day_of_month = self.month.replace(
day=calendar.monthrange(self.month.year, self.month.month)[1]
)
days_of_next_month = []
if last_day_of_month.weekday() != calendar.SUNDAY:
for i in range(1, 7):
day = last_day_of_month + datetime.timedelta(days=i)
days_of_next_month.append(day)
if day.weekday() == calendar.SUNDAY:
break
# Calculate the days of the displayed month
days_of_month = [
self.month + datetime.timedelta(days=i)
for i in range((last_day_of_month - self.month).days + 1)
]
# Create a dictionary with the rental items for each day
rental_dict = {}
for rental in self.get_queryset():
for day in days_of_month:
if rental["date_start"] <= day and rental["date_end"] >= day:
if day not in rental_dict:
rental_dict[day] = []
if rental["rentalitems__name"] not in rental_dict[day]:
rental_dict[day].append(rental["rentalitems__name"])
# Add the displayed, previous and next month
context["month"] = self.month
context["prev_month"] = self.month + datetime.timedelta(days=-1)
context["next_month"] = self.month + datetime.timedelta(
days=calendar.monthrange(self.month.year, self.month.month)[1] + 1
)
# Add the days of the displayed, previous and next month
context["days_of_month"] = days_of_month
context["days_of_prev_month"] = sorted(days_of_prev_month)
context["days_of_next_month"] = days_of_next_month
# Get the current date for the calendar
context["today"] = datetime.datetime.now(tz=datetime.UTC).date()
# Add rental items to the context for the filter
context["rentalitems"] = RentalItem.objects.all()
# Add the (max. 4) selected rental items to the context for the filter
context["rentalitem_filters"] = {"rentalitems": self.rentalitem_filters[:4]}
context["rental_dict"] = rental_dict
return context
def get_queryset(self):
qs = (
super()
.get_queryset()
.filter(
Q(status=Rental.Status.APPROVED)
| Q(status=Rental.Status.ISSUED)
| Q(status=Rental.Status.RETURNED)
)
)
last_day_of_month = self.month.replace(
day=calendar.monthrange(self.month.year, self.month.month)[1]
)
# Filter by date
qs_new = qs.filter(date_start__gte=self.month, date_start__lte=last_day_of_month)
qs_new |= qs.filter(date_end__gte=self.month, date_end__lte=last_day_of_month)
# Filter by rental items
qs = qs.filter(rentalitems__name__in=self.rentalitem_filters).distinct()
qs = qs.values("id", "date_start", "date_end", "rentalitems__name").distinct()
return qs
class RentalCreateView(CreateView):
form_class = RentalCreateForm
model = Rental
template_name = "rental/create.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["rentalitems_addinfo"] = RentalItem.objects.all()
return context
def get_success_url(self):
return reverse("rental:rental_create_done", kwargs={"pk": self.object.pk})
class RentalCreateDoneView(TemplateView):
template_name = "rental/create_done.html"