Browse Source

initial Push

root 1 year ago
parent
commit
a765867ea8
63 changed files with 2138 additions and 0 deletions
  1. 8 0
      .idea/.gitignore
  2. 8 0
      .idea/AD.iml
  3. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  4. 7 0
      .idea/misc.xml
  5. 8 0
      .idea/modules.xml
  6. 6 0
      .idea/vcs.xml
  7. 0 0
      AD/__init__.py
  8. 16 0
      AD/asgi.py
  9. 133 0
      AD/settings.py
  10. 28 0
      AD/urls.py
  11. 16 0
      AD/wsgi.py
  12. BIN
      db.sqlite3
  13. 0 0
      helper/__init__.py
  14. 24 0
      helper/admin.py
  15. 6 0
      helper/apps.py
  16. 13 0
      helper/forms.py
  17. 31 0
      helper/models.py
  18. 3 0
      helper/tests.py
  19. 13 0
      helper/urls.py
  20. 71 0
      helper/views.py
  21. 0 0
      main/__init__.py
  22. 35 0
      main/admin.py
  23. 6 0
      main/apps.py
  24. 58 0
      main/forms.py
  25. 74 0
      main/migrations/0001_initial.py
  26. 33 0
      main/migrations/0002_location_event.py
  27. 23 0
      main/migrations/0003_helper.py
  28. 23 0
      main/migrations/0004_rename_benoetigt_helper_ben_and_more.py
  29. 18 0
      main/migrations/0005_alter_helper_best.py
  30. 22 0
      main/migrations/0006_reinigung.py
  31. 28 0
      main/migrations/0007_rename_auftrag_reinigung_auftrag_and_more.py
  32. 18 0
      main/migrations/0008_shift_info.py
  33. 18 0
      main/migrations/0009_event_belegung.py
  34. 18 0
      main/migrations/0010_event_pax.py
  35. 18 0
      main/migrations/0011_shift_shiftchef.py
  36. 0 0
      main/migrations/__init__.py
  37. 125 0
      main/models.py
  38. 0 0
      main/templatetags/__init__.py
  39. 7 0
      main/templatetags/custom_tags.py
  40. 3 0
      main/tests.py
  41. 23 0
      main/urls.py
  42. 269 0
      main/views.py
  43. 22 0
      manage.py
  44. 7 0
      requirements.txt
  45. BIN
      static/favicon.png
  46. 86 0
      static/main/css/styles.css
  47. 19 0
      templates/base.html
  48. 20 0
      templates/base_pub.html
  49. 21 0
      templates/main/create_event.html
  50. 21 0
      templates/main/create_helper.html
  51. 21 0
      templates/main/create_multiple_shifts.html
  52. 21 0
      templates/main/create_reinigung.html
  53. 224 0
      templates/main/current_week_shifts.html
  54. 22 0
      templates/main/delete_event.html
  55. 22 0
      templates/main/delete_helper.html
  56. 22 0
      templates/main/delete_reinigung.html
  57. 22 0
      templates/main/delete_shift.html
  58. 25 0
      templates/main/edit_event.html
  59. 25 0
      templates/main/edit_helper.html
  60. 25 0
      templates/main/edit_reinigung.html
  61. 25 0
      templates/main/edit_shift.html
  62. 234 0
      templates/main/public.html
  63. 38 0
      templates/registration/login.html

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 8 - 0
.idea/AD.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="Python 3.11 (AD)" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (AD)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/AD.iml" filepath="$PROJECT_DIR$/.idea/AD.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 0 - 0
AD/__init__.py


+ 16 - 0
AD/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for AD project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AD.settings')
+
+application = get_asgi_application()

+ 133 - 0
AD/settings.py

@@ -0,0 +1,133 @@
+"""
+Django settings for AD project.
+
+Generated by 'django-admin startproject' using Django 5.0.6.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.0/ref/settings/
+"""
+
+from pathlib import Path
+import os
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-+hac38u=6&ltcih_adxr9oz8ettju9le&lfq@uslvo-#lncic%'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'bootstrap5',
+    'main',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'AD.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [BASE_DIR / 'templates']
+        ,
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'AD.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': BASE_DIR / 'db.sqlite3',
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.0/topics/i18n/
+
+LANGUAGE_CODE = 'de-de'
+
+TIME_ZONE = 'Europe/Berlin'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.0/howto/static-files/
+
+STATIC_URL = '/static/'
+STATICFILES_DIRS = [
+    BASE_DIR / "static",
+]
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+LOGIN_REDIRECT_URL = '/'
+LOGOUT_REDIRECT_URL = '/'

+ 28 - 0
AD/urls.py

