diff --git a/crewpulse/settings.py b/crewpulse/settings.py index e2424b7..fb39cad 100644 --- a/crewpulse/settings.py +++ b/crewpulse/settings.py @@ -11,6 +11,10 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ """ from pathlib import Path +from import_export.formats.base_formats import CSV + +IMPORT_FORMATS = [CSV] +EXPORT_FORMATS = [CSV] # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -35,6 +39,7 @@ INSTALLED_APPS = [ #ADD_ONs 'jazzmin', 'phonenumber_field', + 'import_export', #Defaul 'django.contrib.admin', @@ -143,3 +148,36 @@ JAZZMIN_SETTINGS = { "site_logo": 'main/img/crewpulse_logo.png', "show_ui_builder": True, # Zeigt den UI-Builder im Admin-Bereich } + +JAZZMIN_UI_TWEAKS = { + "navbar_small_text": False, + "footer_small_text": False, + "body_small_text": True, + "brand_small_text": False, + "brand_colour": False, + "accent": "accent-warning", + "navbar": "navbar-dark", + "no_navbar_border": False, + "navbar_fixed": False, + "layout_boxed": False, + "footer_fixed": False, + "sidebar_fixed": False, + "sidebar": "sidebar-dark-olive", + "sidebar_nav_small_text": False, + "sidebar_disable_expand": False, + "sidebar_nav_child_indent": False, + "sidebar_nav_compact_style": False, + "sidebar_nav_legacy_style": False, + "sidebar_nav_flat_style": False, + "theme": "solar", + "dark_mode_theme": "solar", + "button_classes": { + "primary": "btn-primary", + "secondary": "btn-secondary", + "info": "btn-info", + "warning": "btn-warning", + "danger": "btn-danger", + "success": "btn-success" + }, + "actions_sticky_top": False +} diff --git a/main/admin.py b/main/admin.py index 03485a1..309b13b 100644 --- a/main/admin.py +++ b/main/admin.py @@ -1,39 +1,95 @@ +from django import forms from django.contrib import admin import main.models as MainDB from datetime import date, datetime +from django.utils.html import format_html +from import_export import resources +from import_export.admin import ExportMixin, ImportMixin, ImportExportModelAdmin + + +class contributorsResource(resources.ModelResource): + + class Meta: + model = MainDB.contributors # Register your models here. @admin.register(MainDB.contributors) -class MainDB_contributorsAdmin(admin.ModelAdmin): +# class DeinModellAdmin(ImportExportModelAdmin): +# pass # Damit hast du Import/Export-Funktionalität im Django Admin +class MainDB_contributorsAdmin(ExportMixin, ImportMixin, admin.ModelAdmin): - list_display = ('full_name', 'da_approval', 'teamleader', 'second_teamleader', 'gender', 'birthday_year', 'get_congregation', 'pioneer', 'get_deparment', 'mobilnumber', 'email') + resource_class = contributorsResource + + list_display = ('full_name', 'actual', 'da_approval', 'leader', 'tage_anzeige', 'gender', 'birthday_year', 'get_congregation', 'pioneer', 'get_deparment', 'mobilnumber', 'email') list_filter = ('gender', 'roles', 'deparment', 'da_approval') search_fields = ('name', 'firstname', 'congregation__title', 'deparment__title', 'mobilnumber', 'email') - - def get_congregation(self, obj): - # Gebe eine durch Komma getrennte Liste der Namen der contributors zurück - return ", ".join([str(congregation) for congregation in obj.congregation.all()]) - get_congregation.short_description = 'Versammlung' # Optional: Benennung der Spalte + def get_congregation(self, obj): + return obj.congregation + get_congregation.short_description = 'Versammlung' + get_congregation.admin_order_field = 'congregation__title' + def get_deparment(self, obj): # Gebe eine durch Komma getrennte Liste der Namen der contributors zurück return ", ".join([str(deparment) for deparment in obj.deparment.all()]) get_deparment.short_description = 'Abteilung' # Optional: Benennung der Spalte - # Methode, um den vollständigen Namen zu kombinieren - def full_name(self, obj): - return f"{obj.name}, {obj.firstname}" - full_name.short_description = 'Name' # Spaltenüberschrift im Admin ändern - # Methode, um nur das Jahr des Birthdays anzuzeigen def birthday_year(self, obj): - year = obj.birthday.year if obj.birthday else None + year = obj.birthday.year if obj.birthday != None else 0 return int(datetime.strftime(date.today(), "%Y")) - year birthday_year.short_description = 'Alter' + # Teamleiter ansicht + def leader(self, obj): + if obj.teamleader: + color = "green" + elif not obj.teamleader and obj.second_teamleader: + color = "gold" + else: + color = "gray" + + status = f'' + return format_html(status) + + # Verfügbarkeit anzeigen + def tage_anzeige(self, obj): + """ Kompakte Anzeige mit 2er-Gruppen in farbigen Punkten """ + tage_pairs = [ + ("MiV", "MiN"), + ("DoV", "DoN"), + ("FrV", "FrN"), + ("SaV", "SaN"), + ("SoV", "SoN"), + ("Abbau", None) # Extra2 hat keinen Partner, wird einzeln bewertet + ] + + symbols = [] + for tag1, tag2 in tage_pairs: + active1 = obj.tage & obj.TAGE[tag1] + active2 = obj.TAGE.get(tag2, 0) and obj.tage & obj.TAGE[tag2] if tag2 else None # Sicherstellen, dass None nicht bewertet wird + + if tag2 is None: # Falls nur ein Eintrag in der Gruppe existiert + color = "green" if active1 else "red" # Entweder Grün oder Rot + else: + if active1 and active2: + color = "green" # 🟢 beide aktiv + elif active1 or active2: + color = "gold" # 🟡 nur einer aktiv + else: + color = "red" # 🔴 beide inaktiv + + symbols.append(f'') + + return format_html(" ".join(symbols)) + admin.site.register(MainDB.department) -admin.site.register(MainDB.congregation) + +@admin.register(MainDB.congregation) +class MainDB_congregationAdmin(admin.ModelAdmin): + list_display = ('id', 'title') + admin.site.register(MainDB.role) \ No newline at end of file diff --git a/main/models.py b/main/models.py index d58d962..395f47b 100644 --- a/main/models.py +++ b/main/models.py @@ -1,6 +1,6 @@ from django.db import models from phonenumber_field.modelfields import PhoneNumberField - +from django.contrib import admin # Create your models here. class department(models.Model): @@ -48,41 +48,57 @@ class role(models.Model): def get_absolute_url(self): return reverse("role_detail", kwargs={"pk": self.pk}) - class contributors(models.Model): - DAY_OPTIONS = [ - ('MI', 'Mittwoch'), - ('DO', 'Donnerstag'), - ('FRV', 'Freitag vormittag'), - ('FRN', 'Freitag nachmittag'), - ('SAV', 'Samstag vormittag'), - ('SAN', 'Samstag nachmittag'), - ('SOV', 'Sonntag vormittag'), - ('SON', 'Sonntag nachmittag'), - ('SOA', 'Sonntag abbau'), - ('MO', 'Montag'), - ] + TAGE = { + "MiV": 1, + "MiN": 2, + "DoV": 4, + "DoN": 8, + "FrV": 16, + "FrN": 32, + "SaV": 64, + "SaN": 128, + "SoV": 256, + "SoN": 512, + "Abbau": 1024, + } name = models.CharField(("name"), max_length=50) firstname = models.CharField(("vorname"), max_length=50) - birthday = models.DateField(("geburtstag"), auto_now=False, auto_now_add=False) - congregation = models.ManyToManyField("main.congregation", verbose_name=("congregation"), null=True) - roles = models.ManyToManyField("main.role", verbose_name=("role"), null=True) - pioneer = models.BooleanField(("pionier")) + birthday = models.DateField(("geburtstag"), auto_now=False, auto_now_add=False, blank=True, null=True) + congregation = models.ForeignKey("main.congregation", verbose_name=("Versammlung"), on_delete=models.SET_NULL, null=True, blank=True) + roles = models.ManyToManyField("main.role", verbose_name=("role"), null=True, blank=True) + pioneer = models.BooleanField(("pionier"), default=False) gender = models.CharField("Geschlecht",max_length=20, choices=[("M", "Männlich"), ("W", "Weiblich")]) da_approval = models.BooleanField(("Freigabe"), default=False) - deparment = models.ManyToManyField("main.department", verbose_name=("department"), null=True, blank=True) - availability = models.CharField(("Verfügbarkeit"), max_length=50, choices=DAY_OPTIONS, blank=True) + deparment = models.ManyToManyField("main.department", verbose_name=("Abteilung"), null=True, blank=True) + tage = models.IntegerField(default=0) # Hier speichern wir die Bitmaske teamleader = models.BooleanField(("Leiter"), default=False) - second_teamleader = models.BooleanField(("V-Leiter"), default=False) + second_teamleader = models.BooleanField(("Gruppenleiter"), default=False) mobilnumber = PhoneNumberField(("Mobilnummer"), null=True, blank=True) email = models.EmailField(("E-Mail"), max_length=254, null=True, blank=True) + jwpub = models.EmailField(("JWPUB"), max_length=254, null=True, blank=True) + notes = models.CharField(("Bemerkung"), max_length=255, null=True, blank=True) + actual = models.BooleanField(("Aktuell"), default=False) class Meta: verbose_name = ("Helfer") verbose_name_plural = ("Helfer") + @property + @admin.display( + ordering="name", + description="Name, Vorname", + boolean=False, + ) + def full_name(self): + return self.name + ", " + self.firstname + + def get_tage_list(self): + """ Gibt eine Liste der aktiven Tage zurück """ + return [name for name, bit in self.TAGE.items() if self.tage & bit] + def __str__(self): return f'{self.name}, {self.firstname}'