Compare commits
8 Commits
1f3fc92f6b
...
4e278fccab
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e278fccab | |||
| e17d12a079 | |||
| 1cfe36197f | |||
| 2e55fae418 | |||
| c15d60ac6a | |||
| e98da4426a | |||
| 97f2425434 | |||
| 3adc98144f |
@@ -46,8 +46,6 @@ INSTALLED_APPS = [
|
||||
"ckeditor_uploader",
|
||||
"easy_thumbnails",
|
||||
"rest_framework",
|
||||
"softhyphen",
|
||||
"django_crontab",
|
||||
"django_filters",
|
||||
"django_static_jquery_ui",
|
||||
"fontawesomefree",
|
||||
@@ -61,6 +59,7 @@ INSTALLED_APPS = [
|
||||
"gallery.apps.GalleryConfig",
|
||||
"intern.apps.InternConfig",
|
||||
"finance.apps.FinanceConfig",
|
||||
"rental.apps.RentalConfig",
|
||||
]
|
||||
|
||||
|
||||
@@ -337,12 +336,6 @@ CKEDITOR_CONFIGS = {
|
||||
}
|
||||
|
||||
|
||||
# CRON JOBS
|
||||
CRONJOBS = [
|
||||
("0 16 * * *", "posts.cronjobs.check_to_send_agenda_mail"),
|
||||
]
|
||||
|
||||
|
||||
# ETHERPAD
|
||||
ETHERPAD_HOST = env("ETHERPAD_HOST").strip()
|
||||
if not ETHERPAD_HOST or ETHERPAD_HOST == "":
|
||||
|
||||
@@ -38,6 +38,7 @@ urlpatterns = [
|
||||
path("intern/", include("intern.urls")),
|
||||
path("jobs/", include("blackboard.urls")),
|
||||
path("posts/", include("posts.urls")),
|
||||
path("rental/", include("rental.urls")),
|
||||
path("search/", include("search.urls")),
|
||||
path(
|
||||
"discord/",
|
||||
|
||||
@@ -56,7 +56,7 @@ class BillPeriodeFilter(admin.SimpleListFilter):
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = model_admin.get_queryset(request).order_by("-date")
|
||||
if qs.exists() is not True:
|
||||
if not qs.exists():
|
||||
return None
|
||||
|
||||
count = 0
|
||||
@@ -203,9 +203,11 @@ class BillAdmin(admin.ModelAdmin):
|
||||
]
|
||||
|
||||
actions = ["make_cleared", "make_finished"]
|
||||
autocomplete_fields = ["resolution"]
|
||||
list_filter = ["status", "affiliation", "payer", BillPeriodeFilter]
|
||||
search_fields = ["purpose", "bankdata__name"]
|
||||
show_facets = admin.ShowFacets.ALWAYS
|
||||
ordering = ["-id"]
|
||||
search_fields = ["purpose"]
|
||||
|
||||
readonly_fields = [
|
||||
"get_bankdata_name",
|
||||
@@ -461,7 +463,7 @@ class FeeAdmin(admin.ModelAdmin):
|
||||
self.message_user(
|
||||
request,
|
||||
(
|
||||
"Das PDF File konnte nicht generiert werden, weil der Status nicht auf "
|
||||
"Das PDF-Dokument konnte nicht generiert werden, da der Status nicht auf "
|
||||
"'Eingereicht' gesetzt ist."
|
||||
),
|
||||
messages.WARNING,
|
||||
@@ -669,7 +671,7 @@ class WirefAdmin(admin.ModelAdmin):
|
||||
else:
|
||||
self.message_user(
|
||||
request,
|
||||
"Das PDF File wurde nicht generiert, weil der Status nicht offen ist.",
|
||||
"Das PDF-Dokument konnte nicht generiert werden, da der Status nicht auf 'Offen' gesetzt ist.",
|
||||
messages.WARNING,
|
||||
)
|
||||
return HttpResponseRedirect(".")
|
||||
|
||||
@@ -122,7 +122,6 @@ class BillCreateForm(forms.ModelForm):
|
||||
self.fields["bill_creator"].required = True
|
||||
|
||||
self.fields["invoice"].placeholder = "Firmenname\nStraße\nPLZ und Ort"
|
||||
self.fields["invoice"].cols = 30
|
||||
self.fields["invoice"].rows = 4
|
||||
|
||||
# Bank data fields
|
||||
@@ -212,13 +211,11 @@ class BillUpdateForm(forms.ModelForm):
|
||||
self.fields["status"].disabled = True
|
||||
|
||||
# Config for textarea of invoice. Calc rows for a better view.
|
||||
self.fields["invoice"].cols = 30
|
||||
if (rows := kwargs["instance"].invoice.count("\n") + 1) < 3:
|
||||
rows = 3
|
||||
self.fields["invoice"].rows = rows
|
||||
|
||||
# Config for textarea of comment. Calc rows for a better view.
|
||||
self.fields["comment"].cols = 30
|
||||
rows = kwargs["instance"].comment.count("\n") + 1
|
||||
self.fields["comment"].rows = rows
|
||||
|
||||
@@ -477,7 +474,6 @@ class ResolutionCreateForm(forms.ModelForm):
|
||||
|
||||
self.fields["option"].autofocus = True
|
||||
|
||||
self.fields["voting_text"].cols = 30
|
||||
self.fields["voting_text"].rows = 3
|
||||
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ class Bill(models.Model):
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="Kontodaten",
|
||||
verbose_name="Bankdaten",
|
||||
)
|
||||
|
||||
resolution = models.ForeignKey(
|
||||
|
||||
@@ -98,7 +98,7 @@ class BillListView(LoginRequiredMixin, ListView):
|
||||
|
||||
def get_queryset(self):
|
||||
qs1 = (
|
||||
Fee.objects.filter(bankdata__bankdata_creator__username=self.request.user)
|
||||
Fee.objects.filter(fee_creator__username=self.request.user)
|
||||
.values("amount", "status", "id")
|
||||
.annotate(
|
||||
date=F("date_start"), purpose=F("job"), model=Value("FEE", output_field=CharField())
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# HOW TO ADD CRONJOBS
|
||||
# write a cronjob function
|
||||
# add cronjob to fet2020/settings.py
|
||||
# add cronjob with cmd 'python3 fet2020/manage.py crontab add'
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from .mails import send_agenda_mail
|
||||
from .models import FetMeeting
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_to_send_agenda_mail():
|
||||
agenda_date = timezone.now().date() + timedelta(days=2)
|
||||
next_meeting = FetMeeting.objects.filter(event_start__date=agenda_date).first()
|
||||
|
||||
if next_meeting and next_meeting.has_agenda:
|
||||
logger.info("Agenda für die %s soll gesendet werden.", next_meeting.slug)
|
||||
send_agenda_mail(next_meeting)
|
||||
@@ -1,37 +0,0 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
from html2text import html2text
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_agenda_mail(meeting):
|
||||
date = meeting.event_start.date().strftime("%d.%m.%Y")
|
||||
time = meeting.event_start.time()
|
||||
agenda = html2text(meeting.agenda_html)
|
||||
|
||||
msg = (
|
||||
"Liebe Alle,\n\n"
|
||||
"wir haben am " + str(date) + " um " + str(time) + " wieder eine FET-Sitzung.\n"
|
||||
"du hast noch bis morgen "
|
||||
+ str(time)
|
||||
+ " Zeit, weitere Themen auf die Agenda zu schreiben: "
|
||||
+ settings.HOST_NAME
|
||||
+ "/posts/"
|
||||
+ str(meeting.slug)
|
||||
+ ".\n\n"
|
||||
"Die aktuelle Agenda ist: \n\n" + agenda + "\n\n"
|
||||
"LG deine FET"
|
||||
)
|
||||
|
||||
if send_mail(
|
||||
subject="Test - Agenda der FET Sitzung von " + str(date),
|
||||
message=msg,
|
||||
from_email="service@fet.at",
|
||||
recipient_list=["patrick@fet.at"],
|
||||
):
|
||||
logger.info("Mail für die Agenda %s wurde gesendet.", meeting.slug)
|
||||
else:
|
||||
logger.info("Mail für die Agenda %s wurde nicht gesendet.", meeting.slug)
|
||||
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 (max. 4)
|
||||
self.rentalitem_filters = request.GET.getlist("rentalitems", [])[:4]
|
||||
if not self.rentalitem_filters:
|
||||
for rentalitem in RentalItem.objects.all()[:4]:
|
||||
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 selected rental items to the context for the filter
|
||||
context["rentalitem_filters"] = {"rentalitems": self.rentalitem_filters}
|
||||
|
||||
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"
|
||||
Binary file not shown.
@@ -21,7 +21,5 @@ class NonUserSearchView(SearchView):
|
||||
|
||||
@authenticated_user
|
||||
def index(request):
|
||||
if request.user.is_authenticated:
|
||||
return FetUserSearchView.as_view()(request)
|
||||
|
||||
return NonUserSearchView.as_view()(request)
|
||||
view = FetUserSearchView if request.user.is_authenticated else NonUserSearchView
|
||||
return view.as_view()(request)
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
<li class="{% if 'posts' in request.path %}active{% endif %}"><a href="{% url 'posts:index' %}">News</a></li>
|
||||
<li class="{% if 'members' in request.path %}active{% endif %}"><a href="{% url 'members:index' %}">Fachschaft</a></li>
|
||||
<li class="{% if 'gallery' in request.path %}active{% endif %}"><a href="{% url 'gallery:index' %}">Galerie</a></li>
|
||||
<li class="{% if 'rental' in request.path %}active{% endif %}"><a href="{% url 'rental:index' %}">Verleih</a></li>
|
||||
<li class="{% if 'jobs' in request.path %}active{% endif %}"><a href="{% url 'blackboard:index' %}">Jobs</a></li>
|
||||
|
||||
{% get_flatpages '/kontakt/' as pages %}
|
||||
@@ -229,7 +230,9 @@
|
||||
{% endif %}
|
||||
<hr class="legal-divider">
|
||||
<p class="copyright">© {% now 'Y' %} FET - Alle Rechte vorbehalten.</p>
|
||||
<p class="text-center">{% version %}.</p>
|
||||
{% if request.user.is_authenticated %}
|
||||
<p class="text-center">{% version %}</p>
|
||||
{% 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>
|
||||
|
||||
20
fet2020/templates/baseform/email.html
Normal file
20
fet2020/templates/baseform/email.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<label>
|
||||
<span>{{ field.label }} {% if not field.field.required %}(optional){% endif %}</span>
|
||||
{% if field.errors %}
|
||||
<div class="alert alert-danger">
|
||||
<div class="alert-body">{{ field.errors }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<input
|
||||
type="email"
|
||||
name="{{ field.name }}"
|
||||
{% if field.value %}value="{{ field.value }}"{% endif %}
|
||||
{% if field.field.required %}required{% endif %}
|
||||
{% if field.field.disabled %}disabled{% endif %}
|
||||
{% if field.field.autofocus %}autofocus{% endif %}
|
||||
class="w-full"
|
||||
>
|
||||
{% if field.help_text %}
|
||||
<small class="text-gray-700 dark:text-gray-200">{{ field.help_text }}</small>
|
||||
{% endif %}
|
||||
</label>
|
||||
@@ -38,7 +38,7 @@
|
||||
<small>Details zur ursprünglichen bereits bezahlten Rechnung angeben.</small>
|
||||
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
|
||||
<div class="sm:col-span-4">
|
||||
<div class="col-span-full">
|
||||
{% include "baseform/textarea.html" with field=form.invoice %}
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
@@ -59,7 +59,8 @@
|
||||
type="number"
|
||||
name="amount"
|
||||
value={{ form.amount.value }}
|
||||
required
|
||||
{% if form.amount.field.required %}required{% endif %}
|
||||
{% if form.amount.field.disabled %}disabled{% endif %}
|
||||
min="0.00"
|
||||
step="0.01"
|
||||
placeholder="123,99"
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<small>Details zur ursprünglichen bereits bezahlten Rechnung angeben.</small>
|
||||
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
|
||||
<div class="sm:col-span-4">
|
||||
<div class="col-span-full">
|
||||
{% include "baseform/textarea.html" with field=form.invoice %}
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% load flatpages job_groups softhyphen_tags static %}
|
||||
{% load flatpages job_groups static %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Main Content -->
|
||||
@@ -55,9 +55,9 @@
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if active_job_group %}
|
||||
<li class="internalLI">
|
||||
<li class="internalLI hyphens-auto" lang="de">
|
||||
<a href="{% url 'admin:members_jobgroup_change' active_job_group.id %}">
|
||||
<i class="fa-solid fa-pen-to-square mr-1"></i>{{ active_job_group.name|softhyphen|safe }} bearbeiten
|
||||
<i class="fa-solid fa-pen-to-square mr-1"></i>{{ active_job_group.name|safe }} bearbeiten
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{% extends 'members/index.html' %}
|
||||
|
||||
{% load softhyphen_tags %}
|
||||
|
||||
{% block title %}{{ member.firstname }} {{ member.surname }}{% endblock %}
|
||||
|
||||
{% block extraheader %}
|
||||
@@ -24,7 +22,7 @@
|
||||
</div>
|
||||
{% if member.description %}
|
||||
<div class="mb-2">
|
||||
{{ member.description|softhyphen|safe }}
|
||||
{{ member.description|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
188
fet2020/templates/rental/calendar.html
Normal file
188
fet2020/templates/rental/calendar.html
Normal file
@@ -0,0 +1,188 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Verleih{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="container mx-auto w-full px-4 my-8 flex-1">
|
||||
<h1 class="page-title">Verleih</h1>
|
||||
|
||||
<section class="text-center my-6">
|
||||
<p class="my-6 text-gray-900 dark:text-gray-100">
|
||||
Willkommen bei unserem Verleih!
|
||||
</p>
|
||||
|
||||
<a href="{% url 'rental:rental_create' %}" class="page-subtitle block btn-small btn-primary max-w-xs mx-auto sm:w-max sm:mr-0 sm:ml-auto">
|
||||
<i class="fa-solid fa-plus mr-1"></i> Verleih anfragen
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<form action="" method="GET">
|
||||
<section>
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-6 lg:grid-cols-6 sm:grid-cols-3 pb-6">
|
||||
<button
|
||||
id="filterDropdownButton"
|
||||
data-dropdown-toggle="filterDropdown"
|
||||
class="w-full md:w-auto flex items-center justify-center py-2 px-4 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
type="button"
|
||||
>
|
||||
<i class="h-4 w-4 mr-2 fa-solid fa-filter"></i>
|
||||
Filter
|
||||
<i class="-mr-1 ml-1.5 mt-1 w-5 h-5 fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div id="filterDropdown" class="z-10 hidden w-56 p-3 bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<h6 class="mb-3 text-sm font-medium text-gray-900 dark:text-white">Verleihgegenstände</h6>
|
||||
<ul class="space-y-2 text-sm" aria-labelledby="filterDropdownButton">
|
||||
{% for item in rentalitems %}
|
||||
<li class="flex items-center">
|
||||
<input
|
||||
id="item_{{ item.id }}"
|
||||
type="checkbox"
|
||||
name="rentalitems"
|
||||
value="{{ item.name }}"
|
||||
{% for key, value in rentalitem_filters.items %}
|
||||
{% if key == "rentalitems" and item.name in value %}
|
||||
checked
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
class="w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
|
||||
>
|
||||
<label for="item_{{ item.id }}" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-100">{{ item.name }}</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="block btn btn-primary" name="" value="Submit">Filter anwenden</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="text-center">
|
||||
<div class="wrapper mx-auto bg-white rounded shadow max-w-full">
|
||||
<div class="header flex justify-between border-b p-2">
|
||||
<span class="text-lg font-bold">{{ month|date:'F Y' }}</span>
|
||||
|
||||
<div class="buttons">
|
||||
<button type="submit" class="p-1" name="month" value="{{ prev_month|date:'Y-m' }}">
|
||||
<i class="fa-regular fa-circle-left"></i>
|
||||
</button>
|
||||
<button type="submit" class="p-1" name="month" value="{{ next_month|date:'Y-m' }}">
|
||||
<i class="fa-regular fa-circle-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table-fixed w-full mx-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Monday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Mon</span>
|
||||
</th>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Tuesday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Tue</span>
|
||||
</th>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Wednesday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Wed</span>
|
||||
</th>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Thursday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Thu</span>
|
||||
</th>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Friday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Fri</span>
|
||||
</th>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Saturday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Sat</span>
|
||||
</th>
|
||||
<th class="p-2 border-r h-10 xl:text-sm text-xs">
|
||||
<span class="xl:block lg:block md:block sm:block hidden">Sunday</span>
|
||||
<span class="xl:hidden lg:hidden md:hidden sm:hidden block">Sun</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for day in days_of_prev_month %}
|
||||
{% if day.weekday == 0 %}
|
||||
<tr class="text-center h-20">
|
||||
{% endif %}
|
||||
|
||||
<td class="border p-1 h-40">
|
||||
<div class="flex flex-col h-40">
|
||||
<div class="top h-5 w-full">
|
||||
<span class="text-gray-500 text-sm">{{ day.day }}</span>
|
||||
</div>
|
||||
<div class="bottom flex-grow h-30 py-1 w-full"></div>
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
{% for day in days_of_month %}
|
||||
{% if day.weekday == 0 %}
|
||||
<tr class="text-center h-20">
|
||||
{% endif %}
|
||||
|
||||
<td class="border p-1 h-40">
|
||||
<div class="flex flex-col h-40">
|
||||
<div class="top h-5 w-full">
|
||||
{% if day == today %}
|
||||
<span class="text-gray-100 dark:text-gray-900 border-2 border-blue-900 dark:border-blue-100 rounded-full bg-blue-900 dark:bg-blue-100">{{ day.day }}</span>
|
||||
{% else %}
|
||||
<span class="text-gray-900 dark:text-gray-100">{{ day.day }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% for key, names in rental_dict.items %}
|
||||
{% if key == day %}
|
||||
{% for name in names %}
|
||||
<div class="bottom h-30 py-1 w-full">
|
||||
<div class="event bg-purple-400 text-white rounded p-1 text-sm">
|
||||
<span class="event-name whitespace-nowrap 2xl:block xl:hidden lg:hidden md:hidden sm:hidden hidden">{{ name|truncatechars:26 }}</span>
|
||||
<span class="event-name whitespace-nowrap 2xl:hidden xl:block lg:hidden md:hidden sm:hidden hidden">{{ name|truncatechars:20 }}</span>
|
||||
<span class="event-name whitespace-nowrap 2xl:hidden xl:hidden lg:block md:hidden sm:hidden hidden">{{ name|truncatechars:15 }}</span>
|
||||
<span class="event-name whitespace-nowrap 2xl:hidden xl:hidden lg:hidden md:block sm:hidden hidden">{{ name|truncatechars:10 }}</span>
|
||||
<span class="event-name whitespace-nowrap 2xl:hidden xl:hidden lg:hidden md:hidden sm:block hidden">{{ name|truncatechars:6 }}</span>
|
||||
<span class="event-name whitespace-nowrap 2xl:hidden xl:hidden lg:hidden md:hidden sm:hidden block">{{ name|truncatechars:3 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="bottom flex-grow h-30 py-1 w-full"></div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{% if day.weekday == 6 %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for day in days_of_next_month %}
|
||||
<td class="border p-1 h-40">
|
||||
<div class="flex flex-col h-40">
|
||||
<div class="top h-5 w-full">
|
||||
<span class="text-gray-500 text-sm">{{ day.day }}</span>
|
||||
</div>
|
||||
<div class="bottom flex-grow h-30 py-1 w-full"></div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{% if day.weekday == 6 %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
112
fet2020/templates/rental/create.html
Normal file
112
fet2020/templates/rental/create.html
Normal file
@@ -0,0 +1,112 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Verleih Anfrage{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto w-full px-4 my-8 flex-1">
|
||||
<h1 class="page-title">Verleih Anfrage</h1>
|
||||
|
||||
<form action="" enctype="multipart/form-data" method="POST" class="multiSectionForm max-w-2xl">
|
||||
{% csrf_token %}
|
||||
{% include "baseform/non_field_errors.html" %}
|
||||
|
||||
<section>
|
||||
<h2>Persönliche Daten</h2>
|
||||
<small>Bitte geben Sie Ihre 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">
|
||||
{% include "baseform/text.html" with field=form.firstname %}
|
||||
</div>
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/text.html" with field=form.surname %}
|
||||
</div>
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/text.html" with field=form.organization %}
|
||||
</div>
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/text.html" with field=form.matriculation_number %}
|
||||
</div>
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/email.html" with field=form.email %}
|
||||
</div>
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/text.html" with field=form.phone %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Verleihgegenstände</h2>
|
||||
<small>Wählen Sie die 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 %}
|
||||
<div class="col-span-full alert alert-danger">
|
||||
<div class="alert-body">{{ form.rentalitems.errors }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="{{ form.rentalitems.auto_id }}" class="col-span-full">
|
||||
{% for elem in form.rentalitems %}
|
||||
<div class="col-span-2 mb-2">
|
||||
|
||||
<label for="{{ elem.id_for_label }}">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{{ elem.id_for_label }}"
|
||||
name="{{ form.rentalitems.html_name }}"
|
||||
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>
|
||||
</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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/date.html" with field=form.date_start %}
|
||||
</div>
|
||||
<div class="sm:col-span-3">
|
||||
{% include "baseform/date.html" with field=form.date_end %}
|
||||
</div>
|
||||
<div class="col-span-full">
|
||||
{% include "baseform/textarea.html" with field=form.reason %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Zusätzliche Informationen</h2>
|
||||
<small>Hier können Sie 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">
|
||||
{% include "baseform/textarea.html" with field=form.comment %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="grid grid-cols-1 gap-x-6 gap-y-6 sm:grid-cols-6">
|
||||
<div class="col-span-full">
|
||||
{% include "baseform/checkbox.html" with field=form.conformation %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex justify-end">
|
||||
<button type="submit" class="btn btn-primary w-full sm:w-auto" value="Einreichen">Anfrage abschicken</button>
|
||||
</section>
|
||||
</form>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
17
fet2020/templates/rental/create_done.html
Normal file
17
fet2020/templates/rental/create_done.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Verleihanfrage erfolgreich eingereicht{% endblock %}
|
||||
|
||||
{% 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.
|
||||
</p>
|
||||
<div class="mt-10 flex items-center justify-center">
|
||||
<a href="{% url 'home' %}" type="submit" class="block btn btn-primary">Zur Startseite</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
Reference in New Issue
Block a user