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
  • feature-type-filters
  • koma/feature/preference-polling-form
  • komasolver
  • main
  • renovate/django-5.x
  • renovate/django_csp-4.x
  • renovate/jsonschema-4.x
  • renovate/uwsgi-2.x
8 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
  • 520-akowner
  • 520-fix-event-wizard-datepicker
  • 520-fix-scheduling
  • 520-improve-scheduling
  • 520-improve-scheduling-2
  • 520-improve-submission
  • 520-improve-trackmanager
  • 520-improve-wall
  • 520-message-resolved
  • 520-status
  • 520-upgrades
  • add_express_interest_to_ak_overview
  • admin-production-color
  • bugfixes
  • csp
  • featire-ical-export
  • feature-ak-requirement-lists
  • feature-akslide-export-better-filename
  • feature-akslides
  • feature-better-admin
  • feature-better-cv-list
  • feature-colors
  • feature-constraint-checking
  • feature-constraint-checking-wip
  • feature-dashboard-history-button
  • feature-event-status
  • feature-event-wizard
  • feature-export-flag
  • feature-improve-admin
  • feature-improve-filters
  • feature-improved-user-creation-workflow
  • feature-interest-view
  • feature-mails
  • feature-modular-status
  • feature-plan-autoreload
  • feature-present-default
  • feature-register-link
  • feature-remaining-constraint-validation
  • feature-room-import
  • feature-scheduler-improve
  • feature-scheduling-2.0
  • feature-special-attention
  • feature-time-input
  • feature-tracker
  • feature-wiki-wishes
  • feature-wish-slots
  • feature-wizard-buttons
  • features-availabilities
  • fix-ak-times-above-folg
  • fix-api
  • fix-constraint-violation-string
  • fix-cv-checking
  • fix-default-slot-length
  • fix-default-slot-localization
  • fix-doc-minor
  • fix-duration-display
  • fix-event-tz-pytz-update
  • fix-history-interest
  • fix-interest-view
  • fix-js
  • fix-pipeline
  • fix-plan-timezone-now
  • fix-room-add
  • fix-scheduling-drag
  • fix-slot-defaultlength
  • fix-timezone
  • fix-translation-scheduling
  • fix-virtual-room-admin
  • fix-wizard-csp
  • font-locally
  • improve-admin
  • improve-online
  • improve-slides
  • improve-submission-coupling
  • interest_restriction
  • main
  • master
  • meta-debug-toolbar
  • meta-export
  • meta-makemessages
  • meta-performance
  • meta-tests
  • meta-tests-gitlab-test
  • meta-upgrades
  • mollux-master-patch-02906
  • port-availabilites-fullcalendar
  • qs
  • remove-tags
  • renovate/configure
  • renovate/django-4.x
  • renovate/django-5.x
  • renovate/django-bootstrap-datepicker-plus-5.x
  • renovate/django-bootstrap5-23.x
  • renovate/django-bootstrap5-24.x
  • renovate/django-compressor-4.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-registration-redux-2.x
  • renovate/django-simple-history-3.x
  • renovate/django-split-settings-1.x
  • renovate/django-timezone-field-5.x
