Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • koma/feature/preference-polling-form
  • komasolver
  • main
  • renovate/django_csp-4.x
4 results

Target

Select target project
  • konstantin/akplanning
  • matedealer/akplanning
  • kif/akplanning
  • mirco/akplanning
  • lordofthevoid/akplanning
  • voidptr/akplanning
  • xayomer/akplanning-fork
  • mollux/akplanning
  • neumantm/akplanning
  • mmarx/akplanning
  • nerf/akplanning
  • felix_bonn/akplanning
  • sebastian.uschmann/akplanning
13 results
Select Git revision
  • ak-import
  • feature/clear-schedule-button
  • feature/export-filtering
  • feature/json-export-via-rest-framework
  • feature/json-schedule-import-tests
  • feature/preference-polling-form
  • fix/add-room-import-only-once
  • main
  • renovate/django-5.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-simple-history-3.x
  • renovate/mysqlclient-2.x
12 results
Show changes
Showing
with 1049 additions and 25 deletions
from django.test import TestCase
from AKModel.tests import BasicViewTests
from AKModel.tests.test_views import BasicViewTests
class PlanViewTests(BasicViewTests, TestCase):
......
from datetime import timedelta
import json
from datetime import datetime, timedelta
from django.conf import settings
from django.contrib import messages
from django.db.models import Q
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 django.utils.translation import gettext_lazy as _
from AKModel.models import AKSlot, Room, AKTrack
from AKModel.metaviews.admin import FilterByEventSlugMixin
from AKModel.models import AKSlot, AKTrack, Room, AKType
class PlanIndexView(FilterByEventSlugMixin, ListView):
......@@ -20,10 +23,69 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
template_name = "AKPlan/plan_index.html"
context_object_name = "akslots"
ordering = "start"
types_filter = None
query_string = ""
def get(self, request, *args, **kwargs):
if 'types' in request.GET:
try:
# Initialize types filter, has to be done here such that it is not reused across requests
self.types_filter = {
"yes": [],
"no": [],
"no_set": set(),
"strict": False,
"empty": False,
}
# If types are given, filter the queryset accordingly
types_raw = request.GET['types'].split(',')
for t in types_raw:
type_slug, type_condition = t.split(':')
if type_condition in ["yes", "no"]:
t = AKType.objects.get(slug=type_slug, event=self.event)
self.types_filter[type_condition].append(t)
if type_condition == "no":
# Store slugs of excluded types in a set for faster lookup
self.types_filter["no_set"].add(t.slug)
else:
raise ValueError(f"Unknown type condition: {type_condition}")
if 'strict' in request.GET:
# If strict is specified and marked as "yes",
# exclude all AKs that have any of the excluded types ("no"),
# even if they have other types that are marked as "yes"
self.types_filter["strict"] = request.GET.get('strict') == 'yes'
if 'empty' in request.GET:
# If empty is specified and marked as "yes", include AKs that have no types at all
self.types_filter["empty"] = request.GET.get('empty') == 'yes'
# Will be used for generating a link to the wall view with the same filter
self.query_string = request.GET.urlencode(safe=",:")
except (ValueError, AKType.DoesNotExist):
# Display an error message if the types parameter is malformed
messages.add_message(request, messages.ERROR, _("Invalid type filter"))
self.types_filter = None
s = super().get(request, *args, **kwargs)
return s
def get_queryset(self):
# Ignore slots not scheduled yet
return super().get_queryset().filter(start__isnull=False).select_related('ak', 'room', 'ak__category')
qs = (super().get_queryset().filter(start__isnull=False).
select_related('event', 'ak', 'room', 'ak__category', 'ak__event'))
# Need to prefetch both event and ak__event
# since django is not aware that the two are always the same
# Apply type filter if necessary
if self.types_filter:
# Either include all AKs with the given types or without any types at all
if self.types_filter["empty"]:
qs = qs.filter(Q(ak__types__in=self.types_filter["yes"]) | Q(ak__types__isnull=True)).distinct()
# Or only those with the given types
else:
qs = qs.filter(ak__types__in=self.types_filter["yes"]).distinct()
# Afterwards, if strict, exclude all AKs that have any of the excluded types,
# even though they were included by the previous filter
if self.types_filter["strict"]:
qs = qs.exclude(ak__types__in=self.types_filter["no"]).distinct()
return qs
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
......@@ -39,6 +101,7 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
# Get list of current and next slots
for akslot in context["akslots"]:
self._process_slot(akslot)
# Construct a list of all rooms used by these slots on the fly
if akslot.room is not None:
rooms.add(akslot.room)
......@@ -61,8 +124,38 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
context["tracks"] = self.event.aktrack_set.all()
# Pass query string to template for generating a matching wall link
context["query_string"] = self.query_string
# Generate a list of all types and their current selection state for graphic filtering
types = [{"name": t.name, "slug": t.slug, "state": True} for t in self.event.aktype_set.all()]
if len(types) > 0:
context["type_filtering_active"] = True
if self.types_filter:
for t in types:
if t["slug"] in self.types_filter["no_set"]:
t["state"] = False
# Pass type list as well as filter state for strict filtering and empty types to the template
context["types"] = json.dumps(types)
context["types_filter_strict"] = False
context["types_filter_empty"] = False
if self.types_filter:
context["types_filter_empty"] = self.types_filter["empty"]
context["types_filter_strict"] = self.types_filter["strict"]#
else:
context["type_filtering_active"] = False
return context
def _process_slot(self, akslot):
"""
Function to be called for each slot when looping over the slots
(meant to be overridden in inherited views)
:param akslot: current slot
:type akslot: AKSlot
"""
class PlanScreenView(PlanIndexView):
"""
......@@ -96,26 +189,12 @@ class PlanScreenView(PlanIndexView):
# Restrict AK slots to relevant ones
# This will automatically filter all rooms not needed for the selected range in the orginal get_context method
akslots = super().get_queryset().filter(start__gt=self.start)
return super().get_queryset().filter(start__gt=self.start)
def get_context_data(self, *, object_list=None, **kwargs):
# Find the earliest hour AKs start and end (handle 00:00 as 24:00)
self.earliest_start_hour = 23
self.latest_end_hour = 1
for akslot in akslots.all():
start_hour = akslot.start.astimezone(self.event.timezone).hour
if start_hour < self.earliest_start_hour:
# Use hour - 1 to improve visibility of date change
self.earliest_start_hour = max(start_hour - 1, 0)
end_hour = akslot.end.astimezone(self.event.timezone).hour
# Special case: AK starts before but ends after midnight -- show until midnight
if end_hour < start_hour:
self.latest_end_hour = 24
elif end_hour > self.latest_end_hour:
# Always use hour + 1, since AK may end at :xy and not always at :00
self.latest_end_hour = min(end_hour + 1, 24)
return akslots
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context["start"] = self.start
context["end"] = self.event.end
......@@ -123,6 +202,19 @@ class PlanScreenView(PlanIndexView):
context["latest_end_hour"] = self.latest_end_hour
return context
def _process_slot(self, akslot):
start_hour = akslot.start.astimezone(self.event.timezone).hour
if start_hour < self.earliest_start_hour:
# Use hour - 1 to improve visibility of date change
self.earliest_start_hour = max(start_hour - 1, 0)
end_hour = akslot.end.astimezone(self.event.timezone).hour
# Special case: AK starts before but ends after midnight -- show until midnight
if end_hour < start_hour:
self.latest_end_hour = 24
elif end_hour > self.latest_end_hour:
# Always use hour + 1, since AK may end at :xy and not always at :00
self.latest_end_hour = min(end_hour + 1, 24)
class PlanRoomView(FilterByEventSlugMixin, DetailView):
"""
......@@ -152,7 +244,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
......@@ -10,6 +10,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import decimal
import os
from django.utils.translation import gettext_lazy as _
......@@ -38,6 +39,8 @@ INSTALLED_APPS = [
'AKScheduling.apps.AkschedulingConfig',
'AKPlan.apps.AkplanConfig',
'AKOnline.apps.AkonlineConfig',
'AKPreference.apps.AkpreferenceConfig',
'AKSolverInterface.apps.AksolverinterfaceConfig',
'AKModel.apps.AKAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
......@@ -217,6 +220,15 @@ PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS = 2 * 60 * 60
DASHBOARD_SHOW_RECENT = True
# How many entries max?
DASHBOARD_RECENT_MAX = 25
# How many events should be featured in the dashboard
# (active events will always be featured, even if their number is higher than this threshold)
DASHBOARD_MAX_FEATURED_EVENTS = 3
# In the export to the solver we need to calculate the integer number
# of discrete time slots covered by an AK. This is done by rounding
# the 'exact' number up. To avoid 'overshooting' by 1
# due to FLOP inaccuracies, we subtract this small epsilon before rounding.
EXPORT_CEIL_OFFSET_EPS = decimal.Decimal(1e-4)
# Registration/login behavior
SIMPLE_BACKEND_REDIRECT_URL = "/user/"
......@@ -224,7 +236,7 @@ LOGIN_REDIRECT_URL = SIMPLE_BACKEND_REDIRECT_URL
# Content Security Policy
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'")
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", "'unsafe-eval'")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", "fonts.googleapis.com")
CSP_IMG_SRC = ("'self'", "data:")
CSP_FRAME_SRC = ("'self'", )
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.