diff --git a/AKDashboard/tests.py b/AKDashboard/tests.py index 6253f8a176171159c5fde612bddbebf1d506d318..0cd3e62690f58a6d1d76487f19bf17a10a6a5c83 100644 --- a/AKDashboard/tests.py +++ b/AKDashboard/tests.py @@ -1,11 +1,12 @@ import zoneinfo + from django.apps import apps -from django.test import TestCase, override_settings +from django.test import override_settings, TestCase from django.urls import reverse from django.utils.timezone import now from AKDashboard.models import DashboardButton -from AKModel.models import Event, AK, AKCategory +from AKModel.models import AK, AKCategory, Event from AKModel.tests import BasicViewTests @@ -13,6 +14,7 @@ class DashboardTests(TestCase): """ Specific Dashboard Tests """ + @classmethod def setUpTestData(cls): """ @@ -20,17 +22,17 @@ class DashboardTests(TestCase): """ super().setUpTestData() cls.event = Event.objects.create( - name="Dashboard Test Event", - slug="dashboardtest", - timezone=zoneinfo.ZoneInfo("Europe/Berlin"), - start=now(), - end=now(), - active=True, - plan_hidden=False, + name="Dashboard Test Event", + slug="dashboardtest", + timezone=zoneinfo.ZoneInfo("Europe/Berlin"), + start=now(), + end=now(), + active=True, + plan_hidden=False, ) cls.default_category = AKCategory.objects.create( - name="Test Category", - event=cls.event, + name="Test Category", + event=cls.event, ) def test_dashboard_view(self): @@ -62,12 +64,12 @@ class DashboardTests(TestCase): # History should be empty response = self.client.get(url) - self.assertQuerysetEqual(response.context["recent_changes"], []) + self.assertQuerySetEqual(response.context["recent_changes"], []) AK.objects.create( - name="Test AK", - category=self.default_category, - event=self.event, + name="Test AK", + category=self.default_category, + event=self.event, ) # History should now contain one AK (Test AK) @@ -154,8 +156,8 @@ class DashboardTests(TestCase): self.assertNotContains(response, "Dashboard Button Test") DashboardButton.objects.create( - text="Dashboard Button Test", - event=self.event + text="Dashboard Button Test", + event=self.event ) response = self.client.get(url_event_dashboard) diff --git a/AKModel/models.py b/AKModel/models.py index 92925a1cdbf3bcf52297284daeebe0a1c1bcba41..aca3ee91c25ef7677f005b4deec9b99c2d22379e 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -1,12 +1,11 @@ import itertools -from datetime import timedelta +from datetime import datetime, timedelta -from django.db import models from django.apps import apps +from django.db import models from django.db.models import Count from django.urls import reverse_lazy from django.utils import timezone -from django.utils.datetime_safe import datetime from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords @@ -32,8 +31,8 @@ class Event(models.Model): help_text=_('When should AKs with intention to submit a resolution be done?')) interest_start = models.DateTimeField(verbose_name=_('Interest Window Start'), blank=True, null=True, - help_text= - _('Opening time for expression of interest. When left blank, no interest indication will be possible.')) + help_text= + _('Opening time for expression of interest. When left blank, no interest indication will be possible.')) interest_end = models.DateTimeField(verbose_name=_('Interest Window End'), blank=True, null=True, help_text=_('Closing time for expression of interest.')) @@ -45,7 +44,7 @@ class Event(models.Model): plan_hidden = models.BooleanField(verbose_name=_('Plan Hidden'), help_text=_('Hides plan for non-staff users'), default=True) plan_published_at = models.DateTimeField(verbose_name=_('Plan published at'), blank=True, null=True, - help_text=_('Timestamp at which the plan was published')) + help_text=_('Timestamp at which the plan was published')) base_url = models.URLField(verbose_name=_("Base URL"), help_text=_("Prefix for wiki link construction"), blank=True) wiki_export_template_name = models.CharField(verbose_name=_("Wiki Export Template Name"), blank=True, max_length=50) @@ -53,8 +52,8 @@ class Event(models.Model): help_text=_('Default length in hours that is assumed for AKs in this event.')) contact_email = models.EmailField(verbose_name=_("Contact email address"), blank=True, - help_text=_("An email address that is displayed on every page " - "and can be used for all kinds of questions")) + help_text=_("An email address that is displayed on every page " + "and can be used for all kinds of questions")) class Meta: verbose_name = _('Event') @@ -85,7 +84,7 @@ class Event(models.Model): event = Event.objects.filter(active=True).order_by('start').first() # No active event? Return the next event taking place if event is None: - event = Event.objects.order_by('start').filter(start__gt=datetime.now()).first() + event = Event.objects.order_by('start').filter(start__gt=datetime.now().astimezone()).first() return event def get_categories_with_aks(self, wishes_seperately=False, @@ -246,7 +245,7 @@ class AKCategory(models.Model): help_text=_("Short description of this AK Category")) present_by_default = models.BooleanField(blank=True, default=True, verbose_name=_("Present by default"), help_text=_("Present AKs of this category by default " - "if AK owner did not specify whether this AK should be presented?")) + "if AK owner did not specify whether this AK should be presented?")) event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'), help_text=_('Associated event')) @@ -343,7 +342,7 @@ class AK(models.Model): category = models.ForeignKey(to=AKCategory, on_delete=models.PROTECT, verbose_name=_('Category'), help_text=_('Category of the AK')) types = models.ManyToManyField(to=AKType, blank=True, verbose_name=_('Types'), - help_text=_("This AK is")) + help_text=_("This AK is")) track = models.ForeignKey(to=AKTrack, blank=True, on_delete=models.SET_NULL, null=True, verbose_name=_('Track'), help_text=_('Track the AK belongs to')) @@ -361,8 +360,8 @@ class AK(models.Model): help_text=_('AKs that should precede this AK in the schedule')) notes = models.TextField(blank=True, verbose_name=_('Organizational Notes'), help_text=_( - 'Notes to organizers. These are public. For private notes, please use the button for private messages ' - 'on the detail page of this AK (after creation/editing).')) + 'Notes to organizers. These are public. For private notes, please use the button for private messages ' + 'on the detail page of this AK (after creation/editing).')) interest = models.IntegerField(default=-1, verbose_name=_('Interest'), help_text=_('Expected number of people')) interest_counter = models.IntegerField(default=0, verbose_name=_('Interest Counter'), @@ -678,6 +677,7 @@ class ConstraintViolation(models.Model): Depending on the type, different fields (references to other models) will be filled. Each violation should always be related to an event and at least on other instance of a causing entity """ + class Meta: verbose_name = _('Constraint Violation') verbose_name_plural = _('Constraint Violations') @@ -694,7 +694,7 @@ class ConstraintViolation(models.Model): AK_CONFLICT_COLLISION = 'acc', _('AK Slot is scheduled at the same time as an AK listed as a conflict') AK_BEFORE_PREREQUISITE = 'abp', _('AK Slot is scheduled before an AK listed as a prerequisite') AK_AFTER_RESODEADLINE = 'aar', _( - 'AK Slot for AK with intention to submit a resolution is scheduled after resolution deadline') + 'AK Slot for AK with intention to submit a resolution is scheduled after resolution deadline') AK_CATEGORY_MISMATCH = 'acm', _('AK Slot in a category is outside that categories availabilities') AK_SLOT_COLLISION = 'asc', _('Two AK Slots for the same AK scheduled at the same time') ROOM_CAPACITY_EXCEEDED = 'rce', _('Room does not have enough space for interest in scheduled AK Slot') @@ -895,6 +895,7 @@ class DefaultSlot(models.Model): Model representing a default slot, i.e., a prefered slot to use for typical AKs in the schedule to guarantee enough breaks etc. """ + class Meta: verbose_name = _('Default Slot') verbose_name_plural = _('Default Slots') @@ -907,7 +908,8 @@ class DefaultSlot(models.Model): help_text=_('Associated event')) primary_categories = models.ManyToManyField(to=AKCategory, verbose_name=_('Primary categories'), blank=True, - help_text=_('Categories that should be assigned to this slot primarily')) + help_text=_( + 'Categories that should be assigned to this slot primarily')) @property def start_simplified(self) -> str: diff --git a/AKPlan/views.py b/AKPlan/views.py index 4123256e0ceb18c0da87526deaea1a0b46b91b39..a3c240a9a46a32937307f8a7381c14aa3b076c4f 100644 --- a/AKPlan/views.py +++ b/AKPlan/views.py @@ -1,13 +1,12 @@ -from datetime import timedelta +from datetime import datetime, timedelta from django.conf import settings from django.shortcuts import redirect from django.urls import reverse_lazy -from django.utils.datetime_safe import datetime -from django.views.generic import ListView, DetailView +from django.views.generic import DetailView, ListView -from AKModel.models import AKSlot, Room, AKTrack from AKModel.metaviews.admin import FilterByEventSlugMixin +from AKModel.models import AKSlot, AKTrack, Room class PlanIndexView(FilterByEventSlugMixin, ListView): @@ -152,7 +151,7 @@ class PlanTrackView(FilterByEventSlugMixin, DetailView): context = super().get_context_data(object_list=object_list, **kwargs) # Restrict AKSlot list to given track # while joining AK, room and category information to reduce the amount of necessary SQL queries - context["slots"] = AKSlot.objects.\ - filter(event=self.event, ak__track=context['track']).\ + context["slots"] = AKSlot.objects. \ + filter(event=self.event, ak__track=context['track']). \ select_related('ak', 'room', 'ak__category') return context diff --git a/AKSubmission/api.py b/AKSubmission/api.py index 39c3fd460a404735e66d690746c8f65e892b1137..2e82f4e080b1e14c668c048b1ccc3fc0672ec715 100644 --- a/AKSubmission/api.py +++ b/AKSubmission/api.py @@ -1,7 +1,8 @@ +from datetime import datetime + from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response -from django.utils.datetime_safe import datetime from AKModel.models import AK diff --git a/AKSubmission/tests.py b/AKSubmission/tests.py index 2e79be6d7fa4be5de4429f07fe3cc72be2c3e7d0..98e4ada8cc46a4cf4e7f951e46faf853cffaaa13 100644 --- a/AKSubmission/tests.py +++ b/AKSubmission/tests.py @@ -1,8 +1,7 @@ -from datetime import timedelta +from datetime import datetime, timedelta from django.test import TestCase from django.urls import reverse_lazy -from django.utils.datetime_safe import datetime from AKModel.models import AK, AKSlot, Event from AKModel.tests import BasicViewTests @@ -47,8 +46,8 @@ class ModelViewTests(BasicViewTests, TestCase): 'expected_message': "AK successfully updated"}, {'view': 'akslot_edit', 'target_view': 'ak_detail', 'kwargs': {'event_slug': 'kif42', 'pk': 5}, 'target_kwargs': {'event_slug': 'kif42', 'pk': 1}, 'expected_message': "AK Slot successfully updated"}, - {'view': 'akowner_edit', 'target_view': 'submission_overview', 'kwargs': {'event_slug': 'kif42', 'slug': 'a'}, - 'target_kwargs': {'event_slug': 'kif42'}, 'expected_message': "Person Info successfully updated"}, + {'view': 'akowner_edit', 'target_view': 'submission_overview', 'kwargs': {'event_slug': 'kif42', 'slug': 'a'}, + 'target_kwargs': {'event_slug': 'kif42'}, 'expected_message': "Person Info successfully updated"}, ] def test_akslot_edit_delete_prevention(self): @@ -147,7 +146,7 @@ class ModelViewTests(BasicViewTests, TestCase): add_redirect_url = reverse_lazy(f"{self.APP_NAME}:submit_ak", kwargs={'event_slug': 'kif42', 'owner_slug': 'a'}) response = self.client.post(select_url, {'owner_id': 1}) self.assertRedirects(response, add_redirect_url, status_code=302, target_status_code=200, - msg_prefix=f"Dispatch redirect to ak submission page failed (should go to {add_redirect_url})") + msg_prefix=f"Dispatch redirect to ak submission page failed (should go to {add_redirect_url})") def test_orga_message_submission(self): """ @@ -201,7 +200,7 @@ class ModelViewTests(BasicViewTests, TestCase): # Test indication outside of indication window -> HTTP 403, counter not increased response = self.client.post(interest_api_url) self.assertEqual(response.status_code, 403, - "API end point still reachable even though interest indication window ended ({interest_api_url})") + "API end point still reachable even though interest indication window ended ({interest_api_url})") self.assertEqual(AK.objects.get(pk=1).interest_counter, ak_interest_counter + 1, "Counter was increased even though interest indication window ended") @@ -243,7 +242,7 @@ class ModelViewTests(BasicViewTests, TestCase): Test visibility of requirements field in submission form """ event = Event.get_by_slug('kif42') - form = AKSubmissionForm(data={'name': 'Test AK', 'event': event}, instance=None, initial={"event":event}) + form = AKSubmissionForm(data={'name': 'Test AK', 'event': event}, instance=None, initial={"event": event}) self.assertIn('requirements', form.fields, msg="Requirements field not present in form even though event has requirements") @@ -260,7 +259,7 @@ class ModelViewTests(BasicViewTests, TestCase): Test visibility of types field in submission form """ event = Event.get_by_slug('kif42') - form = AKSubmissionForm(data={'name': 'Test AK', 'event': event}, instance=None, initial={"event":event}) + form = AKSubmissionForm(data={'name': 'Test AK', 'event': event}, instance=None, initial={"event": event}) self.assertIn('types', form.fields, msg="Requirements field not present in form even though event has requirements") diff --git a/AKSubmission/views.py b/AKSubmission/views.py index 5263b875ea21399a5f98d37914403c2f674ec391..171baed823a2bf183024ed1f44922605c7a7997d 100644 --- a/AKSubmission/views.py +++ b/AKSubmission/views.py @@ -1,6 +1,6 @@ -from datetime import timedelta -from math import floor from abc import ABC, abstractmethod +from datetime import datetime, timedelta +from math import floor from django.apps import apps from django.conf import settings @@ -8,19 +8,17 @@ from django.contrib import messages from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy -from django.utils.datetime_safe import datetime from django.utils.translation import gettext_lazy as _ from django.views import View -from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView +from django.views.generic import CreateView, DeleteView, DetailView, ListView, TemplateView, UpdateView from AKModel.availability.models import Availability from AKModel.metaviews import status_manager -from AKModel.metaviews.status import TemplateStatusWidget -from AKModel.models import AK, AKCategory, AKOwner, AKSlot, AKTrack, AKOrgaMessage from AKModel.metaviews.admin import EventSlugMixin, FilterByEventSlugMixin +from AKModel.metaviews.status import TemplateStatusWidget +from AKModel.models import AK, AKCategory, AKOrgaMessage, AKOwner, AKSlot, AKTrack from AKSubmission.api import ak_interest_indication_active -from AKSubmission.forms import AKWishForm, AKOwnerForm, AKSubmissionForm, AKDurationForm, AKOrgaMessageForm, \ - AKForm +from AKSubmission.forms import AKDurationForm, AKForm, AKOrgaMessageForm, AKOwnerForm, AKSubmissionForm, AKWishForm class SubmissionErrorNotConfiguredView(EventSlugMixin, TemplateView): @@ -47,7 +45,7 @@ class AKOverviewView(FilterByEventSlugMixin, ListView): template_name = "AKSubmission/ak_overview.html" wishes_as_category = False - def filter_aks(self, context, category): # pylint: disable=unused-argument + def filter_aks(self, context, category): # pylint: disable=unused-argument """ Filter which AKs to display based on the given context and category @@ -73,7 +71,7 @@ class AKOverviewView(FilterByEventSlugMixin, ListView): """ return context["categories_with_aks"][0][0].name - def get_table_title(self, context): # pylint: disable=unused-argument + def get_table_title(self, context): # pylint: disable=unused-argument """ Specify the title above the AK list/table in this view @@ -91,7 +89,7 @@ class AKOverviewView(FilterByEventSlugMixin, ListView): redirect to error page if necessary (see :class:`SubmissionErrorNotConfiguredView`) """ self._load_event() - self.object_list = self.get_queryset() # pylint: disable=attribute-defined-outside-init + self.object_list = self.get_queryset() # pylint: disable=attribute-defined-outside-init # No categories yet? Redirect to configuration error page if self.object_list.count() == 0: @@ -124,7 +122,7 @@ class AKOverviewView(FilterByEventSlugMixin, ListView): if self.wishes_as_category: categories_with_aks.append( - (AKCategory(name=_("Wishes"), pk=0, description=_("AKs one would like to have")), ak_wishes)) + (AKCategory(name=_("Wishes"), pk=0, description=_("AKs one would like to have")), ak_wishes)) context["categories_with_aks"] = categories_with_aks context["active_category"] = self.get_active_category_name(context) @@ -183,10 +181,12 @@ class AKListByCategoryView(AKOverviewView): This view inherits from :class:`AKOverviewView`, but produces only one list instead of a tabbed one. """ + def dispatch(self, request, *args, **kwargs): # Override dispatching # Needed to handle the checking whether the category exists - self.category = get_object_or_404(AKCategory, pk=kwargs['category_pk']) # pylint: disable=attribute-defined-outside-init,line-too-long + self.category = get_object_or_404(AKCategory, pk=kwargs[ + 'category_pk']) # pylint: disable=attribute-defined-outside-init,line-too-long return super().dispatch(request, *args, **kwargs) def get_active_category_name(self, context): @@ -209,11 +209,12 @@ class AKListByTrackView(AKOverviewView): This view inherits from :class:`AKOverviewView` and there will be one list per category -- but only AKs of a certain given track will be included in them. """ + def dispatch(self, request, *args, **kwargs): # Override dispatching # Needed to handle the checking whether the track exists - self.track = get_object_or_404(AKTrack, pk=kwargs['track_pk']) # pylint: disable=attribute-defined-outside-init + self.track = get_object_or_404(AKTrack, pk=kwargs['track_pk']) # pylint: disable=attribute-defined-outside-init return super().dispatch(request, *args, **kwargs) def filter_aks(self, context, category): @@ -292,6 +293,7 @@ class EventInactiveRedirectMixin: Will add a message explaining why the action was not performed to the user and then redirect to start page of the submission component """ + def get_error_message(self): """ Error message to display after redirect (can be adjusted by this method) @@ -351,6 +353,7 @@ class AKSubmissionView(AKAndAKWishSubmissionView): Extends :class:`AKAndAKWishSubmissionView` """ + def get_initial(self): # Load initial values for the form # Used to directly add the first owner and the event this AK will belong to @@ -500,7 +503,6 @@ class AKOwnerDispatchView(ABC, EventSlugMixin, View): :rtype: HttpResponseRedirect """ - def post(self, request, *args, **kwargs): # This view is solely meant to handle POST requests # Perform dispatching based on the submitted owner_id diff --git a/requirements.txt b/requirements.txt index 915d24967316726b974d9171eb5354110f8d741a..05b06c2c9fa4b91208cc8a96b96a07120672f259 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==4.2.19 +Django==5.1.6 django-bootstrap5==24.2 fontawesomefree==6.5.1 # Makes static files (css, fonts) available locally django-fontawesome-6==1.0.0.0 # Provides an icon field for models and forms as well as handy shortcuts to render icons