@@ -0,0 +1,28 @@
+"""
+URL configuration for AD project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/5.0/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+from django.views.generic import RedirectView
+from django.urls import re_path as url
+admin.autodiscover()
+
+
+urlpatterns = [
+    url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon.png')),
+    path('admin/', admin.site.urls),
+    path('', include('main.urls'))
+]

+ 16 - 0
AD/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for AD project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AD.settings')
+
+application = get_wsgi_application()

BIN
db.sqlite3


+ 0 - 0
helper/__init__.py


+ 24 - 0
helper/admin.py

@@ -0,0 +1,24 @@
+from django.contrib import admin
+from django.contrib.auth.admin import UserAdmin
+from .models import CustomUser, Schichten, Rueckmeldungen
+
+
+# Register your models here.
+class CustomUserAdmin(UserAdmin):
+    list_display = ('username', 'email', 'telefon', 'is_staff', 'is_active', 'available', 'available_from', 'available_until')
+    fieldsets = (
+        (None, {'fields': ('username', 'password')}),
+        ('Persönliche Informationen', {'fields': ('first_name', 'last_name', 'email', 'telefon', 'available', 'available_from', 'available_until')}),
+        ('Berechtigungen', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
+        ('Wichtige Daten', {'fields': ('last_login', 'date_joined')}),
+    )
+    add_fieldsets = (
+        (None, {
+            'classes': ('wide',),
+            'fields': ('username', 'email', 'password1', 'password2', 'telefon'),
+        }),
+    )
+
+admin.site.register(CustomUser, CustomUserAdmin)
+admin.site.register(Schichten)
+admin.site.register(Rueckmeldungen)

+ 6 - 0
helper/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class HelperConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'helper'

+ 13 - 0
helper/forms.py

@@ -0,0 +1,13 @@
+from django import forms
+from django.contrib.auth.forms import UserCreationForm
+from django.contrib.auth.models import User
+
+
+class UserRegistrationForm(UserCreationForm):
+    email = forms.EmailField(max_length=100)
+    first_name = forms.CharField(max_length=30)
+    last_name = forms.CharField(max_length=30)
+
+    class Meta:
+        model = User
+        fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')

+ 31 - 0
helper/models.py

@@ -0,0 +1,31 @@
+from django.db import models
+from django.contrib.auth.models import AbstractUser
+
+
+# Create your models here.
+class CustomUser(AbstractUser):
+    telefon = models.CharField(max_length=15, blank=True, null=True,verbose_name="Telefon")
+    available = models.BooleanField(default=False, verbose_name="Verfügbar")
+    available_from = models.DateField(blank=True, null=True,verbose_name="Verfügbar ab")
+    available_until = models.DateField(blank=True, null=True,verbose_name="Verfügbar bis")
+
+
+class Schichten(models.Model):
+    datum = models.DateField(verbose_name="Datum")
+    beginn = models.TimeField(verbose_name="Arbeitsbeginn")
+    ende = models.TimeField(verbose_name="Voraussichtliches Arbeitsende", null=True, blank=True)
+    anzahl = models.CharField(max_length=2, verbose_name="Benötigte Helfer")
+    beschreibung = models.TextField(verbose_name="Arbeitsbeschreibung")
+
+    def __str__(self):
+        return f"{self.datum} {self.beginn} - {self.beschreibung}"
+
+
+class Rueckmeldungen(models.Model):
+    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, verbose_name="Username")
+    datum = models.ForeignKey(Schichten, on_delete=models.CASCADE, verbose_name="Datum")
+    zusage = models.BooleanField(null=True, blank=True, verbose_name="Zusage")
+    bestaetigt = models.BooleanField(null=True, blank=True, verbose_name="Bestätigt")
+
+    def __str__(self):
+        return f"{self.datum}, {self.user}"

+ 3 - 0
helper/tests.py

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

+ 13 - 0
helper/urls.py

@@ -0,0 +1,13 @@
+from django.contrib import admin
+from django.urls import path, include
+from . import views
+from .views import register, zusagen_list
+
+
+urlpatterns = [
+    path('',  views.schichten_list, name="home"),
+    path('helfer_list/',views.helfer_list, name="helfer_list"),
+    path('register/', register, name='NewUser'),
+    path('zlist/', zusagen_list, name='zlist'),
+    path('accounts/', include("django.contrib.auth.urls")),
+]

+ 71 - 0
helper/views.py

@@ -0,0 +1,71 @@
+from django.http import HttpResponse, request, HttpResponseRedirect
+from django.template import Context, loader
+from django.shortcuts import render, redirect
+from django.contrib import auth, messages
+from .models import Schichten, Rueckmeldungen, CustomUser
+from django.contrib.auth.forms import UserCreationForm
+from .forms import UserRegistrationForm
+from django.contrib.auth.models import User
+from datetime import date, datetime
+# Create your views here.
+
+
+def zusagen_list(request):
+    if request.method == 'POST':
+        datum = request.POST['datum']
+        zusagen =Rueckmeldungen.objects.filter(datum=datum)
+        schicht = Schichten.objects.filter(id=datum)
+        print(schicht)
+
+    context = {
+        'zusagen': zusagen,
+        'schicht': schicht,
+    }
+
+    return render(request, 'helper/zusagen_list.html', context)
+
+
+def schichten_list(request):
+    if request.method == 'POST':
+        datum = request.POST['datum']
+        zusage = request.POST['zusage']
+        rueckmeldungen = Rueckmeldungen.objects.create(zusage=zusage, datum_id=datum, user=request.user)
+
+    loggedin = request.user
+    if request.user.is_superuser:
+        zusagen = Rueckmeldungen.objects.all()
+        schichten = Schichten.objects.filter(datum__gte=date.today())
+    elif request.user.is_authenticated:
+        zusagen = Rueckmeldungen.objects.filter(user=loggedin)
+        schichten = Schichten.objects.exclude(rueckmeldungen__user=loggedin)
+    else:
+        zusagen = []
+        schichten = []
+
+    context = {
+        'schichten': schichten,
+        'zusagen': zusagen
+
+    }
+
+    return render(request, 'helper/home.html', context)
+
+
+def helfer_list(request):
+    helfer = User.objects.filter(is_staff=False)
+    context = {
+        'helfer': helfer
+    }
+
+    return render(request, 'helper/helfer_list.html', context)
+
+
+def register(request):
+    if request.method == 'POST':
+        form = UserRegistrationForm(request.POST)
+        if form.is_valid():
+            form.save()
+            return redirect('login')
+    else:
+        form = UserRegistrationForm()
+    return render(request, 'helper/../templates/registration/register.html', {'form': form})

+ 0 - 0
main/__init__.py


+ 35 - 0
main/admin.py

@@ -0,0 +1,35 @@
+from django.contrib import admin
+from .models import Employee, Shift, Location, Event, Helper, Reinigung
+
+
+class HelperAdmin(admin.ModelAdmin):
+    list_display = ('date', 'ben', 'best', 'info')
+    search_fields = ('date', 'info')
+
+class ReinigungAdmin(admin.ModelAdmin):
+    list_display = ('date', 'auftrag', 'info')
+    search_fields = ('date', 'info')
+
+
+class EmployeeAdmin(admin.ModelAdmin):
+    list_display = ('name', 'daily_workhours', 'info')
+    search_fields = ('name',)
+
+
+class ShiftAdmin(admin.ModelAdmin):
+    list_display = ('date', 'start', 'end', 'employee')
+    list_filter = ('date', 'shifttype', 'employee')
+    search_fields = ('date','employee')
+
+
+class EventAdmin(admin.ModelAdmin):
+    list_display = ('date', 'name', 'event_type')
+    list_filter = ('date', 'name', 'event_type')
+    search_fields = ('date', 'name', 'location', 'event_type')
+
+admin.site.register(Employee, EmployeeAdmin)
+admin.site.register(Shift, ShiftAdmin)
+admin.site.register(Event, EventAdmin)
+admin.site.register(Location)
+admin.site.register(Helper, HelperAdmin)
+admin.site.register(Reinigung, ReinigungAdmin)

+ 6 - 0
main/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class MainConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'main'

+ 58 - 0
main/forms.py

@@ -0,0 +1,58 @@
+from django import forms
+from .models import Shift, Employee, Event, Helper, Reinigung
+from django.utils.translation import gettext_lazy as _
+
+
+class MultipleShiftForm(forms.Form):
+
+    #class Meta:
+       # model = Shift
+       # fields = ['employees', 'date', 'start', 'end', 'shifttype', 'info']
+        #widgets = {
+          #  'employees': forms.ModelMultipleChoiceField(queryset=Employee.objects.all(), widget=forms.CheckboxSelectMultiple),
+          #  'date': forms.widgets.DateInput(attrs={'type': 'date'}),
+          #  'start': forms.TimeField(required=False, widget=forms.TimeInput(format='%H:%M')),
+          #  'end': forms.TimeField(required=False, widget=forms.TimeInput(format='%H:%M')),
+          #  'shifttype': forms.ChoiceField(choices=Shift.ShiftType.choices),
+          #  'info': forms.Textarea()
+        #}
+
+    employees = forms.ModelMultipleChoiceField(queryset=Employee.objects.all(), widget=forms.CheckboxSelectMultiple)
+    date = forms.DateField(widget=forms.DateInput)
+    start = forms.TimeField(required=False, widget=forms.TimeInput(format='%H:%M'))
+    end = forms.TimeField(required=False, widget=forms.TimeInput(format='%H:%M'))
+    shifttype = forms.ChoiceField(choices=Shift.ShiftType.choices)
+    info = forms.CharField(required=False, widget=forms.Textarea)
+
+
+class ShiftForm(forms.ModelForm):
+    class Meta:
+        model = Shift
+        fields = ['date', 'start', 'end', 'shifttype', 'employee', 'info', 'shiftchef']
+
+
+class EventForm(forms.ModelForm):
+    class Meta:
+        model = Event
+        fields = ['date', 'name', 'event_type', 'location', 'belegung', 'pax', 'cvd', 'cvt', 'info']
+
+
+class ReinigungForm(forms.ModelForm):
+    class Meta:
+        model = Reinigung
+        fields = ['date', 'auftrag', 'info']
+
+class HelperForm(forms.ModelForm):
+
+    class Meta:
+        model = Helper
+        fields = ['date', 'ben', 'best', 'info']
+        widgets = {
+            'date': forms.widgets.DateInput(attrs={'type': 'date'})
+        }
+        labels = {
+            'date': _('Datum'),
+            'ben': _('Benötigt'),
+            'best': _('Bestätigt'),
+            'info': _('Info')
+        }

+ 74 - 0
main/migrations/0001_initial.py

@@ -0,0 +1,74 @@
+# Generated by Django 5.0.2 on 2024-10-26 19:45
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Employee',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+                ('daily_workhours', models.PositiveIntegerField()),
+                ('info', models.TextField(blank=True, null=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Helper',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('ben', models.PositiveSmallIntegerField()),
+                ('best', models.PositiveSmallIntegerField(blank=True, null=True)),
+                ('info', models.TextField(blank=True, null=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Location',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Reinigung',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('auftrag', models.CharField(choices=[('Beauftragen', 'Beauftragen'), ('Bestellt', 'Bestellt')], default='', max_length=12, null=True)),
+                ('info', models.TextField(blank=True, null=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Event',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('name', models.CharField(max_length=100)),
+                ('event_type', models.CharField(choices=[('', 'None'), ('AU', 'Aufbau'), ('AB', 'Abbau'), ('UM', 'Umbau'), ('VA', 'VA-Tag'), ('ST', 'Spieltag'), ('TR', 'Training')], default='', max_length=2, null=True)),
+                ('info', models.TextField(blank=True, null=True)),
+                ('cvd', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cvt', to='main.employee')),
+                ('cvt', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cvd', to='main.employee')),
+                ('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.location')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Shift',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('start', models.TimeField(blank=True, null=True)),
+                ('end', models.TimeField(blank=True, null=True)),
+                ('shifttype', models.CharField(choices=[('N', 'None'), ('U', 'Urlaub'), ('C', 'Catering'), ('K', 'Krank'), ('RB', 'Rufbereitschaft'), ('SO', 'Sonderdienst'), ('KKH', 'KKH'), ('ST', 'Stapler'), ('AH', 'Ausser Haus')], default='N', max_length=3, null=True)),
+                ('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.employee')),
+            ],
+        ),
+    ]

+ 33 - 0
main/migrations/0002_location_event.py

@@ -0,0 +1,33 @@
+# Generated by Django 5.0.2 on 2024-06-14 08:30
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Location',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Event',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('name', models.CharField(max_length=100)),
+                ('event_type', models.CharField(choices=[('', 'None'), ('AU', 'Aufbau'), ('AB', 'Abbau'), ('UM', 'Umbau'), ('VA', 'VA-Tag'), ('ST', 'Spieltag'), ('TR', 'Training')], default='', max_length=2, null=True)),
+                ('cvd', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cvt', to='main.employee')),
+                ('cvt', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cvd', to='main.employee')),
+                ('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.location')),
+            ],
+        ),
+    ]

+ 23 - 0
main/migrations/0003_helper.py

@@ -0,0 +1,23 @@
+# Generated by Django 5.0.2 on 2024-07-22 07:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0002_location_event'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Helper',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('benoetigt', models.PositiveSmallIntegerField()),
+                ('bestaetigt', models.PositiveSmallIntegerField()),
+                ('info', models.TextField(blank=True, null=True)),
+            ],
+        ),
+    ]

+ 23 - 0
main/migrations/0004_rename_benoetigt_helper_ben_and_more.py

@@ -0,0 +1,23 @@
+# Generated by Django 5.0.2 on 2024-07-22 07:29
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0003_helper'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='helper',
+            old_name='benoetigt',
+            new_name='ben',
+        ),
+        migrations.RenameField(
+            model_name='helper',
+            old_name='bestaetigt',
+            new_name='best',
+        ),
+    ]

+ 18 - 0
main/migrations/0005_alter_helper_best.py

@@ -0,0 +1,18 @@
+# Generated by Django 5.0.2 on 2024-07-22 19:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0004_rename_benoetigt_helper_ben_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='helper',
+            name='best',
+            field=models.PositiveSmallIntegerField(blank=True, null=True),
+        ),
+    ]

+ 22 - 0
main/migrations/0006_reinigung.py

@@ -0,0 +1,22 @@
+# Generated by Django 5.0.2 on 2024-10-18 15:57
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0005_alter_helper_best'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Reinigung',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateField()),
+                ('Auftrag', models.CharField(choices=[('Beauftragen', 'Beauftragen'), ('Bestellt', 'Bestellt')], default='', max_length=12, null=True)),
+                ('Info', models.TextField(blank=True, null=True)),
+            ],
+        ),
+    ]

+ 28 - 0
main/migrations/0007_rename_auftrag_reinigung_auftrag_and_more.py

@@ -0,0 +1,28 @@
+# Generated by Django 5.0.2 on 2024-10-26 19:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0006_reinigung'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='reinigung',
+            old_name='Auftrag',
+            new_name='auftrag',
+        ),
+        migrations.RenameField(
+            model_name='reinigung',
+            old_name='Info',
+            new_name='info',
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='info',
+            field=models.TextField(blank=True, null=True),
+        ),
+    ]

+ 18 - 0
main/migrations/0008_shift_info.py

@@ -0,0 +1,18 @@
+# Generated by Django 5.0.2 on 2024-10-26 19:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0007_rename_auftrag_reinigung_auftrag_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='shift',
+            name='info',
+            field=models.TextField(blank=True, null=True),
+        ),
+    ]

+ 18 - 0
main/migrations/0009_event_belegung.py

@@ -0,0 +1,18 @@
+# Generated by Django 5.0.2 on 2024-11-09 17:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0008_shift_info'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='event',
+            name='belegung',
+            field=models.CharField(choices=[('', 'None'), ('HH', 'Halbe Halle'), ('GH', 'Ganze Halle')], default='', max_length=2, null=True),
+        ),
+    ]

+ 18 - 0
main/migrations/0010_event_pax.py

@@ -0,0 +1,18 @@
+# Generated by Django 5.0.2 on 2024-11-09 17:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0009_event_belegung'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='event',
+            name='pax',
+            field=models.SmallIntegerField(blank=True, null=True),
+        ),
+    ]

+ 18 - 0
main/migrations/0011_shift_shiftchef.py

@@ -0,0 +1,18 @@
+# Generated by Django 5.0.2 on 2024-11-09 18:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0010_event_pax'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='shift',
+            name='shiftchef',
+            field=models.CharField(choices=[('', 'None'), ('D', 'CVD'), ('T', 'CVT')], default='', max_length=3, null=True),
+        ),
+    ]

+ 0 - 0
main/migrations/__init__.py


+ 125 - 0
main/models.py

@@ -0,0 +1,125 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+# Create your models here.
+
+
+class Helper(models.Model):
+    date = models.DateField()
+    ben = models.PositiveSmallIntegerField()
+    best = models.PositiveSmallIntegerField(blank=True, null=True)
+    info = models.TextField(blank=True, null=True)
+
+    def __str__(self):
+        return f"{self.date} - {self.ben} - {self.best}"
+
+class Reinigung(models.Model):
+    class ShiftType(models.TextChoices):
+        Beauftragen = 'Beauftragen'  # Placeholder for "None" if you're using it
+        Bestellt = 'Bestellt'
+
+    date = models.DateField()
+    auftrag = models.CharField(
+        max_length=12,
+        choices=ShiftType.choices,
+        default='',
+        null=True,
+    )
+    info = models.TextField(blank=True, null=True)
+
+    def __str__(self):
+        return f"{self.date} - {self.auftrag}"
+
+class Employee(models.Model):
+    name = models.CharField(max_length=100)
+    daily_workhours = models.PositiveIntegerField()
+    info = models.TextField(blank=True, null=True)
+
+    def __str__(self):
+        return self.name
+
+
+class Shift(models.Model):
+    class ShiftType(models.TextChoices):
+        NONE = 'N', _('None')  # Placeholder for "None" if you're using it
+        VACATION = 'U', _('Urlaub')
+        CATERING = 'C', _('Catering')
+        SICK = 'K', _('Krank')
+        RB = 'RB', _('Rufbereitschaft')
+        SD = 'SO', _('Sonderdienst')
+        KK = 'KKH', _('KKH')
+        ST = 'ST', _('Stapler')
+        AH = 'AH', _('Ausser Haus')
+
+    class ShiftChef(models.TextChoices):
+        NONE = '', _('None')  # Placeholder for "None" if you're using it
+        CVD = 'D', _('CVD')
+        CVT = 'T', _('CVT')
+
+    date = models.DateField()
+    start = models.TimeField(null=True, blank=True)
+    end = models.TimeField(null=True, blank=True)
+    shifttype = models.CharField(
+        max_length=3,
+        choices=ShiftType.choices,
+        default=ShiftType.NONE,
+        null=True,
+    )
+    shiftchef = models.CharField(
+        max_length=3,
+        choices=ShiftChef.choices,
+        default=ShiftChef.NONE,
+        null=True,
+        blank=True
+    )
+    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
+    info = models.TextField(null=True, blank=True)
+
+    def __str__(self):
+        return f"{self.date} - {self.get_shifttype_display()}"
+
+
+class Location(models.Model):
+    name = models.CharField(max_length=100)
+
+    def __str__(self):
+        return self.name
+
+
+class Event(models.Model):
+    class EventType(models.TextChoices):
+        NONE = '', _('None')
+        AUFBAU = 'AU', _('Aufbau')
+        ABBAU = 'AB', _('Abbau')
+        UMBAU = 'UM', _('Umbau')
+        VATAG = 'VA', _('VA-Tag')
+        SPIELTAG = 'ST', _('Spieltag')
+        TRAINING = 'TR', _('Training')
+
+    class EventBelegung(models.TextChoices):
+        NONE = '', _('None')
+        HalbeHalle = 'HH', _('Halbe Halle')
+        GanzeHalle = 'GH', _('Ganze Halle')
+
+    date = models.DateField()
+    name = models.CharField(max_length=100)
+    event_type = models.CharField(
+        max_length=2,
+        choices=EventType.choices,
+        default=EventType.NONE,
+        null=True,
+    )
+    location = models.ForeignKey(Location, on_delete=models.CASCADE)
+    belegung = models.CharField(
+        max_length=2,
+        choices=EventBelegung.choices,
+        default=EventBelegung.NONE,
+        null=True,
+    )
+    pax = models.SmallIntegerField(null=True, blank=True)
+    cvd = models.ForeignKey(Employee, related_name='cvt', on_delete=models.SET_NULL, null=True, blank=True)
+    cvt = models.ForeignKey(Employee, related_name='cvd', on_delete=models.SET_NULL, null=True, blank=True)
+    info = models.TextField(null=True, blank=True)
+
+
+    def __str__(self):
+        return f"{self.date} - {self.name}"

+ 0 - 0
main/templatetags/__init__.py


+ 7 - 0
main/templatetags/custom_tags.py

@@ -0,0 +1,7 @@
+from django import template
+
+register = template.Library()
+
+@register.filter
+def get_item(dictionary, key):
+    return dictionary.get(key)

+ 3 - 0
main/tests.py

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

+ 23 - 0
main/urls.py

@@ -0,0 +1,23 @@
+from django.urls import path
+from .views import (create_multiple_shifts, edit_shift, delete_shift, delete_event, edit_event,
+                    create_event, public, create_helper, edit_helper, delete_helper, create_reinigung, edit_reinigung,
+                    delete_reinigung)
+from django.contrib.auth import views as auth_views
+
+urlpatterns = [
+    path('create-multiple-shifts/', create_multiple_shifts, name='create_multiple_shifts'),
+    path('create-event/', create_event, name='create_event'),
+    path('create-helper/', create_helper, name='create_helper'),
+    path('create-reinigung/', create_reinigung, name='create_reinigung'),
+    path('edit-shift/<int:pk>/', edit_shift, name='edit_shift'),
+    path('edit-helper/<int:pk>/', edit_helper, name='edit_helper'),
+    path('edit-reinigung/<int:pk>/', edit_reinigung, name='edit_reinigung'),
+    path('delete-shift/<int:pk>/', delete_shift, name='delete_shift'),
+    path('delete-helper/<int:pk>/', delete_helper, name='delete_helper'),
+    path('delete-reinigung/<int:pk>/', delete_reinigung, name='delete_reinigung'),
+    path('edit-event/<int:pk>/', edit_event, name='edit_event'),
+    path('delete-event/<int:pk>/', delete_event, name='delete_event'),
+    path('login/', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'),
+    path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
+    path('', public, name='public'),
+]

+ 269 - 0
main/views.py

@@ -0,0 +1,269 @@
+from django.shortcuts import render, redirect, get_object_or_404
+from .forms import MultipleShiftForm, ShiftForm, EventForm, HelperForm, ReinigungForm
+from .models import Shift, Employee, Location, Event, Helper, Reinigung
+from django.utils.timezone import datetime, timedelta
+from django.utils import timezone
+from django.contrib.auth.decorators import user_passes_test
+import calendar
+
+
+def create_multiple_shifts(request):
+    date_str = request.GET.get('date')
+    initial_data = {}
+    if date_str:
+        initial_data['date'] = date_str
+
+    if request.method == 'POST':
+        form = MultipleShiftForm(request.POST)
+        if form.is_valid():
+            employees = form.cleaned_data['employees']
+            date = form.cleaned_data['date']
+            start = form.cleaned_data['start']
+            end = form.cleaned_data['end']
+            shifttype = form.cleaned_data['shifttype']
+            info = form.cleaned_data['info']
+
+
+            for employee in employees:
+                Shift.objects.create(
+                    employee=employee,
+                    date=date,
+                    start=start,
+                    end=end,
+                    shifttype=shifttype,
+                    info=info
+                )
+            return redirect('public')  # Annahme, dass Sie eine Erfolgsmeldung anzeigen möchten
+    else:
+        form = MultipleShiftForm(initial=initial_data)
+
+    return render(request, 'main/create_multiple_shifts.html', {'form': form})
+
+
+def create_event(request):
+    date_str = request.GET.get('date')
+    initial_data = {}
+    if date_str:
+        initial_data['date'] = date_str
+
+    if request.method == 'POST':
+        form = EventForm(request.POST)
+        if form.is_valid():
+            name = form.cleaned_data['name']
+            date = form.cleaned_data['date']
+            event_type = form.cleaned_data['event_type']
+            location = form.cleaned_data['location']
+            belegung = form.cleaned_data['belegung']
+            pax = form.cleaned_data['pax']
+            cvd = form.cleaned_data['cvd']
+            cvt = form.cleaned_data['cvt']
+            Event.objects.create(
+                name=name,
+                date=date,
+                event_type=event_type,
+                location=location,
+                belegung=belegung,
+                pax = pax,
+                cvd=cvd,
+                cvt=cvt
+                )
+            return redirect('public')  # Annahme, dass Sie eine Erfolgsmeldung anzeigen möchten
+    else:
+        form = EventForm(initial=initial_data)
+
+    return render(request, 'main/create_event.html', {'form': form})
+
+
+# Test function to check if the user is an admin
+def is_admin(user):
+    return user.is_authenticated and user.is_staff
+
+
+def public(request):
+    # Standardmäßig die aktuelle Woche anzeigen
+    today = timezone.now().date()
+
+    # Überprüfen, ob ein Startdatum in der URL angegeben ist
+    start_date_str = request.GET.get('start_date')
+    if start_date_str:
+        try:
+            start_of_week = datetime.strptime(start_date_str, '%Y-%m-%d').date()
+        except ValueError:
+            start_of_week = today - timedelta(days=today.weekday())  # Fallback auf die aktuelle Woche bei Fehler
+    else:
+        start_of_week = today - timedelta(days=today.weekday())  # Montag der aktuellen Woche
+
+    end_of_week = start_of_week + timedelta(days=6)  # Sonntag der aktuellen Woche
+
+    # Berechnung für die nächste und vorherige Woche
+    previous_week = start_of_week - timedelta(days=7)
+    next_week = start_of_week + timedelta(days=7)
+
+    # Berechnung der Kalenderwoche
+    calendar_week = start_of_week.isocalendar()[1]
+
+    # Initialisiere ein Dictionary für die Schichten der Mitarbeiter
+    shifts_by_employee = {}
+    events_by_location = {}
+    helpers_by_date = {}
+    employees = Employee.objects.all()
+    locations = Location.objects.all()
+    helpers = Helper.objects.all()
+
+    for employee in employees:
+        shifts_by_employee[employee] = {day: None for day in range(7)}
+
+    for location in locations:
+        events_by_location[location] = {day: None for day in range(7)}
+
+    for helper in helpers:
+        helpers_by_date = {day: None for day in range(7)}
+
+        # Hole alle Schichten für die aktuelle Woche
+    shifts = Shift.objects.filter(date__range=[start_of_week, end_of_week])
+
+    events = Event.objects.filter(date__range=[start_of_week, end_of_week])
+
+    helpers = Helper.objects.filter(date__range=[start_of_week, end_of_week])
+
+
+    # Fülle das Dictionary mit den Schichtdaten
+    for shift in shifts:
+        employee = shift.employee
+        day_of_week = (shift.date - start_of_week).days
+        shifts_by_employee[employee][day_of_week] = shift
+
+    for event in events:
+        location = event.location
+        day_of_week = (event.date - start_of_week).days
+        events_by_location[location][day_of_week] = event
+
+    for helper in helpers:
+        day_of_week = (helper.date - start_of_week).days
+        helpers_by_date[day_of_week] = helper
+
+    # Bereite die Daten der Woche für das Template vor
+    week_dates = [(start_of_week + timedelta(days=i)).strftime("%d.%m.%Y") for i in range(7)]
+    days_of_week = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
+    days_with_dates = list(zip(days_of_week, week_dates))
+
+    context = {
+        'shifts_by_employee': shifts_by_employee,
+        'events_by_location': events_by_location,
+        'helpers_by_date': helpers_by_date,
+        'start_of_week': start_of_week,
+        'end_of_week': end_of_week,
+        'days_with_dates': days_with_dates,
+        'range_days': range(7),
+        'previous_week': previous_week.strftime('%Y-%m-%d'),
+        'next_week': next_week.strftime('%Y-%m-%d'),
+        'calendar_week': calendar_week,
+    }
+    return render(request, 'main/public.html', context)
+
+def create_reinigung(request):
+    date_str = request.GET.get('date')
+    initial_data = {}
+    if date_str:
+        initial_data['date'] = date_str
+
+    if request.method == 'POST':
+        form = ReinigungForm(request.POST)
+        if form.is_valid():
+            form.save()
+            return redirect('public')  # Redirect to a relevant page after saving
+    else:
+        form = ReinigungForm()
+    return render(request, 'main/create_reinigung.html', {'form': form})
+
+def create_helper(request):
+    date_str = request.GET.get('date')
+    initial_data = {}
+    if date_str:
+        initial_data['date'] = date_str
+
+    if request.method == 'POST':
+        form = HelperForm(request.POST)
+        if form.is_valid():
+            form.save()
+            return redirect('public')  # Redirect to a relevant page after saving
+    else:
+        form = HelperForm()
+    return render(request, 'main/create_helper.html', {'form': form})
+
+
+def edit_reinigung(request, pk):
+    reinigung = get_object_or_404(Reinigung, pk=pk)
+    if request.method == 'POST':
+        form = ReinigungForm(request.POST, instance=reinigung)
+        if form.is_valid():
+            form.save()
+            return redirect('public')
+    else:
+        form = ReinigungForm(instance=reinigung)
+    return render(request, 'main/edit_reinigung.html', {'form': form, 'reinigung': reinigung})
+
+def edit_helper(request, pk):
+    helper = get_object_or_404(Helper, pk=pk)
+    if request.method == 'POST':
+        form = HelperForm(request.POST, instance=helper)
+        if form.is_valid():
+            form.save()
+            return redirect('public')
+    else:
+        form = HelperForm(instance=helper)
+    return render(request, 'main/edit_helper.html', {'form': form, 'helper': helper})
+
+
+def edit_shift(request, pk):
+    shift = get_object_or_404(Shift, pk=pk)
+    if request.method == 'POST':
+        form = ShiftForm(request.POST, instance=shift)
+        if form.is_valid():
+            form.save()
+            return redirect('public')
+    else:
+        form = ShiftForm(instance=shift)
+    return render(request, 'main/edit_shift.html', {'form': form, 'shift': shift})
+
+
+def edit_event(request, pk):
+    event = get_object_or_404(Event, pk=pk)
+    if request.method == 'POST':
+        form = EventForm(request.POST, instance=event)
+        if form.is_valid():
+            form.save()
+            return redirect('public')
+    else:
+        form = EventForm(instance=event)
+    return render(request, 'main/edit_event.html', {'form': form, 'event': event})
+
+
+def delete_shift(request, pk):
+    shift = get_object_or_404(Shift, pk=pk)
+    if request.method == 'POST':
+        shift.delete()
+        return redirect('public')
+    return render(request, 'main/delete_shift.html', {'shift': shift})
+
+def delete_reinigung(request, pk):
+    reinigung = get_object_or_404(Reinigung, pk=pk)
+    if request.method == 'POST':
+        reinigung.delete()
+        return redirect('public')
+    return render(request, 'main/delete_reinigung.html', {'reinigung': reinigung})
+
+def delete_helper(request, pk):
+    helper = get_object_or_404(Helper, pk=pk)
+    if request.method == 'POST':
+        helper.delete()
+        return redirect('public')
+    return render(request, 'main/delete_helper.html', {'helper': helper})
+
+
+def delete_event(request, pk):
+    event = get_object_or_404(Event, pk=pk)
+    if request.method == 'POST':
+        event.delete()
+        return redirect('public')
+    return render(request, 'main/delete_event.html', {'event': event})

+ 22 - 0
manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'AD.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 7 - 0
requirements.txt

@@ -0,0 +1,7 @@
+asgiref==3.7.2
+beautifulsoup4==4.12.2
+Django==5.0.2
+django-bootstrap-v5==1.0.11
+soupsieve==2.5
+sqlparse==0.4.4
+tzdata==2023.4

BIN
static/favicon.png


+ 86 - 0
static/main/css/styles.css

@@ -0,0 +1,86 @@
+/* Basic reset */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+body {
+    font-family: 'Arial', sans-serif;
+    line-height: 1.6;
+    padding: 20px;
+    background-color: #f4f4f4;
+}
+
+header {
+    background: #333;
+    color: #fff;
+    padding-top: 30px;
+    min-height: 70px;
+    border-bottom: #0779e4 3px solid;
+}
+
+header a {
+    color: #fff;
+    text-decoration: none;
+    text-transform: uppercase;
+    margin: 0 15px;
+}
+
+header li {
+    display: inline;
+    padding: 0 20px;
+}
+
+header nav {
+    text-align: center;
+}
+
+main {
+    padding: 20px;
+}
+
+h2 {
+    color: #333;
+}
+
+table {
+    width: 100%;
+    margin-top: 20px;
+    border-collapse: collapse;
+}
+
+table, th, td {
+    border: 1px solid #bbbbbb;
+    padding: 10px;
+    text-align: left;
+}
+
+th {
+    background-color: #f8f8f8;
+}
+
+form {
+    background-color: #fff;
+    padding: 20px;
+    border-radius: 5px;
+}
+
+form p, form h3 {
+    margin: 15px 0;
+}
+
+button, input[type="submit"] {
+    background: #0d6efd;
+    color: #fff;
+    border: 0;
+    padding: 10px 20px;
+    cursor: pointer;
+    border-radius: 5px;
+}
+
+button:hover, input[type="submit"]:hover {
+    background: #0b5ed7;
+}
+
+/* Add any additional styles as needed */

+ 19 - 0
templates/base.html

@@ -0,0 +1,19 @@
+{% load static %}
+<!DOCTYPE html>
+<html lang="en" style="font-size:0.75em">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>{% block title %}Shift Manager{% endblock title %}</title>
+    <link rel="stylesheet" href="{% static 'main/css/styles.css' %}">
+    <link rel="icon"href="{% static 'favicon.png' %}">
+    {% load bootstrap5 %}
+    {% bootstrap_css %}
+    {% bootstrap_javascript %}
+</head>
+<body>
+        {% block content %}
+        <!-- Content goes here -->
+        {% endblock %}
+</body>
+</html>

+ 20 - 0
templates/base_pub.html

@@ -0,0 +1,20 @@
+{% load static %}
+<!DOCTYPE html>
+<html lang="en" data-bs-theme="dark" style="font-size:1.2em">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="refresh" content="120">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>{% block title %}Shift Manager{% endblock title %}</title>
+    <link rel="stylesheet" href="{% static 'roster/css/styles.css' %}">
+    <link rel="icon"href="{% static 'favicon.png' %}">
+    {% load bootstrap5 %}
+    {% bootstrap_css %}
+    {% bootstrap_javascript %}
+</head>
+<body  >
+        {% block content %}
+        <!-- Content goes here -->
+        {% endblock %}
+</body>
+</html>

+ 21 - 0
templates/main/create_event.html

@@ -0,0 +1,21 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Event erstellen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Event erstellen</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Event erstellen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 21 - 0
templates/main/create_helper.html

@@ -0,0 +1,21 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Helfer einstellen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Helfer einstellen</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Helfer einstellen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 21 - 0
templates/main/create_multiple_shifts.html

@@ -0,0 +1,21 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Mehrere Schichten erstellen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Mehrere Schichten erstellen</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Schichten erstellen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 21 - 0
templates/main/create_reinigung.html

@@ -0,0 +1,21 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Reinigungsauftrag erstellen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Reinigungsauftrag erstellen</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Event erstellen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 224 - 0
templates/main/current_week_shifts.html

@@ -0,0 +1,224 @@
+{% load static %}
+{% load custom_tags %}
+{% load bootstrap5 %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="refresh" content="120; URL=/current-week-shifts/" >
+    <title>Schichten der aktuellen Woche</title>
+    <link rel="icon" href="{% static 'favicon.png' %}">
+    {% bootstrap_css %}
+    <style>
+        body {
+            font-family: Tahoma, sans-serif;
+            margin: 0;
+            padding: 0;
+            font-size: 10px; /* Set the base font size */
+        }
+        .shift-none {
+            background-color: white !important;
+            text-align: center !important;
+            cursor: pointer !important;
+        }
+        .shift-vacation {
+            background-color: blue !important;
+            color: white !important;
+            text-align: center;
+        }
+        .shift-sick {
+            background-color: yellow !important;
+            text-align: center;
+        }
+        .shift-other {
+            background-color: gray !important;
+            color: white !important;
+            text-align: center;
+        }
+        .buttons-container {
+            display: flex;
+            justify-content: space-between;
+            padding: 10px;
+            background-color: #f8f9fa;
+            border-bottom: 1px solid #dee2e6;
+        }
+        .container-fluid {
+            padding-top: 10px; /* Reduce the padding to remove extra space */
+        }
+        a {
+            color: black;
+            text-decoration: none;
+        }
+        a:hover {
+            color: black;
+        }
+        .employee-name {
+            text-align: left;
+            font-weight: bold;
+            width: 150px; /* Set a fixed width for the employee column */
+        }
+        .day-name {
+            text-align: center;
+            width: 150px; /* Set a fixed width for the employee column */
+        }
+        .table th, .table td {
+            vertical-align: middle;
+            width: 100px; /* Set a fixed width for all other columns */
+            padding: 4px; /* Reduce padding to decrease row height */
+            line-height: 1.2; /* Adjust line height to decrease row height */
+        }
+        .custom-heading {
+            margin: 0; /* Remove margin to eliminate extra space */
+        }
+        @media print {
+            .buttons-container {
+                display: none; /* Hide the buttons when printing */
+            }
+        }
+    </style>
+</head>
+<body>
+      <div class="container-fluid">
+        <h4 class="custom-heading">Dienstplan für KW {{ calendar_week }}
+
+            <a href="?start_date={{ previous_week }}" class="btn btn-primary">Eine Woche zurück</a>
+            <a href="/current-week-shifts/" class="btn btn-primary">Aktuelle Woche</a>
+            <form method="post" action="{% url 'logout' %}" style="display: inline;">
+             {% csrf_token %}
+                <button type="submit" class="btn btn-secondary">Logout</button>
+            </form>
+            <a href="?start_date={{ next_week }}" class="btn btn-primary">Eine Woche vorwärts</a></h4>
+
+
+        <div class="table-responsive">
+            <table class="table table-bordered table-striped table-hover w-100">
+                <thead class="table-dark">
+                    <tr>
+                        <th class="employee-name">Veranstaltungen</th>
+                        {% for day, date in days_with_dates %}
+                        <th class="day-name">{{ day }} {{ date }}</th>
+                        {% endfor %}
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for location, events in events_by_location.items %}
+                    <tr>
+                        <td class="employee-name">{{ location.name }}</td>
+                        {% for day, date in days_with_dates %}
+                            {% with event=events|get_item:forloop.counter0 %}
+                                {% if event == None %}
+                                    {% if user.is_superuser %}
+                                        <td class="shift-none" onclick="window.location.href='{% url 'create_event' %}?date={{ date }}'"></td>
+                                    {% endif %}
+                                {% else %}
+                                    <td class="shift-none" onclick="window.location.href='{% url 'edit_event' pk=event.id %}'" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Name: {{ event.name }}<br> Location: {{ event.location }}<br> CVD: {{ event.cvd }}<br> CVT: {{ event.cvt }}<br> Info: {{ event.info }}">
+                                            {% if event.name %}
+                                                {{ event.name }}
+                                            {% else %}
+                                                F
+                                            {% endif %}
+                                    </td>
+                                {% endif %}
+                            {% endwith %}
+                        {% endfor %}
+                    </tr>
+                    {% endfor %}
+                    <tr>
+                        <td class="employee-name">Reinigung</td>
+                        {% for day in range_days %}
+                            {% if reinigungs_by_date|default_if_none:None %}
+                                {% if reinigungs_by_date|get_item:day %}
+                                    {% with reinigung=reinigungs_by_date|get_item:day %}
+                                        <td class="shift-none" onclick="window.location.href='{% url 'edit_reinigung' pk=reinigung.id %}'">
+                                            {{ reinigung.auftrag }}
+                                        </td>
+                                    {% endwith %}
+                                {% else %}
+                                    <td class="shift-none" onclick="window.location.href='{% url 'create_reinigung' %}?date={{ date }}'">--</td>
+                                {% endif %}
+                            {% else %}
+                                <td class="shift-none" onclick="window.location.href='{% url 'create_reinigung' %}?date={{ date }}'">--</td>
+                            {% endif %}
+                        {% endfor %}
+                    </tr>
+                    <tr>
+                        <td class="employee-name">Helfer</td>
+                        {% for day in range_days %}
+                            {% if helpers_by_date|default_if_none:None %}
+                                {% if helpers_by_date|get_item:day %}
+                                    {% with helper=helpers_by_date|get_item:day %}
+                                        <td class="shift-none" onclick="window.location.href='{% url 'edit_helper' pk=helper.id %}'">
+                                            {{ helper.ben }}/{{ helper.best }}
+                                        </td>
+                                    {% endwith %}
+                                {% else %}
+                                    <td class="shift-none" onclick="window.location.href='{% url 'create_helper' %}?date={{ date }}'">--</td>
+                                {% endif %}
+                            {% else %}
+                                <td class="shift-none" onclick="window.location.href='{% url 'create_helper' %}?date={{ date }}'">--</td>
+                            {% endif %}
+                        {% endfor %}
+                    </tr>
+
+
+
+                </tbody>
+                <tbody>
+                    <tr class="table-dark">
+                        <th class="employee-name">Mitarbeiter</th>
+                        {% for day, date in days_with_dates %}
+                        <th class="day-name">{{ day }} {{ date }}</th>
+                        {% endfor %}
+                    </tr>
+                    {% for employee, shifts in shifts_by_employee.items %}
+                    <tr>
+                        <td class="employee-name">{{ employee.name }}</td>
+                        {% for day, date in days_with_dates %}
+                            {% with shift=shifts|get_item:forloop.counter0 %}
+                                {% if shift == None %}
+                                    <td class="shift-none" onclick="window.location.href='{% url 'create_multiple_shifts' %}?date={{ date }}'">
+                                        F
+                                    </td>
+                                {% else %}
+                                    <td class="
+                                        {% if shift.start and shift.end %}
+                                            shift-none
+                                        {% elif shift.shifttype == 'U' %}
+                                            shift-vacation
+                                        {% elif shift.shifttype == 'K' %}
+                                            shift-sick
+                                        {% else %}
+                                            shift-other
+                                        {% endif %}
+                                        "
+                                        onclick="window.location.href='{% url 'edit_shift' pk=shift.id %}'"
+                                            {% if shift.info  %}
+                                                data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Info: {{ shift.info }}"
+                                            {% endif %}
+                                            >
+                                            {% if shift.start and shift.end %}
+                                                {{ shift.start|time:"H:i" }} - {{ shift.end|time:"H:i" }}
+                                            {% else %}
+                                                {{ shift.get_shifttype_display }}
+                                            {% endif %}
+                                    </td>
+                                {% endif %}
+                            {% endwith %}
+                        {% endfor %}
+                    </tr>
+                    {% endfor %}
+                </tbody>
+            </table>
+        </div>
+    </div>
+    {% bootstrap_javascript %}
+    <script>
+        // Initialize all tooltips on the page
+        var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
+        var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+            return new bootstrap.Tooltip(tooltipTriggerEl)
+        })
+    </script>
+</body>
+</html>

+ 22 - 0
templates/main/delete_event.html

@@ -0,0 +1,22 @@
+{% load bootstrap5 %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Event löschen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Event löschen</h2>
+        <p>Möchten Sie die VA {{ event.name }} am {{ event.date }} wirklich löschen?</p>
+        <form method="post">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 22 - 0
templates/main/delete_helper.html

@@ -0,0 +1,22 @@
+{% load bootstrap5 %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Helfer löschen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Helfer löschen</h2>
+        <p>Möchten Sie die Helferschicht wirklich löschen?</p>
+        <form method="post">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 22 - 0
templates/main/delete_reinigung.html

@@ -0,0 +1,22 @@
+{% load bootstrap5 %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Reinigungsauftrag löschen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Helfer löschen</h2>
+        <p>Möchten Sie den Reinigungsauftrag wirklich löschen?</p>
+        <form method="post">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 22 - 0
templates/main/delete_shift.html

@@ -0,0 +1,22 @@
+{% load bootstrap5 %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Schicht löschen</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Schicht löschen</h2>
+        <p>Möchten Sie die Schicht von {{ shift.employee.name }} am {{ shift.date }} wirklich löschen?</p>
+        <form method="post">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 25 - 0
templates/main/edit_event.html

@@ -0,0 +1,25 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Event bearbeiten</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Event bearbeiten</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Speichern</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+        <form method="post" action="{% url 'delete_event' pk=event.id %}" class="mt-3">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 25 - 0
templates/main/edit_helper.html

@@ -0,0 +1,25 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Helfer bearbeiten</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Helfer bearbeiten</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Speichern</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+        <form method="post" action="{% url 'delete_helper' pk=helper.id %}" class="mt-3">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 25 - 0
templates/main/edit_reinigung.html

@@ -0,0 +1,25 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Reinigungsauftrag bearbeiten</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Reinigungsauftrag bearbeiten</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Speichern</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+        <form method="post" action="{% url 'delete_reinigung' pk=reinigung.id %}" class="mt-3">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 25 - 0
templates/main/edit_shift.html

@@ -0,0 +1,25 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Schicht bearbeiten</title>
+    {% bootstrap_css %}
+</head>
+<body>
+    <div class="container">
+        <h2>Schicht bearbeiten</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Speichern</button>
+            <a href="{% url 'public' %}" class="btn btn-secondary">Abbrechen</a>
+        </form>
+        <form method="post" action="{% url 'delete_shift' pk=shift.id %}" class="mt-3">
+            {% csrf_token %}
+            <button type="submit" class="btn btn-danger">Löschen</button>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>

+ 234 - 0
templates/main/public.html

@@ -0,0 +1,234 @@
+{% load static %}
+{% load custom_tags %}
+{% load bootstrap5 %}
+
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="refresh" content="120; URL=/" >
+    <title>Schichten der aktuellen Woche</title>
+    <link rel="icon" href="{% static 'favicon.png' %}">
+    {% bootstrap_css %}
+    <style>
+        body {
+            font-family: Tahoma, sans-serif;
+            margin: 0;
+            padding: 0;
+            font-size: 10px; /* Set the base font size */
+        }
+        .shift-none {
+            background-color: white !important;
+            text-align: center !important;
+            {% if user.is_authenticated %}
+                cursor: pointer !important;
+            {% endif %}
+        }
+        .shift-vacation {
+            background-color: blue !important;
+            color: white !important;
+            text-align: center;
+        }
+        .shift-sick {
+            background-color: yellow !important;
+            text-align: center;
+        }
+        .shift-other {
+            background-color: gray !important;
+            color: white !important;
+            text-align: center;
+        }
+        .buttons-container {
+            display: flex;
+            justify-content: space-between;
+            padding: 10px;
+            background-color: #f8f9fa;
+            border-bottom: 1px solid #dee2e6;
+        }
+        .container-fluid {
+            padding-top: 10px; /* Reduce the padding to remove extra space */
+        }
+        a {
+            color: black;
+            text-decoration: none;
+        }
+        a:hover {
+            color: black;
+        }
+        .employee-name {
+            text-align: left;
+            font-weight: bold;
+            width: 150px; /* Set a fixed width for the employee column */
+        }
+        .day-name {
+            text-align: center;
+            width: 150px; /* Set a fixed width for the employee column */
+        }
+        .table th, .table td {
+            vertical-align: middle;
+            width: 100px; /* Set a fixed width for all other columns */
+            padding: 4px; /* Reduce padding to decrease row height */
+            line-height: 1.2; /* Adjust line height to decrease row height */
+        }
+        .custom-heading {
+            margin: 0; /* Remove margin to eliminate extra space */
+        }
+        @media print {
+            .btn {
+                display: none; /* Hide the buttons when printing */
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="container-fluid">
+        <table class="table">
+            <tr>
+                <td><a href="?start_date={{ previous_week }}" class="btn btn-primary text-start">Eine Woche zurück</a></td>
+                <td><a href="/" class="btn btn-primary">Aktuelle Woche</a></td>
+                <td><h4 class="custom-heading text-center">Dienstplan für KW {{ calendar_week }}</h4></td>
+                <td class="text-end">{% if user.is_authenticated %}
+                        <form method="post" action="{% url 'logout' %}" style="display: inline;">
+                            {% csrf_token %}
+                            <button type="submit" class="btn btn-secondary">Logout</button>
+                        </form>
+                    {% else %}
+                        <a href="/login/" class="btn btn-secondary">Login</a>
+                    {% endif %}</td>
+                <td class="text-end"><a href="?start_date={{ next_week }}" class="btn btn-primary">Eine Woche vorwärts</a></td>
+            </tr>
+        </table>
+        <div class="table-responsive">
+            <table class="table  table-bordered table-striped table-hover w-100">
+                <thead class="table-dark">
+                    <tr>
+                        <th class="employee-name">Veranstaltungen</th>
+                        {% for day, date in days_with_dates %}
+                        <th class="day-name">{{ day }} {{ date }}</th>
+                        {% endfor %}
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for location, events in events_by_location.items %}
+                    <tr>
+                        <td class="employee-name">{{ location.name }}</td>
+                        {% for day, date in days_with_dates %}
+                            {% with event=events|get_item:forloop.counter0 %}
+                                {% if event == None %}
+                                    {% if user.is_authenticated %}
+                                        <td class="shift-none" onclick="window.location.href='{% url 'create_event' %}?date={{ date }}'"></td>
+                                    {% endif %}
+                                {% else %}
+                                    <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'edit_event' pk=event.id %}'" {% endif %} data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Name: {{ event.name }}<br> Location: {{ event.location }}<br> Belegung: {{ event.belegung }}<br> PAX: {{ event.pax }}<br> CVD: {{ event.cvd }}<br> CVT: {{ event.cvt }}<br> Info: {{ event.info }}">
+                                            {% if event.name %}
+                                                {{ event.name }}
+                                            {% else %}
+                                                F
+                                            {% endif %}
+                                    </td>
+                                {% endif %}
+                            {% endwith %}
+                        {% endfor %}
+                    </tr>
+                    {% endfor %}
+                    <tr>
+                        <td class="employee-name">Reinigung</td>
+                        {% for day in range_days %}
+                            {% if reinigungs_by_date|default_if_none:None %}
+                                {% if reinigungs_by_date|get_item:day %}
+                                    {% with reinigung=reinigungs_by_date|get_item:day %}
+                                        <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'edit_reinigung' pk=reinigung.id %}'" {% endif %}>
+                                            {{ reinigung.auftrag }}
+                                        </td>
+                                    {% endwith %}
+                                {% else %}
+                                    <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'create_reinigung' %}?date={{ date }}'" {% endif %}>--</td>
+                                {% endif %}
+                            {% else %}
+                                <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'create_reinigung' %}?date={{ date }}'" {% endif %}>--</td>
+                            {% endif %}
+                        {% endfor %}
+                    </tr>
+                    <tr>
+                        <td class="employee-name">Helfer</td>
+                        {% for day in range_days %}
+                            {% if helpers_by_date|default_if_none:None %}
+                                {% if helpers_by_date|get_item:day %}
+                                    {% with helper=helpers_by_date|get_item:day %}
+                                        <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'edit_helper' pk=helper.id %}'" {% endif %}>
+                                            {{ helper.ben }}/{{ helper.best }}
+                                        </td>
+                                    {% endwith %}
+                                {% else %}
+                                    <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'create_helper' %}?date={{ date }}'" {% endif %}>--</td>
+                                {% endif %}
+                            {% else %}
+                                <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'create_helper' %}?date={{ date }}'" {% endif %}>--</td>
+                            {% endif %}
+                        {% endfor %}
+                    </tr>
+
+                </tbody>
+                <tbody>
+                    <tr class="table-dark">
+                        <th class="employee-name">Mitarbeiter</th>
+                        {% for day, date in days_with_dates %}
+                        <th class="day-name">{{ day }} {{ date }}</th>
+                        {% endfor %}
+                    </tr>
+                    {% for employee, shifts in shifts_by_employee.items %}
+                    <tr>
+                        <td class="employee-name">{{ employee.name }}</td>
+                        {% for day, date in days_with_dates %}
+                            {% with shift=shifts|get_item:forloop.counter0 %}
+                                {% if shift == None %}
+                                    <td class="shift-none" {% if user.is_authenticated %} onclick="window.location.href='{% url 'create_multiple_shifts' %}?date={{ date }}'"{% endif %}>
+                                        F
+                                    </td>
+                                {% else %}
+                                    <td class="
+                                        {% if shift.start and shift.end %}
+                                            shift-none
+                                        {% elif shift.shifttype == 'U' %}
+                                            shift-vacation
+                                        {% elif shift.shifttype == 'K' %}
+                                            shift-sick
+                                        {% else %}
+                                            shift-other
+                                        {% endif %}
+                                    "
+                                        {% if user.is_authenticated %}
+                                            onclick="window.location.href='{% url 'edit_shift' pk=shift.id %}'"
+                                        {% endif %}
+                                            {% if shift.info  %}
+                                                data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" title="Info: {{ shift.info }}"
+                                            {% endif %}
+                                    >
+                                            {% if shift.start and shift.end %}
+                                                {{ shift.start|time:"H:i" }} - {{ shift.end|time:"H:i" }}
+                                                {% if shift.shiftchef  %}
+                                                    {{ shift.shiftchef }}
+                                                {% endif %}
+                                            {% else %}
+                                                {{ shift.get_shifttype_display }}
+                                            {% endif %}
+                                    </td>
+                                {% endif %}
+                            {% endwith %}
+                        {% endfor %}
+                    </tr>
+                    {% endfor %}
+                </tbody>
+            </table>
+        </div>
+    </div>
+    {% bootstrap_javascript %}
+    <script>
+        // Initialize all tooltips on the page
+        var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
+        var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
+            return new bootstrap.Tooltip(tooltipTriggerEl)
+        })
+    </script>
+</body>
+</html>

+ 38 - 0
templates/registration/login.html

@@ -0,0 +1,38 @@
+{% load bootstrap5 %}
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <title>Login</title>
+    {% bootstrap_css %}
+    <style>
+        body {
+            font-family: Tahoma, sans-serif;
+            margin: 0;
+            padding: 0;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            background-color: #f8f9fa;
+        }
+        .login-container {
+            background: white;
+            padding: 20px;
+            border-radius: 8px;
+            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+        }
+    </style>
+</head>
+<body>
+    <div class="login-container">
+        <h2>Login</h2>
+        <form method="post">
+            {% csrf_token %}
+            {% bootstrap_form form %}
+            <button type="submit" class="btn btn-primary">Login</button>
+        </form>
+    </div>
+    {% bootstrap_javascript %}
+</body>
+</html>