diff --git a/AKModel/admin.py b/AKModel/admin.py index a9e7e6b28d6242b7708ba12203e9e37003436a1f..56833b20bfdd2471e0bb52fe0e94291981dbb79c 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -2,6 +2,8 @@ from django import forms from django.apps import apps from django.contrib import admin, messages from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter, action, display +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.models import User # pylint: disable=E5142 from django.db.models import Count, F from django.http import HttpResponseRedirect from django.shortcuts import render, redirect @@ -568,3 +570,41 @@ class DefaultSlotAdmin(EventTimezoneFormMixin, admin.ModelAdmin): list_display = ['start_simplified', 'end_simplified', 'event'] list_filter = ['event'] form = DefaultSlotAdminForm + + +# Define a new User admin +class UserAdmin(BaseUserAdmin): + """ + Admin interface for Users + Enhances the built-in UserAdmin with additional actions to activate and deactivate users and a custom selection + of displayed properties in overview list + """ + list_display = ["username", "email", "is_active", "is_staff", "is_superuser"] + actions = ['activate', 'deactivate'] + + @admin.action(description=_("Activate selected users")) + def activate(self, request, queryset): + """ + Bulk activate users + + :param request: HTTP request + :param queryset: queryset containing all users that should be activated + """ + queryset.update(is_active=True) + self.message_user(request, _("The selected users have been activated.")) + + @admin.action(description=_("Deactivate selected users")) + def deactivate(self, request, queryset): + """ + Bulk deactivate users + + :param request: HTTP request + :param queryset: queryset containing all users that should be deactivated + """ + queryset.update(is_active=False) + self.message_user(request, _("The selected users have been deactivated.")) + + +# Re-register UserAdmin +admin.site.unregister(User) +admin.site.register(User, UserAdmin) diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po index b480a7d2abffd93a34fe01cd00de202e590e865e..7b8d710abd0eadfd21b5d98876be2d5dd053db73 100644 --- a/AKModel/locale/de_DE/LC_MESSAGES/django.po +++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-25 22:33+0100\n" +"POT-Creation-Date: 2025-02-26 16:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: AKModel/admin.py:86 AKModel/admin.py:96 +#: AKModel/admin.py:88 AKModel/admin.py:98 #: AKModel/templates/admin/AKModel/event_wizard/activate.html:35 #: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:51 #: AKModel/templates/admin/AKModel/event_wizard/finish.html:21 @@ -21,69 +21,85 @@ msgstr "" msgid "Status" msgstr "Status" -#: AKModel/admin.py:98 +#: AKModel/admin.py:100 msgid "Toggle plan visibility" msgstr "Plansichtbarkeit ändern" -#: AKModel/admin.py:110 AKModel/admin.py:121 AKModel/views/manage.py:138 +#: AKModel/admin.py:112 AKModel/admin.py:123 AKModel/views/manage.py:138 msgid "Publish plan" msgstr "Plan veröffentlichen" -#: AKModel/admin.py:113 AKModel/admin.py:129 AKModel/views/manage.py:151 +#: AKModel/admin.py:115 AKModel/admin.py:131 AKModel/views/manage.py:151 msgid "Unpublish plan" msgstr "Plan verbergen" -#: AKModel/admin.py:168 AKModel/models.py:381 AKModel/models.py:707 +#: AKModel/admin.py:170 AKModel/models.py:381 AKModel/models.py:707 #: AKModel/templates/admin/AKModel/aks_by_user.html:12 #: AKModel/templates/admin/AKModel/status/event_aks.html:10 #: AKModel/views/manage.py:73 AKModel/views/status.py:102 msgid "AKs" msgstr "AKs" -#: AKModel/admin.py:234 +#: AKModel/admin.py:236 msgid "Wish" msgstr "AK-Wunsch" -#: AKModel/admin.py:240 +#: AKModel/admin.py:242 msgid "Is wish" msgstr "Ist ein Wunsch" -#: AKModel/admin.py:241 +#: AKModel/admin.py:243 msgid "Is not a wish" msgstr "Ist kein Wunsch" -#: AKModel/admin.py:301 +#: AKModel/admin.py:303 msgid "Export to wiki syntax" msgstr "In Wiki-Syntax exportieren" -#: AKModel/admin.py:318 +#: AKModel/admin.py:320 msgid "Cannot export AKs from more than one event at the same time." msgstr "Kann nicht AKs von mehreren Events zur selben Zeit exportieren." -#: AKModel/admin.py:333 AKModel/views/ak.py:99 +#: AKModel/admin.py:335 AKModel/views/ak.py:99 msgid "Reset interest in AKs" msgstr "Interesse an AKs zurücksetzen" -#: AKModel/admin.py:343 AKModel/views/ak.py:114 +#: AKModel/admin.py:345 AKModel/views/ak.py:114 msgid "Reset AKs' interest counters" msgstr "Interessenszähler der AKs zurücksetzen" -#: AKModel/admin.py:442 AKModel/admin.py:456 +#: AKModel/admin.py:444 AKModel/admin.py:458 msgid "AK Details" msgstr "AK-Details" -#: AKModel/admin.py:518 AKModel/views/manage.py:99 +#: AKModel/admin.py:520 AKModel/views/manage.py:99 msgid "Mark Constraint Violations as manually resolved" msgstr "Markiere Constraintverletzungen als manuell behoben" -#: AKModel/admin.py:527 AKModel/views/manage.py:112 +#: AKModel/admin.py:529 AKModel/views/manage.py:112 msgid "Set Constraint Violations to level \"violation\"" msgstr "Constraintverletzungen auf Level \"Violation\" setzen" -#: AKModel/admin.py:536 AKModel/views/manage.py:125 +#: AKModel/admin.py:538 AKModel/views/manage.py:125 msgid "Set Constraint Violations to level \"warning\"" msgstr "Constraintverletzungen auf Level \"Warning\" setzen" +#: AKModel/admin.py:580 +msgid "Activate selected users" +msgstr "Ausgewählte Benutzer*innen aktivieren" + +#: AKModel/admin.py:583 +msgid "The selected users have been activated." +msgstr "Benutzer*innen aktiviert" + +#: AKModel/admin.py:585 +msgid "Deactivate selected users" +msgstr "Ausgewählte Benutzer*innen deaktivieren" + +#: AKModel/admin.py:588 +msgid "The selected users have been deactivated." +msgstr "Benutzer*innen deaktiviert" + #: AKModel/availability/forms.py:25 AKModel/availability/models.py:271 msgid "Availability" msgstr "Verfügbarkeit" diff --git a/AKModel/models.py b/AKModel/models.py index 0890e53602abed42f8d544223ddab98e323e3425..26ed0f2142a94b8435b91647c161617face6d433 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -408,8 +408,22 @@ class AK(models.Model): detail_string += f"\n{_('Requirements')}: {', '.join(str(r) for r in self.requirements.all())}" if self.types.count() > 0: detail_string += f"\n{_('Types')}: {', '.join(str(r) for r in self.types.all())}" + + # Find conflicts + # (both directions, those specified for this AK and those were this AK was specified as conflict) + # Deduplicate and order list alphabetically + conflicts = set() if self.conflicts.count() > 0: - detail_string += f"\n{_('Conflicts')}: {', '.join(str(c) for c in self.conflicts.all())}" + for c in self.conflicts.all(): + conflicts.add(str(c)) + if self.conflict.count() > 0: + for c in self.conflict.all(): + conflicts.add(str(c)) + if len(conflicts) > 0: + conflicts = list(conflicts) + conflicts.sort() + detail_string += f"\n{_('Conflicts')}: {', '.join(conflicts)}" + if self.prerequisites.count() > 0: detail_string += f"\n{_('Prerequisites')}: {', '.join(str(p) for p in self.prerequisites.all())}" detail_string += f"\n{_('Availabilities')}: \n{availabilities}" diff --git a/AKScheduling/forms.py b/AKScheduling/forms.py index 47a19f7857c3c9e68d2c6e2b59cecf0d79fbcebf..5eff1fcaa65515ac2f14ccc6a5b3c0eaef151b61 100644 --- a/AKScheduling/forms.py +++ b/AKScheduling/forms.py @@ -24,7 +24,8 @@ class AKAddSlotForm(forms.Form): start = forms.CharField(label=_("Start"), disabled=True) end = forms.CharField(label=_("End"), disabled=True) duration = forms.CharField(label=_("Duration"), disabled=True) - room = forms.IntegerField(label=_("Room"), disabled=True) + room = forms.IntegerField(label=_("Room"), disabled=True, widget=forms.HiddenInput()) + room_name = forms.CharField(label=_("Room"), disabled=True) def __init__(self, event): super().__init__() diff --git a/AKScheduling/templates/admin/AKScheduling/scheduling.html b/AKScheduling/templates/admin/AKScheduling/scheduling.html index 8d592aecba3cab360c8fb944715ffe0ec36fecf8..872534551d0daf3aa457860ef4246dbb4b241c7e 100644 --- a/AKScheduling/templates/admin/AKScheduling/scheduling.html +++ b/AKScheduling/templates/admin/AKScheduling/scheduling.html @@ -157,6 +157,7 @@ $('#id_start').val(info.startStr); $('#id_end').val(info.endStr); $('#id_room').val(info.resource._resource.id); + $('#id_room_name').val(info.resource._resource.title); $('#id_duration').val(Math.abs(info.end-info.start)/1000/60/60); $('#id_ak').val(""); $('#newAKSlotModal').modal('show'); @@ -354,9 +355,11 @@ <h5 class="mt-2">{{ track_slots.grouper }}</h5> {% endif %} {% for slot in track_slots.list %} - <div class="unscheduled-slot badge" style='background-color: {{ slot.ak.category.color }}' - data-event='{ "title": "{{ slot.ak.short_name }}", "duration": {"hours": "{{ slot.duration|unlocalize }}"}, "constraint": "roomAvailable", "description": "{{ slot.ak.details | escapejs }}", "slotID": "{{ slot.pk }}", "backgroundColor": "{{ slot.ak.category.color }}", "url": "{% url "admin:AKModel_akslot_change" slot.pk %}"}' data-details="{{ slot.ak.details }}">{{ slot.ak.short_name }} + <div class="unscheduled-slot badge" style='background-color: {% with slot.ak.category.color as color %} {% if color %}{{ color }}{% else %}#000000;{% endif %}{% endwith %}' + {% with slot.ak.details as details %} + data-event='{ "title": "{{ slot.ak.short_name }}", "duration": {"hours": "{{ slot.duration|unlocalize }}"}, "constraint": "roomAvailable", "description": "{{ details | escapejs }}", "slotID": "{{ slot.pk }}", "backgroundColor": "{{ slot.ak.category.color }}", "url": "{% url "admin:AKModel_akslot_change" slot.pk %}"}' data-details="{{ details }}">{{ slot.ak.short_name }} ({{ slot.duration }} h)<br>{{ slot.ak.owners_list }} + {% endwith %} </div> {% endfor %} {% endfor %} diff --git a/AKScheduling/templates/admin/AKScheduling/special_attention.html b/AKScheduling/templates/admin/AKScheduling/special_attention.html index 7591cdb5143751a7990f83decf555fb3673e4700..c5a29f037e97e5a382607485889645328cec130f 100644 --- a/AKScheduling/templates/admin/AKScheduling/special_attention.html +++ b/AKScheduling/templates/admin/AKScheduling/special_attention.html @@ -13,14 +13,19 @@ {% block content %} <h4 class="mt-4 mb-4">{% trans "AKs with public notes" %}</h4> {% for ak in aks_with_comment %} - <a href="{{ ak.edit_url }}">{{ ak }}</a><br>{{ ak.notes }}<br><br> + <a href="{{ ak.detail_url }}">{{ ak }}</a> + <a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a> + <a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br> + {{ ak.notes }}<br><br> {% empty %} - {% endfor %} <h4 class="mt-4 mb-4">{% trans "AKs without availabilities" %}</h4> {% for ak in aks_without_availabilities %} - <a href="{{ ak.edit_url }}">{{ ak }}</a><br> + <a href="{{ ak.detail_url }}">{{ ak }}</a> + <a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a> + <a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br> {% empty %} -<br> {% endfor %} @@ -30,7 +35,10 @@ <h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4> {% for ak in ak_wishes_with_slots %} - <a href="{{ ak.detail_url }}">{{ ak }}</a> <a href="{% url "admin:AKModel_akslot_changelist" %}?ak={{ ak.pk }}">({{ ak.akslot__count }})</a><br> + <a href="{% url "admin:AKModel_akslot_changelist" %}?ak={{ ak.pk }}">({{ ak.akslot__count }})</a> + <a href="{{ ak.detail_url }}">{{ ak }}</a> + <a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a> + <a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br> {% empty %} -<br> {% endfor %} @@ -39,7 +47,9 @@ <h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4> {% for ak in aks_without_slots %} - <a href="{{ ak.detail_url }}">{{ ak }}</a><br> + <a href="{{ ak.detail_url }}">{{ ak }}</a> + <a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a> + <a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br> {% empty %} -<br> {% endfor %} diff --git a/AKScheduling/views.py b/AKScheduling/views.py index c7ecd4fc3d419c00857d6f53d4e8abc1893f61c6..d7533f3d4b0eec0b6bef82cf1a3159216a62cf9d 100644 --- a/AKScheduling/views.py +++ b/AKScheduling/views.py @@ -41,7 +41,9 @@ class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): context_object_name = "slots_unscheduled" def get_queryset(self): - return super().get_queryset().filter(start__isnull=True).select_related('event', 'ak').order_by('ak__track') + return super().get_queryset().filter(start__isnull=True).select_related('event', 'ak', 'ak__track', + 'ak__category').prefetch_related('ak__types', 'ak__owners', 'ak__conflicts', 'ak__prerequisites', + 'ak__requirements', 'ak__conflict').order_by('ak__track', 'ak') def get_context_data(self, *, object_list=None, **kwargs): context = super().get_context_data(object_list=object_list, **kwargs)