Files
fet2020/fet2020/rental/views.py
2025-10-29 22:36:11 +01:00

276 lines
9.4 KiB
Python

import calendar
import datetime
from datetime import date
from django.db.models import Q
from django.shortcuts import render
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
from .models import Rental, RentalItem
def _calc_days_from_current_month(month: date) -> list:
last_day_of_month = month.replace(day=calendar.monthrange(month.year, month.month)[1])
return [month + datetime.timedelta(days=i) for i in range((last_day_of_month - month).days + 1)]
def _calc_days_from_prev_month(month: date) -> list:
days_of_prev_period = []
if month.weekday() != calendar.MONDAY:
for i in range(1, 7):
day = month + datetime.timedelta(days=-i)
days_of_prev_period.append(day)
if day.weekday() == calendar.MONDAY:
break
return sorted(days_of_prev_period)
def _calc_days_from_next_month(month: date) -> list:
last_day_of_month = month.replace(day=calendar.monthrange(month.year, month.month)[1])
days_of_next_period = []
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_period.append(day)
if day.weekday() == calendar.SUNDAY:
break
return days_of_next_period
def _get_display_period(view_type: str, period: str) -> date:
display_date: date = None
# Handle week view
if view_type == "week":
try:
# Parse the requested calendar week
display_date = (
datetime.datetime.strptime(f"{period}-1", "%G-KW%V-%u")
.replace(tzinfo=datetime.UTC)
.date()
)
except Exception:
# Get first day of the current week
today = datetime.datetime.now(tz=datetime.UTC).date()
display_date = today - datetime.timedelta(days=today.weekday())
# Handle month view
else:
try:
# Parse the requested month
display_date = (
datetime.datetime.strptime(period, "%Y-%m").replace(tzinfo=datetime.UTC).date()
)
except Exception:
# Get the first day of the current month
display_date = datetime.datetime.now(tz=datetime.UTC).date().replace(day=1)
return display_date
class RentalListView(ListView):
model = Rental
template_name = "rental/calendar.html"
def __init__(self):
super().__init__()
# Current display period and view settings
self.display_period = None
self.view_type = "month" # Default view
self.rentalitem_filters = []
def get(self, request, *args, **kwargs):
# Get view parameters from request
_view_type = request.GET.get("view_type", "week")
_period = request.GET.get("period_value", "")
_prev_period = request.GET.get("prev_period", "")
_next_period = request.GET.get("next_period", "")
if _prev_period:
_period = _prev_period
elif _next_period:
_period = _next_period
self.view_type = _view_type
self.display_period = _get_display_period(_view_type, _period)
self.rentalitem_filters = request.GET.getlist("rentalitems", [])
if not self.rentalitem_filters:
items = RentalItem.objects.all()
self.rentalitem_filters = [item.name for item in items]
# Update request.GET
_request_get_list = request.GET.copy()
_request_get_list.pop("prev_period", None)
_request_get_list.pop("next_period", None)
_request_get_list["period_value"] = _period
request.GET = _request_get_list
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.view_type != "week":
# Add the displayed, previous and next month
context["view_period"] = self.display_period
context["prev_period"] = self.display_period + datetime.timedelta(days=-1)
context["next_period"] = self.display_period + datetime.timedelta(
days=calendar.monthrange(self.display_period.year, self.display_period.month)[1] + 1
)
# Add the days of the displayed, previous and next month
days_of_view_period = _calc_days_from_current_month(self.display_period)
context["days_of_view_period"] = days_of_view_period
context["days_of_prev_period"] = _calc_days_from_prev_month(self.display_period)
context["days_of_next_period"] = _calc_days_from_next_month(self.display_period)
context["view_type"] = "month"
context["week_num"] = None
else:
# Current week
year, week_num, _ = self.display_period.isocalendar()
context["view_period"] = f"{year}-KW{week_num:02d}" # formats as "2025-KW02"
# Calculate previous week
prev_week = self.display_period - datetime.timedelta(days=7)
prev_year, prev_week_num, _ = prev_week.isocalendar()
context["prev_period"] = f"{prev_year}-KW{prev_week_num:02d}"
# Calculate next week
next_week = self.display_period + datetime.timedelta(days=7)
next_year, next_week_num, _ = next_week.isocalendar()
context["next_period"] = f"{next_year}-KW{next_week_num:02d}"
# Add days of week (7 days starting from first_day_of_week)
days_of_view_period = [
self.display_period + datetime.timedelta(days=i) for i in range(7)
]
context["days_of_view_period"] = days_of_view_period
context["days_of_prev_period"] = []
context["days_of_next_period"] = []
context["view_type"] = "week"
context["week_num"] = week_num
# 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 selected rental items to the context for the filter
context["rentalitem_filters"] = {"rentalitems": self.rentalitem_filters}
# Create a dictionary with the rental items for each day
rental_dict = {}
for rental in self.get_queryset():
for day in days_of_view_period:
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"])
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)
)
)
if self.view_type == "week":
qs_date_end = self.display_period + datetime.timedelta(days=6)
else:
qs_date_end = self.display_period.replace(
day=calendar.monthrange(self.display_period.year, self.display_period.month)[1]
)
# Filter by date
if self.display_period and qs_date_end:
qs_new = qs.filter(date_start__gte=self.display_period, date_start__lte=qs_date_end)
qs_new |= qs.filter(date_end__gte=self.display_period, date_end__lte=qs_date_end)
# Filter by rental items
qs = qs.filter(rentalitems__name__in=self.rentalitem_filters).distinct()
return qs.values("id", "date_start", "date_end", "rentalitems__name").distinct()
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"
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"
def rental_calendar(request):
"""
ICS-calendar for Outlook, Google Calendar, etc.
"""
rentals = Rental.objects.all()
context = {
"rentals": rentals,
}
response = render(request, "rental/rental_calendar.ics", context, content_type="text/calendar")
# End of line (EOL) must be CRLF, to be compliant with RFC5545. Django/Python set the EOL to LF.
response.content = response.content.replace(b"\n", b"\r\n")
return response