add rental
This commit is contained in:
0
fet2020/rental/__init__.py
Normal file
0
fet2020/rental/__init__.py
Normal file
84
fet2020/rental/admin.py
Normal file
84
fet2020/rental/admin.py
Normal 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
12
fet2020/rental/apps.py
Normal 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
60
fet2020/rental/forms.py
Normal 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
71
fet2020/rental/models.py
Normal 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
3
fet2020/rental/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
16
fet2020/rental/urls.py
Normal file
16
fet2020/rental/urls.py
Normal 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",
|
||||
),
|
||||
]
|
||||
11
fet2020/rental/validators.py
Normal file
11
fet2020/rental/validators.py
Normal 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
152
fet2020/rental/views.py
Normal 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"
|
||||
Reference in New Issue
Block a user