276 lines
9.4 KiB
Python
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
|