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

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
Show changes
Showing
with 329 additions and 160 deletions
......@@ -76,10 +76,15 @@ class EventRoomsWidget(TemplateStatusWidget):
def render_actions(self, context: {}) -> list[dict]:
actions = super().render_actions(context)
# Action has to be added here since it depends on the event for URL building
import_room_url = reverse_lazy("admin:room-import", kwargs={"event_slug": context["event"].slug})
for action in actions:
if action["url"] == import_room_url:
return actions
actions.append(
{
"text": _("Import Rooms from CSV"),
"url": reverse_lazy("admin:room-import", kwargs={"event_slug": context["event"].slug}),
"url": import_room_url,
}
)
return actions
......
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
......@@ -217,6 +217,9 @@ 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
# Registration/login behavior
SIMPLE_BACKEND_REDIRECT_URL = "/user/"
......@@ -224,7 +227,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'", )
......
......@@ -165,7 +165,7 @@ class EventSerializer(serializers.ModelSerializer):
# Updated room if needed (pk changed -- otherwise, no need for an additional database lookup)
new_room_id = validated_data.get('room')["pk"]
if instance.room.pk != new_room_id:
if instance.room is None or instance.room.pk != new_room_id:
instance.room = get_object_or_404(Room, pk=new_room_id)
instance.save()
......
......@@ -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__()
......
......@@ -224,7 +224,7 @@
<ul data-id="None" data-sync="false" class="ak-list">
{% for ak in aks_without_track %}
<li data-ak-id="{{ ak.pk }}" data-bs-toggle="tooltip" data-placement="top" title="">
{{ ak.name }} ({{ ak.category }})
{{ ak.name }} <span style="color:{{ ak.category.color }}">({{ ak.category }})</span>
</li>
{% endfor %}
</ul>
......@@ -243,7 +243,7 @@
<ul data-track-id="{{ track.pk }}" data-name="{{ track }}" data-sync="true" class="ak-list">
{% for ak in track.aks_with_category %}
<li data-ak-id="{{ ak.pk }}" data-bs-toggle="tooltip" data-placement="top" title="">
{{ ak.name }} ({{ ak.category }})
{{ ak.name }} <span style="color:{{ ak.category.color }}">({{ ak.category }})</span>
</li>
{% endfor %}
</ul>
......
......@@ -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 %}
......
......@@ -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 %}
......
......@@ -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)
......@@ -152,6 +154,13 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
def get_success_url(self):
return self.request.path
def form_valid(self, form):
# Don't create a history entry for this change
form.instance.skip_history_when_saving = True
r = super().form_valid(form)
del form.instance.skip_history_when_saving
return r
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = f"{_('Enter interest')}"
......
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
......
......@@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
from AKModel.availability.forms import AvailabilitiesFormMixin
from AKModel.availability.models import Availability
from AKModel.models import AK, AKOwner, AKCategory, AKRequirement, AKSlot, AKOrgaMessage
from AKModel.models import AK, AKOwner, AKCategory, AKRequirement, AKSlot, AKOrgaMessage, AKType
class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
......@@ -37,6 +37,7 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
'owners',
'description',
'category',
'types',
'reso',
'present',
'requirements',
......@@ -48,6 +49,7 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
widgets = {
'requirements': forms.CheckboxSelectMultiple,
'types': forms.CheckboxSelectMultiple,
'event': forms.HiddenInput,
}
......@@ -61,6 +63,10 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
self.fields["prerequisites"].widget.attrs = {'class': 'chosen-select'}
self.fields['category'].queryset = AKCategory.objects.filter(event=self.initial.get('event'))
self.fields['types'].queryset = AKType.objects.filter(event=self.initial.get('event'))
# Don't ask for types if there are no types configured for this event
if self.fields['types'].queryset.count() == 0:
self.fields.pop('types')
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:
......@@ -146,6 +152,9 @@ class AKSubmissionForm(AKForm):
class Meta(AKForm.Meta):
# Exclude fields again that were previously included in the parent class
exclude = ['link', 'protocol_link'] #pylint: disable=modelform-uses-exclude
widgets = AKForm.Meta.widgets | {
'types': forms.CheckboxSelectMultiple(attrs={'checked' : 'checked'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
......@@ -182,6 +191,9 @@ class AKWishForm(AKForm):
class Meta(AKForm.Meta):
# Exclude fields again that were previously included in the parent class
exclude = ['owners', 'link', 'protocol_link'] #pylint: disable=modelform-uses-exclude
widgets = AKForm.Meta.widgets | {
'types': forms.CheckboxSelectMultiple(attrs={'checked': 'checked'}),
}
class AKOwnerForm(forms.ModelForm):
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-16 16:30+0200\n"
"POT-Creation-Date: 2025-03-25 15:58+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"
......@@ -17,16 +17,16 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: AKSubmission/forms.py:93
#: AKSubmission/forms.py:101
#, python-format
msgid "\"%(duration)s\" is not a valid duration"
msgstr "\"%(duration)s\" ist keine gültige Dauer"
#: AKSubmission/forms.py:159
#: AKSubmission/forms.py:164
msgid "Duration(s)"
msgstr "Dauer(n)"
#: AKSubmission/forms.py:161
#: AKSubmission/forms.py:166
msgid ""
"Enter at least one planned duration (in hours). If your AK should have "
"multiple slots, use multiple lines"
......@@ -47,177 +47,173 @@ msgstr ""
#: AKSubmission/templates/AKSubmission/submission_overview.html:7
#: AKSubmission/templates/AKSubmission/submission_overview.html:11
#: AKSubmission/templates/AKSubmission/submission_overview.html:36
#: AKSubmission/templates/AKSubmission/submit_new.html:31
#: AKSubmission/templates/AKSubmission/submit_new.html:38
#: AKSubmission/templates/AKSubmission/submit_new_wish.html:13
msgid "AK Submission"
msgstr "AK-Eintragung"
#: AKSubmission/templates/AKSubmission/ak_detail.html:77
#: AKSubmission/templates/AKSubmission/ak_detail.html:126
#: AKSubmission/templates/AKSubmission/ak_interest_script.html:50
msgid "Interest indication currently not allowed. Sorry."
msgstr "Interessenangabe aktuell nicht erlaubt. Sorry."
#: AKSubmission/templates/AKSubmission/ak_detail.html:79
#: AKSubmission/templates/AKSubmission/ak_detail.html:128
#: AKSubmission/templates/AKSubmission/ak_interest_script.html:52
msgid "Could not save your interest. Sorry."
msgstr "Interesse konnte nicht gespeichert werden. Sorry."
#: AKSubmission/templates/AKSubmission/ak_detail.html:100
#: AKSubmission/templates/AKSubmission/ak_detail.html:149
msgid "Interest"
msgstr "Interesse"
#: AKSubmission/templates/AKSubmission/ak_detail.html:102
#: AKSubmission/templates/AKSubmission/ak_table.html:55
#: AKSubmission/templates/AKSubmission/ak_detail.html:151
#: AKSubmission/templates/AKSubmission/ak_table.html:65
msgid "Show Interest"
msgstr "Interesse bekunden"
#: AKSubmission/templates/AKSubmission/ak_detail.html:108
#: AKSubmission/templates/AKSubmission/ak_table.html:46
#: AKSubmission/templates/AKSubmission/ak_detail.html:157
#: AKSubmission/templates/AKSubmission/ak_table.html:56
msgid "Open external link"
msgstr "Externen Link öffnen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:113
#: AKSubmission/templates/AKSubmission/ak_detail.html:162
msgid "Open protocol link"
msgstr "Protokolllink öffnen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:118
#: AKSubmission/templates/AKSubmission/ak_detail.html:167
#: AKSubmission/templates/AKSubmission/ak_history.html:19
#: AKSubmission/templates/AKSubmission/ak_history.html:31
msgid "History"
msgstr "Versionsgeschichte"
#: AKSubmission/templates/AKSubmission/ak_detail.html:121
#: AKSubmission/templates/AKSubmission/ak_detail.html:170
#: AKSubmission/templates/AKSubmission/akmessage_add.html:8
#: AKSubmission/templates/AKSubmission/akmessage_add.html:16
#: AKSubmission/templates/AKSubmission/akmessage_add.html:22
msgid "Add confidential message to organizers"
msgstr "Sende eine private Nachricht an das Organisationsteam"
#: AKSubmission/templates/AKSubmission/ak_detail.html:124
#: AKSubmission/templates/AKSubmission/ak_detail.html:269
#: AKSubmission/templates/AKSubmission/ak_detail.html:173
#: AKSubmission/templates/AKSubmission/ak_detail.html:326
#: AKSubmission/templates/AKSubmission/ak_edit.html:16
#: AKSubmission/templates/AKSubmission/ak_table.html:51
#: AKSubmission/templates/AKSubmission/ak_table.html:61
msgid "Edit"
msgstr "Bearbeiten"
#: AKSubmission/templates/AKSubmission/ak_detail.html:129
#: AKSubmission/templates/AKSubmission/ak_detail.html:178
#: AKSubmission/templates/AKSubmission/ak_history.html:31
#: AKSubmission/templates/AKSubmission/ak_table.html:34
#: AKSubmission/templates/AKSubmission/ak_table.html:37
msgid "AK Wish"
msgstr "AK-Wunsch"
#: AKSubmission/templates/AKSubmission/ak_detail.html:136
#: AKSubmission/templates/AKSubmission/ak_detail.html:186
#, python-format
msgid ""
"\n"
" This AK currently takes place for another "
"%(featured_slot_remaining)s minute(s) in %(room)s.\n"
" &nbsp;\n"
" "
"This AK currently takes place for another <span v-html=\"timeUntilEnd\">"
"%(featured_slot_remaining)s</span> minute(s) in %(room)s.&nbsp;"
msgstr ""
"\n"
" Dieser AK findet noch %(featured_slot_remaining)s "
"Minute(n) in %(room)s statt.&nbsp;\n"
"Dieser AK findet noch <span v-html=\"timeUntilEnd\">"
"%(featured_slot_remaining)s</span> Minute(n) in %(room)s statt.&nbsp;\n"
" "
#: AKSubmission/templates/AKSubmission/ak_detail.html:142
#: AKSubmission/templates/AKSubmission/ak_detail.html:189
#, python-format
msgid ""
"\n"
" This AK starts in %(featured_slot_remaining)s "
"minute(s) in %(room)s.&nbsp;\n"
" "
"This AK starts in <span v-html=\"timeUntilStart\">"
"%(featured_slot_remaining)s</span> minute(s) in %(room)s.&nbsp;"
msgstr ""
"\n"
" This AK beginnt in %(featured_slot_remaining)s "
"Minute(n) in %(room)s.&nbsp;\n"
"Dieser AK beginnt in <span v-html=\"timeUntilStart\">"
"%(featured_slot_remaining)s</span> Minute(n) in %(room)s.&nbsp;\n"
" "
#: AKSubmission/templates/AKSubmission/ak_detail.html:149
#: AKSubmission/templates/AKSubmission/ak_detail.html:277
#: AKSubmission/templates/AKSubmission/ak_detail.html:194
#: AKSubmission/templates/AKSubmission/ak_detail.html:334
msgid "Go to virtual room"
msgstr "Zum virtuellen Raum"
#: AKSubmission/templates/AKSubmission/ak_detail.html:158
#: AKSubmission/templates/AKSubmission/ak_detail.html:205
#: AKSubmission/templates/AKSubmission/ak_table.html:10
msgid "Who?"
msgstr "Wer?"
#: AKSubmission/templates/AKSubmission/ak_detail.html:164
#: AKSubmission/templates/AKSubmission/ak_detail.html:211
#: AKSubmission/templates/AKSubmission/ak_history.html:36
#: AKSubmission/templates/AKSubmission/ak_table.html:11
msgid "Category"
msgstr "Kategorie"
#: AKSubmission/templates/AKSubmission/ak_detail.html:171
#: AKSubmission/templates/AKSubmission/ak_detail.html:218
#: AKSubmission/templates/AKSubmission/ak_table.html:13
msgid "Types"
msgstr "Typen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:228
#: AKSubmission/templates/AKSubmission/ak_history.html:37
msgid "Track"
msgstr "Track"
#: AKSubmission/templates/AKSubmission/ak_detail.html:177
#, fuzzy
#| msgid "Present results of this AK"
#: AKSubmission/templates/AKSubmission/ak_detail.html:234
msgid "Present this AK"
msgstr "Die Ergebnisse dieses AKs vorstellen"
msgstr "Diesen AK vorstellen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:182
#: AKSubmission/templates/AKSubmission/ak_detail.html:239
msgid "(Category Default)"
msgstr "(Kategorievoreinstellung)"
#: AKSubmission/templates/AKSubmission/ak_detail.html:188
#: AKSubmission/templates/AKSubmission/ak_detail.html:245
msgid "Reso intention?"
msgstr "Resoabsicht?"
#: AKSubmission/templates/AKSubmission/ak_detail.html:195
#: AKSubmission/templates/AKSubmission/ak_detail.html:252
msgid "Requirements"
msgstr "Anforderungen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:208
#: AKSubmission/templates/AKSubmission/ak_detail.html:265
msgid "Conflicting AKs"
msgstr "AK-Konflikte"
#: AKSubmission/templates/AKSubmission/ak_detail.html:216
#: AKSubmission/templates/AKSubmission/ak_detail.html:273
msgid "Prerequisite AKs"
msgstr "Vorausgesetzte AKs"
#: AKSubmission/templates/AKSubmission/ak_detail.html:224
#: AKSubmission/templates/AKSubmission/ak_detail.html:281
msgid "Notes"
msgstr "Notizen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:237
#: AKSubmission/templates/AKSubmission/ak_detail.html:294
msgid "When?"
msgstr "Wann?"
#: AKSubmission/templates/AKSubmission/ak_detail.html:239
#: AKSubmission/templates/AKSubmission/ak_detail.html:296
#: AKSubmission/templates/AKSubmission/akslot_delete.html:35
msgid "Duration"
msgstr "Dauer"
#: AKSubmission/templates/AKSubmission/ak_detail.html:241
#: AKSubmission/templates/AKSubmission/ak_detail.html:298
msgid "Room"
msgstr "Raum"
#: AKSubmission/templates/AKSubmission/ak_detail.html:272
#: AKSubmission/templates/AKSubmission/ak_detail.html:329
msgid "Delete"
msgstr "Löschen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:283
#: AKSubmission/templates/AKSubmission/ak_detail.html:340
msgid "Schedule"
msgstr "Schedule"
#: AKSubmission/templates/AKSubmission/ak_detail.html:295
#: AKSubmission/templates/AKSubmission/ak_detail.html:352
msgid "Add another slot"
msgstr "Einen neuen AK-Slot hinzufügen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:305
#: AKSubmission/templates/AKSubmission/ak_detail.html:362
msgid "Possible Times"
msgstr "Mögliche Zeiten"
#: AKSubmission/templates/AKSubmission/ak_detail.html:309
#: AKSubmission/templates/AKSubmission/ak_detail.html:366
msgid "Start"
msgstr "Start"
#: AKSubmission/templates/AKSubmission/ak_detail.html:310
#: AKSubmission/templates/AKSubmission/ak_detail.html:367
msgid "End"
msgstr "Ende"
......@@ -269,16 +265,16 @@ msgid "Time"
msgstr "Zeit"
#: AKSubmission/templates/AKSubmission/ak_history.html:48
#: AKSubmission/templates/AKSubmission/ak_table.html:25
#: AKSubmission/templates/AKSubmission/ak_table.html:28
msgid "Present results of this AK"
msgstr "Die Ergebnisse dieses AKs vorstellen"
#: AKSubmission/templates/AKSubmission/ak_history.html:52
#: AKSubmission/templates/AKSubmission/ak_table.html:29
#: AKSubmission/templates/AKSubmission/ak_table.html:32
msgid "Intends to submit a resolution"
msgstr "Beabsichtigt eine Resolution einzureichen"
#: AKSubmission/templates/AKSubmission/ak_list.html:6 AKSubmission/views.py:84
#: AKSubmission/templates/AKSubmission/ak_list.html:6 AKSubmission/views.py:82
msgid "All AKs"
msgstr "Alle AKs"
......@@ -294,11 +290,11 @@ msgstr "AK-Liste"
msgid "Add AK"
msgstr "AK hinzufügen"
#: AKSubmission/templates/AKSubmission/ak_table.html:42
#: AKSubmission/templates/AKSubmission/ak_table.html:52
msgid "Details"
msgstr "Details"
#: AKSubmission/templates/AKSubmission/ak_table.html:66
#: AKSubmission/templates/AKSubmission/ak_table.html:76
msgid "There are no AKs in this category yet"
msgstr "Es gibt noch keine AKs in dieser Kategorie"
......@@ -309,7 +305,7 @@ msgstr "Senden"
#: AKSubmission/templates/AKSubmission/akmessage_add.html:31
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:26
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:29
#: AKSubmission/templates/AKSubmission/submit_new.html:52
#: AKSubmission/templates/AKSubmission/submit_new.html:59
msgid "Reset Form"
msgstr "Formular leeren"
......@@ -317,7 +313,7 @@ msgstr "Formular leeren"
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:30
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:33
#: AKSubmission/templates/AKSubmission/akslot_delete.html:45
#: AKSubmission/templates/AKSubmission/submit_new.html:56
#: AKSubmission/templates/AKSubmission/submit_new.html:63
msgid "Cancel"
msgstr "Abbrechen"
......@@ -385,8 +381,8 @@ msgstr "Ich leite bisher keine AKs"
#: AKSubmission/templates/AKSubmission/submission_overview.html:67
#: AKSubmission/templates/AKSubmission/submit_new.html:9
#: AKSubmission/templates/AKSubmission/submit_new.html:34
#: AKSubmission/templates/AKSubmission/submit_new.html:41
#: AKSubmission/templates/AKSubmission/submit_new.html:48
msgid "New AK"
msgstr "Neuer AK"
......@@ -400,15 +396,21 @@ msgstr ""
"Dieses Event is nicht aktiv. Es können keine AKs hinzugefügt oder bearbeitet "
"werden"
#: AKSubmission/templates/AKSubmission/submit_new.html:48
#: AKSubmission/templates/AKSubmission/submit_new.html:29
msgid ""
"only relevant for KIF-AKs - determines whether the AK appears in the slides "
"for the closing plenary session"
msgstr "nur relevant für KIF-AKs - entscheidet, ob der AK in den Folien fürs Abschlussplenum auftaucht"
#: AKSubmission/templates/AKSubmission/submit_new.html:55
msgid "Submit"
msgstr "Eintragen"
#: AKSubmission/views.py:127
#: AKSubmission/views.py:125
msgid "Wishes"
msgstr "Wünsche"
#: AKSubmission/views.py:127
#: AKSubmission/views.py:125
msgid "AKs one would like to have"
msgstr ""
"AKs die sich gewünscht wurden, aber bei denen noch nicht klar ist, wer sie "
......@@ -418,60 +420,60 @@ msgstr ""
msgid "Currently planned AKs"
msgstr "Aktuell geplante AKs"
#: AKSubmission/views.py:300
#: AKSubmission/views.py:305
msgid "Event inactive. Cannot create or update."
msgstr "Event inaktiv. Hinzufügen/Bearbeiten nicht möglich."
#: AKSubmission/views.py:325
#: AKSubmission/views.py:330
msgid "AK successfully created"
msgstr "AK erfolgreich angelegt"
#: AKSubmission/views.py:398
#: AKSubmission/views.py:404
msgid "AK successfully updated"
msgstr "AK erfolgreich aktualisiert"
#: AKSubmission/views.py:449
#: AKSubmission/views.py:455
#, python-brace-format
msgid "Added '{owner}' as new owner of '{ak.name}'"
msgstr "'{owner}' als neue Leitung von '{ak.name}' hinzugefügt"
#: AKSubmission/views.py:553
#: AKSubmission/views.py:558
msgid "No user selected"
msgstr "Keine Person ausgewählt"
#: AKSubmission/views.py:569
#: AKSubmission/views.py:574
msgid "Person Info successfully updated"
msgstr "Personen-Info erfolgreich aktualisiert"
#: AKSubmission/views.py:605
#: AKSubmission/views.py:610
msgid "AK Slot successfully added"
msgstr "AK-Slot erfolgreich angelegt"
#: AKSubmission/views.py:624
#: AKSubmission/views.py:629
msgid "You cannot edit a slot that has already been scheduled"
msgstr "Bereits geplante AK-Slots können nicht mehr bearbeitet werden"
#: AKSubmission/views.py:634
#: AKSubmission/views.py:639
msgid "AK Slot successfully updated"
msgstr "AK-Slot erfolgreich aktualisiert"
#: AKSubmission/views.py:652
#: AKSubmission/views.py:657
msgid "You cannot delete a slot that has already been scheduled"
msgstr "Bereits geplante AK-Slots können nicht mehr gelöscht werden"
#: AKSubmission/views.py:662
#: AKSubmission/views.py:667
msgid "AK Slot successfully deleted"
msgstr "AK-Slot erfolgreich angelegt"
#: AKSubmission/views.py:674
#: AKSubmission/views.py:679
msgid "Messages"
msgstr "Nachrichten"
#: AKSubmission/views.py:684
#: AKSubmission/views.py:689
msgid "Delete all messages"
msgstr "Alle Nachrichten löschen"
#: AKSubmission/views.py:711
#: AKSubmission/views.py:716
msgid "Message to organizers successfully saved"
msgstr "Nachricht an die Organisator*innen erfolgreich gespeichert"
......
......@@ -27,6 +27,55 @@
{% block imports %}
{% include "AKPlan/plan_akslot.html" %}
<script type="module">
const { createApp } = Vue
function getCurrentTimestamp() {
return Date.now() / 1000
}
createApp({
delimiters: ["[[", "]]"],
data() {
return {
featuredSlot: "{% if featured_slot %}true{% else %}false{% endif %}",
timer: null,
now: getCurrentTimestamp(),
akStart: "{{ featured_slot.start | date:'U' }}",
akEnd: "{{ featured_slot.end | date:'U' }}",
showBoxWithoutJS: false,
}
},
computed: {
showFeatured() {
return this.featuredSlot && this.now < this.akEnd
},
isBefore() {
return this.featuredSlot && this.now < this.akStart
},
isDuring() {
return this.featuredSlot && this.akStart < this.now && this.now < this.akEnd
},
timeUntilStart() {
return Math.ceil((this.akStart - this.now) / 60)
},
timeUntilEnd() {
return Math.floor((this.akEnd - this.now) / 60)
}
},
mounted: function () {
if(this.featuredSlot) {
this.timer = setInterval(() => {
this.now = getCurrentTimestamp()
}, 10000)
}
},
beforeUnmount() {
clearInterval(this.timer)
}
}).mount('#app')
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// CSRF Protection/Authentication
......@@ -128,30 +177,28 @@
<h2>{% if ak.wish %}{% trans "AK Wish" %}: {% endif %}{{ ak.name }}</h2>
{# Show current or upcoming slot featured in a box on top of the plage #}
{% if featured_slot_type != "NONE" %}
<div class="card border-success mt-3 mb-3">
<div class="card-body font-weight-bold">
{% if featured_slot_type == "CURRENT" %}
{% blocktrans with room=featured_slot.room %}
This AK currently takes place for another {{ featured_slot_remaining }} minute(s) in {{ room }}.
&nbsp;
{% endblocktrans %}
{% elif featured_slot_type == "UPCOMING" %}
{% blocktrans with room=featured_slot.room %}
This AK starts in {{ featured_slot_remaining }} minute(s) in {{ room }}.&nbsp;
{% endblocktrans %}
{% endif %}
<div id="app">
{# Show current or upcoming slot featured in a box on top of the plage #}
{% if featured_slot_type != "NONE" %}
<div class="card border-success mt-3 mb-3" v-show="showFeatured">
<div class="card-body font-weight-bold">
<span v-show="isDuring" style="{% if not featured_slot_type == "CURRENT" %}display:none;{% endif %}">
{% blocktrans with room=featured_slot.room %}This AK currently takes place for another <span v-html="timeUntilEnd">{{ featured_slot_remaining }}</span> minute(s) in {{ room }}.&nbsp;{% endblocktrans %}
</span>
<span v-show="isBefore" style="{% if not featured_slot_type == "UPCOMING" %}display:none;{% endif %}">
{% blocktrans with room=featured_slot.room %}This AK starts in <span v-html="timeUntilStart">{{ featured_slot_remaining }}</span> minute(s) in {{ room }}.&nbsp;{% endblocktrans %}
</span>
{% if "AKOnline"|check_app_installed and featured_slot.room.virtual and featured_slot.room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ featured_slot.room.virtual.url }}">
{% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a>
{% endif %}
{% if "AKOnline"|check_app_installed and featured_slot.room.virtual and featured_slot.room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ featured_slot.room.virtual.url }}">
{% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% endif %}
</div>
<table class="table table-borderless">
<tr>
......@@ -166,6 +213,16 @@
{% category_linked_badge ak.category ak.event.slug %}
</td>
</tr>
{% if ak.types.count > 0 %}
<tr>
<td>{% trans "Types" %}</td>
<td>
{% for type in ak.types.all %}
<span class="badge bg-info">{{ type }}</span>
{% endfor %}
</td>
</tr>
{% endif %}
{% if ak.track %}
<tr>
<td>{% trans 'Track' %}</td>
......
......@@ -9,6 +9,9 @@
<th>{% trans "Name" %}</th>
<th>{% trans "Who?" %}</th>
<th>{% trans 'Category' %}</th>
{% if show_types %}
<th>{% trans 'Types' %}</th>
{% endif %}
<th></th>
</tr>
</thead>
......@@ -37,6 +40,13 @@
{% endif %}
</td>
<td>{% category_linked_badge ak.category event.slug %}</td>
{% if show_types %}
<td>
{% for aktype in ak.types.all %}
<span class="badge bg-info">{{ aktype }}</span>
{% endfor %}
</td>
{% endif %}
<td class="text-end" style="white-space: nowrap;">
<a href="{{ ak.detail_url }}" data-bs-toggle="tooltip"
title="{% trans 'Details' %}"
......
......@@ -23,6 +23,13 @@
);
});
</script>
<style>
#id_present_helptext::after {
content: " ({% trans "only relevant for KIF-AKs - determines whether the AK appears in the slides for the closing plenary session" %})";
color: #6c757d;
}
</style>
{% endblock %}
{% block breadcrumbs %}
......
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
from AKSubmission.forms import AKSubmissionForm
class ModelViewTests(BasicViewTests, TestCase):
......@@ -46,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):
......@@ -146,7 +146,8 @@ 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 "
f"(should go to {add_redirect_url})")
def test_orga_message_submission(self):
"""
......@@ -200,7 +201,8 @@ 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")
......@@ -236,3 +238,39 @@ class ModelViewTests(BasicViewTests, TestCase):
msg_prefix=f"No correct redirect: {add_new_user_to_ak_url} (POST) -> {detail_url}")
self._assert_message(response, "Added 'New test owner' as new owner of 'Test AK Inhalt'")
self.assertEqual(AK.objects.get(pk=1).owners.count(), 2)
def test_visibility_requirements_in_submission_form(self):
"""
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})
self.assertIn('requirements', form.fields,
msg="Requirements field not present in form even though event has requirements")
event2 = Event.objects.create(name='Event without requirements',
slug='no_req',
start=datetime.now().astimezone(event.timezone),
end=datetime.now().astimezone(event.timezone),
active=True)
form2 = AKSubmissionForm(data={'name': 'Test AK', 'event': event2}, instance=None, initial={"event": event2})
self.assertNotIn('requirements', form2.fields,
msg="Requirements field should not be present for events without requirements")
def test_visibility_types_in_submission_form(self):
"""
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})
self.assertIn('types', form.fields,
msg="Requirements field not present in form even though event has requirements")
event2 = Event.objects.create(name='Event without types',
slug='no_types',
start=datetime.now().astimezone(event.timezone),
end=datetime.now().astimezone(event.timezone),
active=True)
form2 = AKSubmissionForm(data={'name': 'Test AK', 'event': event2}, instance=None, initial={"event": event2})
self.assertNotIn('types', form2.fields,
msg="Requirements field should not be present for events without types")
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
......@@ -59,7 +57,7 @@ class AKOverviewView(FilterByEventSlugMixin, ListView):
:rtype: QuerySet[AK]
"""
# Use prefetching and relation selection/joining to reduce the amount of necessary queries
return category.ak_set.select_related('event').prefetch_related('owners').all()
return category.ak_set.select_related('event').prefetch_related('owners').prefetch_related('types').all()
def get_active_category_name(self, context):
"""
......@@ -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,12 +122,14 @@ 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)
context['table_title'] = self.get_table_title(context)
context['show_types'] = self.event.aktype_set.count() > 0
# ==========================================================
# Display interest indication button?
# ==========================================================
......@@ -181,10 +181,13 @@ 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
# noinspection PyAttributeOutsideInit
# pylint: disable=attribute-defined-outside-init
self.category = get_object_or_404(AKCategory, pk=kwargs['category_pk'])
return super().dispatch(request, *args, **kwargs)
def get_active_category_name(self, context):
......@@ -207,11 +210,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):
......@@ -290,6 +294,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)
......@@ -349,6 +354,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
......@@ -498,7 +504,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
......
......@@ -10,7 +10,7 @@ setup.
### System Requirements
* Python 3.8+ incl. development tools
* Python3.11+ incl. development tools
* Virtualenv
* pdflatex & beamer
class (`texlive-latex-base texlive-latex-recommended texlive-latex-extra texlive-fonts-extra texlive-luatex`)
......
......@@ -8,14 +8,21 @@ if [ -z ${VIRTUAL_ENV+x} ]; then
fi
# enable really all warnings, some of them are silenced by default
if [[ "$@" == *"--all"* ]]; then
export PYTHONWARNINGS=all
fi
for arg in "$@"; do
if [[ "$arg" == "--all" ]]; then
export PYTHONWARNINGS=all
fi
done
# in case of checking production setup
if [[ "$@" == *"--prod"* ]]; then
export DJANGO_SETTINGS_MODULE=AKPlanning.settings_production
./manage.py check --deploy
fi
for arg in "$@"; do
if [[ "$arg" == "--prod" ]]; then
export DJANGO_SETTINGS_MODULE=AKPlanning.settings_production
./manage.py check --deploy
./manage.py makemigrations --dry-run --check
fi
done
# check the setup
./manage.py check
./manage.py makemigrations --dry-run --check
......@@ -10,7 +10,7 @@ rm -rf venv/
# Setup Python Environment
# Requires: Virtualenv, appropriate Python installation
virtualenv venv -p python3.9
virtualenv venv -p python3.11
source venv/bin/activate
pip install --upgrade setuptools pip wheel
pip install -r requirements.txt
......