100 results
Show changes
Showing
with 344 additions and 35 deletions
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-03-26 19:51+0200\n" "POT-Creation-Date: 2023-08-16 16:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -34,7 +34,7 @@ msgstr "Raum" ...@@ -34,7 +34,7 @@ msgstr "Raum"
msgid "Virtual Room" msgid "Virtual Room"
msgstr "Virtueller Raum" msgstr "Virtueller Raum"
#: AKOnline/models.py:17 AKOnline/views.py:27 #: AKOnline/models.py:17 AKOnline/views.py:38
msgid "Virtual Rooms" msgid "Virtual Rooms"
msgstr "Virtuelle Räume" msgstr "Virtuelle Räume"
...@@ -42,12 +42,12 @@ msgstr "Virtuelle Räume" ...@@ -42,12 +42,12 @@ msgstr "Virtuelle Räume"
msgid "Leave empty if that room is not virtual/hybrid." msgid "Leave empty if that room is not virtual/hybrid."
msgstr "Leer lassen wenn der Raum nicht virtuell/hybrid ist" msgstr "Leer lassen wenn der Raum nicht virtuell/hybrid ist"
#: AKOnline/views.py:18 #: AKOnline/views.py:25
#, python-format #, python-format
msgid "Created Room '%(room)s'" msgid "Created Room '%(room)s'"
msgstr "Raum '%(room)s' angelegt" msgstr "Raum '%(room)s' angelegt"
#: AKOnline/views.py:20 #: AKOnline/views.py:28
#, python-format #, python-format
msgid "Created related Virtual Room '%(vroom)s'" msgid "Created related Virtual Room '%(vroom)s'"
msgstr "Verbundenen virtuellen Raum '%(vroom)s' angelegt" msgstr "Verbundenen virtuellen Raum '%(vroom)s' angelegt"
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from AKModel.models import Event, Room from AKModel.models import Room
class VirtualRoom(models.Model): class VirtualRoom(models.Model):
...@@ -18,6 +18,12 @@ class VirtualRoom(models.Model): ...@@ -18,6 +18,12 @@ class VirtualRoom(models.Model):
@property @property
def event(self): def event(self):
"""
Property: Event this virtual room belongs to.
:return: Event this virtual room belongs to
:rtype: Event
"""
return self.room.event return self.room.event
def __str__(self): def __str__(self):
......
# Create your tests here.
...@@ -9,20 +9,31 @@ from AKOnline.forms import RoomWithVirtualForm ...@@ -9,20 +9,31 @@ from AKOnline.forms import RoomWithVirtualForm
class RoomCreationWithVirtualView(RoomCreationView): class RoomCreationWithVirtualView(RoomCreationView):
"""
View to create both rooms and optionally virtual rooms by filling one form
"""
form_class = RoomWithVirtualForm form_class = RoomWithVirtualForm
template_name = 'admin/AKOnline/room_create_with_virtual.html' template_name = 'admin/AKOnline/room_create_with_virtual.html'
room = None
def form_valid(self, form): def form_valid(self, form):
# This will create the room and additionally a virtual room if the url field is not blank
# objects['room'] will always a room instance afterwards, objects['virtual'] may be empty
objects = form.save() objects = form.save()
self.room = objects['room'] self.room = objects['room']
messages.success(self.request, _("Created Room '%(room)s'" % {'room': objects['room']})) # Create a (translated) success message containing information about the created room
messages.success(self.request, _("Created Room '%(room)s'" % {'room': objects['room']})) #pylint: disable=consider-using-f-string, line-too-long
if objects['virtual'] is not None: if objects['virtual'] is not None:
messages.success(self.request, _("Created related Virtual Room '%(vroom)s'" % {'vroom': objects['virtual']})) # Create a (translated) success message containing information about the created virtual room
messages.success(self.request, _("Created related Virtual Room '%(vroom)s'" % {'vroom': objects['virtual']})) #pylint: disable=consider-using-f-string, line-too-long
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
@status_manager.register(name="event_virtual_rooms") @status_manager.register(name="event_virtual_rooms")
class EventVirtualRoomsWidget(TemplateStatusWidget): class EventVirtualRoomsWidget(TemplateStatusWidget):
"""
Status page widget to contain information about all virtual rooms belonging to the given event
"""
required_context_type = "event" required_context_type = "event"
title = _("Virtual Rooms") title = _("Virtual Rooms")
template_name = "admin/AKOnline/status/event_virtual_rooms.html" template_name = "admin/AKOnline/status/event_virtual_rooms.html"
# Register your models here.
...@@ -2,4 +2,7 @@ from django.apps import AppConfig ...@@ -2,4 +2,7 @@ from django.apps import AppConfig
class AkplanConfig(AppConfig): class AkplanConfig(AppConfig):
"""
App configuration (default, only specifies name of the app)
"""
name = 'AKPlan' name = 'AKPlan'
# Create your models here.
...@@ -51,6 +51,8 @@ ...@@ -51,6 +51,8 @@
start: '{{ start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', start: '{{ start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
end: '{{ end | timezone:event.timezone | date:"Y-m-d H:i:s"}}', end: '{{ end | timezone:event.timezone | date:"Y-m-d H:i:s"}}',
}, },
slotMinTime: '{{ earliest_start_hour }}:00:00',
slotMaxTime: '{{ latest_end_hour }}:00:00',
eventDidMount: function(info) { eventDidMount: function(info) {
$(info.el).tooltip({title: info.event.extendedProps.description}); $(info.el).tooltip({title: info.event.extendedProps.description});
}, },
......
# gradients based on http://bsou.io/posts/color-gradients-with-python # gradients based on http://bsou.io/posts/color-gradients-with-python
def hex_to_rgb(hex): def hex_to_rgb(hex): #pylint: disable=redefined-builtin
""" """
Convert hex color to RGB color code Convert hex color to RGB color code
:param hex: hex encoded color :param hex: hex encoded color
...@@ -23,8 +23,7 @@ def rgb_to_hex(rgb): ...@@ -23,8 +23,7 @@ def rgb_to_hex(rgb):
""" """
# Components need to be integers for hex to make sense # Components need to be integers for hex to make sense
rgb = [int(x) for x in rgb] rgb = [int(x) for x in rgb]
return "#"+"".join(["0{0:x}".format(v) if v < 16 else return "#"+"".join([f"0{v:x}" if v < 16 else f"{v:x}" for v in rgb])
"{0:x}".format(v) for v in rgb])
def linear_blend(start_hex, end_hex, position): def linear_blend(start_hex, end_hex, position):
......
...@@ -11,6 +11,14 @@ register = template.Library() ...@@ -11,6 +11,14 @@ register = template.Library()
@register.filter @register.filter
def highlight_change_colors(akslot): def highlight_change_colors(akslot):
"""
Adjust color to highlight recent changes if needed
:param akslot: akslot to determine color for
:type akslot: AKSlot
:return: color that should be used (either default color of the category or some kind of red)
:rtype: str
"""
# Do not highlight in preview mode or when changes occurred before the plan was published # Do not highlight in preview mode or when changes occurred before the plan was published
if akslot.event.plan_hidden or (akslot.event.plan_published_at is not None if akslot.event.plan_hidden or (akslot.event.plan_published_at is not None
and akslot.event.plan_published_at > akslot.updated): and akslot.event.plan_published_at > akslot.updated):
...@@ -25,9 +33,14 @@ def highlight_change_colors(akslot): ...@@ -25,9 +33,14 @@ def highlight_change_colors(akslot):
# Recent change? Calculate gradient blend between red and # Recent change? Calculate gradient blend between red and
recentness = seconds_since_update / settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS recentness = seconds_since_update / settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS
return darken("#b71540", recentness) return darken("#b71540", recentness)
# return linear_blend("#b71540", "#000000", recentness)
@register.simple_tag @register.simple_tag
def timestamp_now(tz): def timestamp_now(tz):
"""
Get the current timestamp for the given timezone
:param tz: timezone to be used for the timestamp
:return: current timestamp in given timezone
"""
return date_format(datetime.now().astimezone(tz), "c") return date_format(datetime.now().astimezone(tz), "c")
from django.test import TestCase from django.test import TestCase
from AKModel.tests import BasicViewTests from AKModel.tests.test_views import BasicViewTests
class PlanViewTests(BasicViewTests, TestCase): class PlanViewTests(BasicViewTests, TestCase):
"""
Tests for AKPlan
"""
fixtures = ['model.json'] fixtures = ['model.json']
APP_NAME = 'plan' APP_NAME = 'plan'
...@@ -15,7 +18,10 @@ class PlanViewTests(BasicViewTests, TestCase): ...@@ -15,7 +18,10 @@ class PlanViewTests(BasicViewTests, TestCase):
] ]
def test_plan_hidden(self): def test_plan_hidden(self):
view_name_with_prefix, url = self._name_and_url(('plan_overview', {'event_slug': 'kif23'})) """
Test correct handling of plan visibility
"""
_, url = self._name_and_url(('plan_overview', {'event_slug': 'kif23'}))
self.client.logout() self.client.logout()
response = self.client.get(url) response = self.client.get(url)
...@@ -28,8 +34,11 @@ class PlanViewTests(BasicViewTests, TestCase): ...@@ -28,8 +34,11 @@ class PlanViewTests(BasicViewTests, TestCase):
msg_prefix="Plan is not visible for staff user") msg_prefix="Plan is not visible for staff user")
def test_wall_redirect(self): def test_wall_redirect(self):
view_name_with_prefix, url_wall = self._name_and_url(('plan_wall', {'event_slug': 'kif23'})) """
view_name_with_prefix, url_plan = self._name_and_url(('plan_overview', {'event_slug': 'kif23'})) Test: Make sure that user is redirected from wall to overview when plan is hidden
"""
_, url_wall = self._name_and_url(('plan_wall', {'event_slug': 'kif23'}))
_, url_plan = self._name_and_url(('plan_overview', {'event_slug': 'kif23'}))
response = self.client.get(url_wall) response = self.client.get(url_wall)
self.assertRedirects(response, url_plan, self.assertRedirects(response, url_plan,
......
from datetime import timedelta from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.datetime_safe import datetime from django.views.generic import DetailView, ListView
from django.views.generic import ListView, DetailView
from AKModel.models import AKSlot, Room, AKTrack
from AKModel.metaviews.admin import FilterByEventSlugMixin from AKModel.metaviews.admin import FilterByEventSlugMixin
from AKModel.models import AKSlot, AKTrack, Room
class PlanIndexView(FilterByEventSlugMixin, ListView): class PlanIndexView(FilterByEventSlugMixin, ListView):
"""
Default plan view
Shows two lists of current and upcoming AKs and a graphical full plan below
"""
model = AKSlot model = AKSlot
template_name = "AKPlan/plan_index.html" template_name = "AKPlan/plan_index.html"
context_object_name = "akslots" context_object_name = "akslots"
...@@ -60,6 +64,15 @@ class PlanIndexView(FilterByEventSlugMixin, ListView): ...@@ -60,6 +64,15 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
class PlanScreenView(PlanIndexView): class PlanScreenView(PlanIndexView):
"""
Plan view optimized for screens and projectors
This again shows current and upcoming AKs as well as a graphical plan,
but no navigation elements and trys to use the available space as best as possible
such that no scrolling is needed.
The view contains a frontend functionality for auto-reload.
"""
template_name = "AKPlan/plan_wall.html" template_name = "AKPlan/plan_wall.html"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
...@@ -69,11 +82,12 @@ class PlanScreenView(PlanIndexView): ...@@ -69,11 +82,12 @@ class PlanScreenView(PlanIndexView):
return redirect(reverse_lazy("plan:plan_overview", kwargs={"event_slug": self.event.slug})) return redirect(reverse_lazy("plan:plan_overview", kwargs={"event_slug": self.event.slug}))
return s return s
""" # pylint: disable=attribute-defined-outside-init
def get_queryset(self): def get_queryset(self):
# Determine interesting range (some hours ago until some hours in the future as specified in the settings)
now = datetime.now().astimezone(self.event.timezone) now = datetime.now().astimezone(self.event.timezone)
# Wall during event: Adjust, show only parts in the future
if self.event.start < now < self.event.end: if self.event.start < now < self.event.end:
# Determine interesting range (some hours ago until some hours in the future as specified in the settings)
self.start = now - timedelta(hours=settings.PLAN_WALL_HOURS_RETROSPECT) self.start = now - timedelta(hours=settings.PLAN_WALL_HOURS_RETROSPECT)
else: else:
self.start = self.event.start self.start = self.event.start
...@@ -81,33 +95,63 @@ class PlanScreenView(PlanIndexView): ...@@ -81,33 +95,63 @@ class PlanScreenView(PlanIndexView):
# Restrict AK slots to relevant ones # Restrict AK slots to relevant ones
# This will automatically filter all rooms not needed for the selected range in the orginal get_context method # This will automatically filter all rooms not needed for the selected range in the orginal get_context method
return super().get_queryset().filter(start__gt=self.start) akslots = super().get_queryset().filter(start__gt=self.start)
"""
# 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): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
context["start"] = self.event.start context["start"] = self.start
context["end"] = self.event.end context["end"] = self.event.end
context["earliest_start_hour"] = self.earliest_start_hour
context["latest_end_hour"] = self.latest_end_hour
return context return context
class PlanRoomView(FilterByEventSlugMixin, DetailView): class PlanRoomView(FilterByEventSlugMixin, DetailView):
"""
Plan view for a single room
"""
template_name = "AKPlan/plan_room.html" template_name = "AKPlan/plan_room.html"
model = Room model = Room
context_object_name = "room" context_object_name = "room"
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
# Restrict AKSlot list to the given room
# while joining AK, room and category information to reduce the amount of necessary SQL queries
context["slots"] = AKSlot.objects.filter(room=context['room']).select_related('ak', 'ak__category', 'ak__track') context["slots"] = AKSlot.objects.filter(room=context['room']).select_related('ak', 'ak__category', 'ak__track')
return context return context
class PlanTrackView(FilterByEventSlugMixin, DetailView): class PlanTrackView(FilterByEventSlugMixin, DetailView):
"""
Plan view for a single track
"""
template_name = "AKPlan/plan_track.html" template_name = "AKPlan/plan_track.html"
model = AKTrack model = AKTrack
context_object_name = "track" context_object_name = "track"
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
context["slots"] = AKSlot.objects.filter(event=self.event, ak__track=context['track']).select_related('ak', 'room', 'ak__category') # 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']). \
select_related('ak', 'room', 'ak__category')
return context return context
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-15 20:03+0200\n" "POT-Creation-Date: 2023-08-16 16:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -17,10 +17,10 @@ msgstr "" ...@@ -17,10 +17,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: AKPlanning/settings.py:147 #: AKPlanning/settings.py:148
msgid "German" msgid "German"
msgstr "Deutsch" msgstr "Deutsch"
#: AKPlanning/settings.py:148 #: AKPlanning/settings.py:149
msgid "English" msgid "English"
msgstr "Englisch" msgstr "Englisch"
...@@ -10,6 +10,7 @@ For the full list of settings and their values, see ...@@ -10,6 +10,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/ https://docs.djangoproject.com/en/2.2/ref/settings/
""" """
import decimal
import os import os
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
...@@ -38,6 +39,8 @@ INSTALLED_APPS = [ ...@@ -38,6 +39,8 @@ INSTALLED_APPS = [
'AKScheduling.apps.AkschedulingConfig', 'AKScheduling.apps.AkschedulingConfig',
'AKPlan.apps.AkplanConfig', 'AKPlan.apps.AkplanConfig',
'AKOnline.apps.AkonlineConfig', 'AKOnline.apps.AkonlineConfig',
'AKPreference.apps.AkpreferenceConfig',
'AKSolverInterface.apps.AksolverinterfaceConfig',
'AKModel.apps.AKAdminConfig', 'AKModel.apps.AKAdminConfig',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
...@@ -52,9 +55,9 @@ INSTALLED_APPS = [ ...@@ -52,9 +55,9 @@ INSTALLED_APPS = [
'rest_framework', 'rest_framework',
'simple_history', 'simple_history',
'registration', 'registration',
'bootstrap_datepicker_plus',
'django_tex', 'django_tex',
'compressor', 'compressor',
'docs',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
...@@ -217,6 +220,15 @@ PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS = 2 * 60 * 60 ...@@ -217,6 +220,15 @@ PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS = 2 * 60 * 60
DASHBOARD_SHOW_RECENT = True DASHBOARD_SHOW_RECENT = True
# How many entries max? # How many entries max?
DASHBOARD_RECENT_MAX = 25 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 # Registration/login behavior
SIMPLE_BACKEND_REDIRECT_URL = "/user/" SIMPLE_BACKEND_REDIRECT_URL = "/user/"
...@@ -224,7 +236,7 @@ LOGIN_REDIRECT_URL = SIMPLE_BACKEND_REDIRECT_URL ...@@ -224,7 +236,7 @@ LOGIN_REDIRECT_URL = SIMPLE_BACKEND_REDIRECT_URL
# Content Security Policy # Content Security Policy
CSP_DEFAULT_SRC = ("'self'",) 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_STYLE_SRC = ("'self'", "'unsafe-inline'", "fonts.googleapis.com")
CSP_IMG_SRC = ("'self'", "data:") CSP_IMG_SRC = ("'self'", "data:")
CSP_FRAME_SRC = ("'self'", ) CSP_FRAME_SRC = ("'self'", )
...@@ -234,4 +246,9 @@ CSP_FONT_SRC = ("'self'", "data:", "fonts.gstatic.com") ...@@ -234,4 +246,9 @@ CSP_FONT_SRC = ("'self'", "data:", "fonts.gstatic.com")
SEND_MAILS = True SEND_MAILS = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Documentation
DOCS_ROOT = os.path.join(BASE_DIR, 'docs/_build/html')
DOCS_ACCESS = 'public'
include(optional("settings/*.py")) include(optional("settings/*.py"))
"""AKPlanning URL Configuration """
AKPlanning URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/ https://docs.djangoproject.com/en/2.2/topics/http/urls/
...@@ -13,13 +14,15 @@ Including another URLconf ...@@ -13,13 +14,15 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
import debug_toolbar import debug_toolbar
from django.apps import apps from django.apps import apps
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include, re_path
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
re_path(r'^docs/', include('docs.urls')),
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('accounts/', include('registration.backends.simple.urls')), path('accounts/', include('registration.backends.simple.urls')),
path('', include('AKModel.urls', namespace='model')), path('', include('AKModel.urls', namespace='model')),
...@@ -34,3 +37,5 @@ if apps.is_installed("AKDashboard"): ...@@ -34,3 +37,5 @@ if apps.is_installed("AKDashboard"):
urlpatterns.append(path('', include('AKDashboard.urls', namespace='dashboard'))) urlpatterns.append(path('', include('AKDashboard.urls', namespace='dashboard')))
if apps.is_installed("AKPlan"): if apps.is_installed("AKPlan"):
urlpatterns.append(path('', include('AKPlan.urls', namespace='plan'))) urlpatterns.append(path('', include('AKPlan.urls', namespace='plan')))
if apps.is_installed("AKPreference"):
urlpatterns.append(path('', include('AKPreference.urls', namespace='poll')))
from django import forms
from django.contrib import admin
from AKPreference.models import AKPreference, EventParticipant
from AKModel.admin import PrepopulateWithNextActiveEventMixin
from AKModel.models import AK
@admin.register(EventParticipant)
class EventParticipantAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
"""
Admin interface for EventParticipant
"""
model = EventParticipant
list_display = ['name', 'institution', 'event']
list_filter = ['event', 'institution']
list_editable = []
ordering = ['name']
class AKPreferenceAdminForm(forms.ModelForm):
"""
Adapted admin form for AK preferences for usage in :class:`AKPreferenceAdmin`)
"""
class Meta:
widgets = {
'participant': forms.Select,
'ak': forms.Select,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Filter possible values for foreign keys & m2m when event is specified
if hasattr(self.instance, "event") and self.instance.event is not None:
self.fields["participant"].queryset = EventParticipant.objects.filter(event=self.instance.event)
self.fields["ak"].queryset = AK.objects.filter(event=self.instance.event)
@admin.register(AKPreference)
class AKPreferenceAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
"""
Admin interface for AK preferences.
Uses an adapted form (see :class:`AKPreferenceAdminForm`)
"""
model = AKPreference
form = AKPreferenceAdminForm
list_display = ['preference', 'participant', 'ak', 'event']
list_filter = ['event', 'ak', 'participant']
list_editable = []
ordering = ['participant', 'preference', 'ak']
from django.apps import AppConfig
class AkpreferenceConfig(AppConfig):
"""
App configuration (default, only specifies name of the app)
"""
name = "AKPreference"
from django import forms
from django.utils.translation import gettext_lazy as _
from AKModel.availability.forms import AvailabilitiesFormMixin
from AKModel.availability.models import Availability
from AKModel.models import AKRequirement
from AKPreference.models import EventParticipant
class EventParticipantForm(AvailabilitiesFormMixin, forms.ModelForm):
"""Form to add EventParticipants"""
required_css_class = "required"
class Meta:
model = EventParticipant
fields = [
"name",
"institution",
"requirements",
"event",
]
widgets = {
"requirements": forms.CheckboxSelectMultiple,
"event": forms.HiddenInput,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial = {**self.initial, **kwargs["initial"]}
self.fields["requirements"].queryset = AKRequirement.objects.filter(
event=self.initial.get("event")
)
# Don't ask for requirements if there are no requirements configured for this event
if self.fields["requirements"].queryset.count() == 0:
self.fields.pop("requirements")
def clean_availabilities(self):
"""
Automatically improve availabilities entered.
If the user did not specify availabilities assume the full event duration is possible
:return: cleaned availabilities
(either user input or one availability for the full length of the event if user input was empty)
"""
availabilities = super().clean_availabilities()
if len(availabilities) == 0:
availabilities.append(
Availability.with_event_length(event=self.cleaned_data["event"])
)
return availabilities
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-13 23:58+0000\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: AKPreference/templates/AKPreference/poll.html:11
#: AKPreference/templates/AKPreference/poll_not_configured.html:7
msgid "AKs"
msgstr "AKs"
#: AKPreference/templates/AKPreference/poll.html:11
msgid "Preferences"
msgstr "Präferenzen"
#: AKPreference/templates/AKPreference/poll.html:34
#: AKPreference/templates/AKPreference/poll.html:41
msgid "AK Preferences"
msgstr "AK-Präferenzen"
#: AKPreference/templates/AKPreference/poll.html:47
msgid "Your preferences"
msgstr "Deine Präferenzen"
#: AKPreference/templates/AKPreference/poll.html:49
msgid "Please enter your preferences."
msgstr "Trage bitte deine Präferenzen zu den AKs ein."
#: AKPreference/templates/AKPreference/poll.html:68
#: AKPreference/templates/AKPreference/poll.html:82
msgid "Intends to submit a resolution"
msgstr "Beabsichtigt eine Resolution einzureichen"
#: AKPreference/templates/AKPreference/poll.html:73
msgid "Description"
msgstr "Beschreibung"
#: AKPreference/templates/AKPreference/poll.html:78
msgid "Responsible"
msgstr "Verantwortlich"
#: AKPreference/templates/AKPreference/poll.html:93
msgid "Submit"
msgstr "Eintragen"
#: AKPreference/templates/AKPreference/poll.html:97
msgid "Reset Form"
msgstr "Formular leeren"
#: AKPreference/templates/AKPreference/poll.html:101
msgid "Cancel"
msgstr "Abbrechen"
#: AKPreference/templates/AKPreference/poll_base.html:13
msgid "Write to organizers of this event for questions and comments"
msgstr "Fragen oder Kommentare? Schreib den Orgas dieses Events eine Mail"
#: AKPreference/templates/AKPreference/poll_not_configured.html:7
#: AKPreference/templates/AKPreference/poll_not_configured.html:11
msgid "AK Preference Poll"
msgstr "Abfrage von AK-Präferenzen"
#: AKPreference/templates/AKPreference/poll_not_configured.html:20
msgid ""
"System is not yet configured for AK preference polling. Please try again "
"later."
msgstr ""
"Das System ist bisher nicht für die Erfassung von AK-Präferenzen "
"konfiguriert. Bitte versuche es später wieder."
#: AKPreference/views.py:18
msgid "AK preferences were registered successfully"
msgstr "AK-Präferenzen erfolgreich registriert"
#: AKPreference/views.py:113
msgid ""
"Something went wrong. Your preferences were not saved. Please try again or "
"contact the organizers."
msgstr ""
"Etwas ging schief. Deine Präferenzen konnten nicht gespeichert werden. "
"Versuche es bitte erneut oder kontaktiere die Orgas."