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
  • komasolver
  • main
  • renovate/django-5.x
  • renovate/django-debug-toolbar-5.x
  • renovate/django_csp-4.x
  • renovate/djangorestframework-3.x
  • renovate/sphinxcontrib-apidoc-0.x
  • renovate/tzdata-2025.x
  • renovate/uwsgi-2.x
9 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/json-export-via-rest-framework
  • feature/json-schedule-import-tests
  • feature/preference-polling
  • feature/preference-polling-form
  • feature/preference-polling-form-rebased
  • feature/preference-polling-rebased
  • fix/add-room-import-only-once
  • main
  • merge-to-upstream
  • renovate/django-5.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-simple-history-3.x
  • renovate/mysqlclient-2.x
15 results
Show changes
Showing
with 682 additions and 349 deletions
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load bootstrap4 %} {% load django_bootstrap5 %}
{% load i18n %} {% load i18n %}
{% load l10n %} {% load l10n %}
{% load tz %} {% load tz %}
{% load static %} {% load static %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
...@@ -28,11 +28,9 @@ ...@@ -28,11 +28,9 @@
<div class="mb-3"> <div class="mb-3">
<form method="POST" class="post-form">{% csrf_token %} <form method="POST" class="post-form">{% csrf_token %}
{% bootstrap_form form %} {% bootstrap_form form %}
{% buttons %} <button type="submit" class="save btn btn-primary float-end">
<button type="submit" class="save btn btn-primary float-right"> {% fa6_icon "check" 'fas' %} {% trans "Submit" %}
{% fa5_icon "check" 'fas' %} {% trans "Submit" %} </button>
</button>
{% endbuttons %}
</form> </form>
</div> </div>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
{% load tz %} {% load tz %}
{% load static %} {% load static %}
{% load tags_AKPlan %} {% load tags_AKPlan %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% block title %}{% trans "Scheduling for" %} {{event}}{% endblock %} {% block title %}{% trans "Scheduling for" %} {{event}}{% endblock %}
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
<script src="{% static "common/vendor/sortable/Sortable.min.js" %}"></script> <script src="{% static "common/vendor/sortable/Sortable.min.js" %}"></script>
<script src="{% static "common/vendor/sortable/jquery-sortable.js" %}"></script> <script src="{% static "common/vendor/sortable/jquery-sortable.js" %}"></script>
<script src="{% static "AKScheduling/vendor/dragula/dragula.js" %}"></script>
<style> <style>
.ak-list { .ak-list {
...@@ -30,8 +31,18 @@ ...@@ -30,8 +31,18 @@
.track-delete { .track-delete {
cursor: pointer; cursor: pointer;
} }
.card-header {
cursor: move;
}
.card {
padding:0!important;
}
</style> </style>
<link rel="stylesheet" href="{% static "AKScheduling/vendor/dragula/dragula.css" %}">
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// CSRF Protection/Authentication // CSRF Protection/Authentication
...@@ -119,11 +130,6 @@ ...@@ -119,11 +130,6 @@
$('.ak-list').sortable(sortable_options); $('.ak-list').sortable(sortable_options);
// Display tooltips containing the tags for each list item (if available)
$('.ak-list li').each(function() {
$(this).tooltip({title: $(this).attr('data-title'), trigger: 'hover'});
});
// Add a new track container (and make usable for dragging) // Add a new track container (and make usable for dragging)
$('#btn-add-track').click(function () { $('#btn-add-track').click(function () {
var new_track_name = prompt("{% trans 'Name of new ak track' %}"); var new_track_name = prompt("{% trans 'Name of new ak track' %}");
...@@ -136,7 +142,7 @@ ...@@ -136,7 +142,7 @@
}, },
success: function (response) { success: function (response) {
console.log(response); console.log(response);
$('<div class="card border-success mb-3 track-container" style="width: 20rem;margin-right:20px;margin-bottom: 20px;"><div class="card-header"><span class="btn btn-danger float-right track-delete" data-track-id="' + response["id"] + '">{% fa5_icon "trash" "fas" %}</span><input class="track-name" data-track-id="None" type="text" value="' + response["name"] + '"></div><div class="card-body"><ul data-track-id="' + response["id"] + '" data-name="' + response["name"] + '" data-sync="true" class="ak-list"></ul></div></div>') $('<div class="card border-success mb-3 track-container" style="width: 20rem;margin-right:20px;margin-bottom: 20px;"><div class="card-header"><span class="btn btn-danger float-end track-delete" data-track-id="' + response["id"] + '">{% fa6_icon "trash" "fas" %}</span><input class="track-name" data-track-id="None" type="text" value="' + response["name"] + '"></div><div class="card-body"><ul data-track-id="' + response["id"] + '" data-name="' + response["name"] + '" data-sync="true" class="ak-list"></ul></div></div>')
.appendTo($("#workspace")) .appendTo($("#workspace"))
.find("ul").sortable(sortable_options) .find("ul").sortable(sortable_options)
}, },
...@@ -193,6 +199,13 @@ ...@@ -193,6 +199,13 @@
}); });
} }
}); });
// Make track containers sortable (when dragging the headers)
dragula([$('#workspace')[0]], {
moves: function (el, container, handle) {
return handle.classList.contains('card-header');
}
});
}); });
</script> </script>
{% endblock extrahead %} {% endblock extrahead %}
...@@ -201,7 +214,7 @@ ...@@ -201,7 +214,7 @@
<div class="mb-5"> <div class="mb-5">
<h3>{{ event }}: {% trans "Manage AK Tracks" %}</h3> <h3>{{ event }}: {% trans "Manage AK Tracks" %}</h3>
<a id="btn-add-track" href="#" class="btn btn-primary">{% fa5_icon "plus" "fas" %} {% trans "Add ak track" %}</a> <a id="btn-add-track" href="#" class="btn btn-primary">{% fa6_icon "plus" "fas" %} {% trans "Add ak track" %}</a>
</div> </div>
<div id="workspace" class="row" style=""> <div id="workspace" class="row" style="">
...@@ -210,8 +223,8 @@ ...@@ -210,8 +223,8 @@
<div class="card-body"> <div class="card-body">
<ul data-id="None" data-sync="false" class="ak-list"> <ul data-id="None" data-sync="false" class="ak-list">
{% for ak in aks_without_track %} {% for ak in aks_without_track %}
<li data-ak-id="{{ ak.pk }}" data-toggle="tooltip" data-placement="top" title="" data-title="{{ ak.tags_list }}"> <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> </li>
{% endfor %} {% endfor %}
</ul> </ul>
...@@ -221,16 +234,16 @@ ...@@ -221,16 +234,16 @@
{% for track in tracks %} {% for track in tracks %}
<div class="card border-success mb-3 track-container" style="width: 20rem;margin-right:20px;margin-bottom: 20px;"> <div class="card border-success mb-3 track-container" style="width: 20rem;margin-right:20px;margin-bottom: 20px;">
<div class="card-header"> <div class="card-header">
<span class="btn btn-danger float-right track-delete" data-track-id="{{ track.pk }}"> <span class="btn btn-danger float-end track-delete" data-track-id="{{ track.pk }}">
{% fa5_icon "trash" "fas" %} {% fa6_icon "trash" "fas" %}
</span> </span>
<input class="track-name" data-track-id="{{ track.pk }}" type="text" value="{{ track }}"> <input class="track-name" data-track-id="{{ track.pk }}" type="text" value="{{ track }}">
</div> </div>
<div class="card-body"> <div class="card-body">
<ul data-track-id="{{ track.pk }}" data-name="{{ track }}" data-sync="true" class="ak-list"> <ul data-track-id="{{ track.pk }}" data-name="{{ track }}" data-sync="true" class="ak-list">
{% for ak in track.ak_set.all %} {% for ak in track.aks_with_category %}
<li data-ak-id="{{ ak.pk }}" data-toggle="tooltip" data-placement="top" title="" data-title="{{ ak.tags_list }}"> <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> </li>
{% endfor %} {% endfor %}
</ul> </ul>
......
{% load compress %}
{% load tags_AKModel %} {% load tags_AKModel %}
{% load tags_AKPlan %} {% load tags_AKPlan %}
...@@ -6,8 +7,9 @@ ...@@ -6,8 +7,9 @@
{% load tz %} {% load tz %}
{% load static %} {% load static %}
{% load bootstrap4 %} {% load django_bootstrap5 %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load tags_AKModel %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
...@@ -19,9 +21,17 @@ ...@@ -19,9 +21,17 @@
<title>{% block title %}{% trans "Scheduling for" %} {{event}}{% endblock %}</title> <title>{% block title %}{% trans "Scheduling for" %} {{event}}{% endblock %}</title>
{# Load Bootstrap CSS and JavaScript as well as font awesome #} {# Load Bootstrap CSS and JavaScript as well as font awesome #}
{% bootstrap_css %} {% compress css %}
{% bootstrap_javascript jquery=True %} <link rel="stylesheet" type="text/x-scss" href="{% static 'common/vendor/bootswatch-lumen/theme.scss' %}">
{% fontawesome_5_static %} {% fontawesome_6_css %}
<link rel="stylesheet" href="{% static 'common/css/custom.css' %}">
{% endcompress %}
{% compress js %}
{% bootstrap_javascript %}
<script src="{% static 'common/vendor/jquery/jquery-3.6.3.min.js' %}"></script>
{% fontawesome_6_js %}
{% endcompress %}
{% include "AKModel/load_fullcalendar.html" %} {% include "AKModel/load_fullcalendar.html" %}
...@@ -82,7 +92,11 @@ ...@@ -82,7 +92,11 @@
}, },
//aspectRatio: 2, //aspectRatio: 2,
height: '100%', height: '100%',
themeSystem: 'bootstrap', themeSystem: 'bootstrap5',
buttonIcons: {
prev: 'ignore fa-solid fa-angle-left',
next: 'ignore fa-solid fa-angle-right',
},
// Adapt to user selected locale // Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}', locale: '{{ LANGUAGE_CODE }}',
initialView: 'resourceTimelineEventVert', initialView: 'resourceTimelineEventVert',
...@@ -143,6 +157,7 @@ ...@@ -143,6 +157,7 @@
$('#id_start').val(info.startStr); $('#id_start').val(info.startStr);
$('#id_end').val(info.endStr); $('#id_end').val(info.endStr);
$('#id_room').val(info.resource._resource.id); $('#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_duration').val(Math.abs(info.end-info.start)/1000/60/60);
$('#id_ak').val(""); $('#id_ak').val("");
$('#newAKSlotModal').modal('show'); $('#newAKSlotModal').modal('show');
...@@ -201,20 +216,24 @@ ...@@ -201,20 +216,24 @@
if(response.length > 0) { if(response.length > 0) {
// Update violations table // Update violations table
for(let i=0;i<response.length;i++) { for(let i=0;i<response.length;i++) {
if(response[i].manually_resolved) let icon_html = '';
table_html += '<tr class="text-muted"><td class="nowrap">{% fa5_icon "check" "fas" %} '; let muted_html = '';
if(response[i].manually_resolved) {
icon_html = '{% fa6_icon "check" "fas" %} ';
muted_html = 'text-muted';
}
else { else {
table_html += '<tr><td>';
unresolved_violations_count++; unresolved_violations_count++;
} }
if(response[i].level_display==='{% trans "Violation" %}') if(response[i].level_display==='{% trans "Violation" %}')
table_html += '{% fa5_icon "exclamation-triangle" "fas" %}'; icon_html += '{% fa6_icon "exclamation-triangle" "fas" %}';
else else
table_html += '{% fa5_icon "info-circle" "fas" %}'; icon_html += '{% fa6_icon "info-circle" "fas" %}';
table_html += '<tr class="'+ muted_html+ '"><td class="nowrap">' + icon_html;
table_html += "</td><td class='small'>" + response[i].type_display + "</td></tr>"; table_html += "</td><td class='small'>" + response[i].type_display + "</td></tr>";
table_html += "<tr><td colspan='2' class='small'>" + response[i].details + "</td></tr>" table_html += "<tr class='" + muted_html + "'><td colspan='2' class='small'>" + response[i].details + "</td></tr>"
} }
} }
else { else {
...@@ -224,9 +243,9 @@ ...@@ -224,9 +243,9 @@
// Update violation count badge // Update violation count badge
if(unresolved_violations_count > 0) if(unresolved_violations_count > 0)
$('#violationCountBadge').html(response.length).removeClass('badge-success').addClass('badge-warning'); $('#violationCountBadge').html(unresolved_violations_count).removeClass('bg-success').addClass('bg-warning');
else else
$('#violationCountBadge').html(0).removeClass('badge-warning').addClass('badge-success'); $('#violationCountBadge').html(0).removeClass('bg-warning').addClass('bg-success');
// Show violation list (potentially empty) in violations table // Show violation list (potentially empty) in violations table
$('#violationsTableBody').html(table_html); $('#violationsTableBody').html(table_html);
...@@ -287,9 +306,6 @@ ...@@ -287,9 +306,6 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">{% trans "Add slot" %}</h5> <h5 class="modal-title">{% trans "Add slot" %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form> <form>
...@@ -298,7 +314,7 @@ ...@@ -298,7 +314,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" id="newAKSlotModalSubmitButton">{% trans "Add" %}</button> <button type="button" class="btn btn-primary" id="newAKSlotModalSubmitButton">{% trans "Add" %}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
</div> </div>
</div> </div>
</div> </div>
...@@ -309,12 +325,12 @@ ...@@ -309,12 +325,12 @@
<div class="col"> <div class="col">
<h2 class="d-inline"> <h2 class="d-inline">
<button class="btn btn-outline-warning" id="reloadBtn" style="vertical-align: text-bottom;"> <button class="btn btn-outline-warning" id="reloadBtn" style="vertical-align: text-bottom;">
<span id="reloadBtnVisDefault">{% fa5_icon "redo" "fas" %}</span> <span id="reloadBtnVisDefault">{% fa6_icon "redo" "fas" %}</span>
</button> </button>
{% trans "Scheduling for" %} {{event}} {% trans "Scheduling for" %} {{event}}
</h2> </h2>
<h5 class="d-inline ml-2"> <h5 class="d-inline ml-2">
<a href="{% url 'admin:event_status' event.slug %}">{% trans "Event Status" %} {% fa5_icon "level-up-alt" "fas" %}</a> <a href="{% url 'admin:event_status' event.slug %}">{% trans "Event Status" %} {% fa6_icon "level-up-alt" "fas" %}</a>
</h5> </h5>
</div> </div>
</div> </div>
...@@ -325,29 +341,31 @@ ...@@ -325,29 +341,31 @@
<div class="col-md-4 col-lg-3 col-xl-2" id="sidebar"> <div class="col-md-4 col-lg-3 col-xl-2" id="sidebar">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#unscheduled-slots">{% trans "Unscheduled" %}</a> <a class="nav-link active" data-bs-toggle="tab" href="#unscheduled-slots">{% trans "Unscheduled" %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#violations"><span id="violationCountBadge" class="badge badge-success">0</span> {% trans "Violation(s)" %}</a> <a class="nav-link" data-bs-toggle="tab" href="#violations"><span id="violationCountBadge" class="badge bg-success">0</span> {% trans "Violation(s)" %}</a>
</li> </li>
</ul> </ul>
<div id="sidebarContent" class="tab-content"> <div id="sidebarContent" class="tab-content">
<div class="tab-pane fade show active" id="unscheduled-slots"> <div class="tab-pane fade show active" id="unscheduled-slots" style="height: 80vh;overflow-y: scroll;">
{% regroup slots_unscheduled by ak.track as slots_unscheduled_by_track_list %} {% regroup slots_unscheduled by ak.track as slots_unscheduled_by_track_list %}
{% for track_slots in slots_unscheduled_by_track_list %} {% for track_slots in slots_unscheduled_by_track_list %}
{% if track_slots.grouper %} {% if track_slots.grouper %}
<h5 class="mt-2">{{ track_slots.grouper }}</h5> <h5 class="mt-2">{{ track_slots.grouper }}</h5>
{% endif %} {% endif %}
{% for slot in track_slots.list %} {% for slot in track_slots.list %}
<div class="unscheduled-slot badge badge-primary" style='background-color: {{ slot.ak.category.color }}' <div class="unscheduled-slot badge" style='background-color: {% with slot.ak.category.color as color %} {% if color %}{{ color }}{% else %}#000000;{% endif %}{% endwith %}'
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 }}"}' data-details="{{ slot.ak.details }}">{{ slot.ak.short_name }} {% 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 }} ({{ slot.duration }} h)<br>{{ slot.ak.owners_list }}
{% endwith %}
</div> </div>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</div> </div>
<div class="tab-pane fade" id="violations"> <div class="tab-pane fade" id="violations">
<table class="table table-striped mt-4 mb-4"> <table class="table mt-4 mb-4">
<thead> <thead>
<tr> <tr>
<th>{% trans "Level" %}</th> <th>{% trans "Level" %}</th>
......
...@@ -6,21 +6,26 @@ ...@@ -6,21 +6,26 @@
{% load tz %} {% load tz %}
{% load static %} {% load static %}
{% load tags_AKPlan %} {% load tags_AKPlan %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block content %} {% block content %}
<h4 class="mt-4 mb-4">{% trans "AKs with public notes" %}</h4> <h4 class="mt-4 mb-4">{% trans "AKs with public notes" %}</h4>
{% for ak in aks_with_comment %} {% for ak in aks_with_comment %}
<a href="{% url "submit:ak_edit" event_slug=event.slug pk=ak.pk %}">{{ 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 %} {% empty %}
- -
{% endfor %} {% endfor %}
<h4 class="mt-4 mb-4">{% trans "AKs without availabilities" %}</h4> <h4 class="mt-4 mb-4">{% trans "AKs without availabilities" %}</h4>
{% for ak in aks_without_availabilities %} {% for ak in aks_without_availabilities %}
<a href="{% url "submit:ak_edit" event_slug=event.slug pk=ak.pk %}">{{ 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 %} {% empty %}
-<br> -<br>
{% endfor %} {% endfor %}
...@@ -30,7 +35,10 @@ ...@@ -30,7 +35,10 @@
<h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4> <h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4>
{% for ak in ak_wishes_with_slots %} {% for ak in ak_wishes_with_slots %}
<a href="{% url "submit:ak_detail" event_slug=event.slug pk=ak.pk %}">{{ 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 %} {% empty %}
-<br> -<br>
{% endfor %} {% endfor %}
...@@ -39,7 +47,9 @@ ...@@ -39,7 +47,9 @@
<h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4> <h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4>
{% for ak in aks_without_slots %} {% for ak in aks_without_slots %}
<a href="{% url "submit:ak_detail" event_slug=event.slug pk=ak.pk %}">{{ 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 %} {% empty %}
-<br> -<br>
{% endfor %} {% endfor %}
......
{% load i18n %}
<div class="text-center">
<a href="{% url 'admin:constraint-violations' slug=event.slug %}">
<h1>{{ constraint_violations_count }}</h1>
{% blocktrans count constraint_violations_count=constraint_violations_count %}
<h3>Constraint Violation</h3>
{% plural %}
<h3>Constraint Violations</h3>
{% endblocktrans %}
</a>
</div>
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<li> <li>
{% with group.grouper as ak %} {% with group.grouper as ak %}
{% if "AKSubmission"|check_app_installed %} {% if "AKSubmission"|check_app_installed %}
<a href="{% url 'submit:ak_detail' event_slug=ak.event.slug pk=ak.pk %}">{{ ak }}</a> <a href="{{ ak.detail_url }}">{{ ak }}</a>
{% else %} {% else %}
{{ ak }} {{ ak }}
{% endif %} {% endif %}
......
import json
from datetime import timedelta
from django.test import TestCase from django.test import TestCase
from AKModel.tests import BasicViewTests from django.utils import timezone
from AKModel.tests import BasicViewTests
from AKModel.models import AKSlot, Event, Room
class ModelViewTests(BasicViewTests, TestCase): class ModelViewTests(BasicViewTests, TestCase):
"""
Tests for AKScheduling
"""
fixtures = ['model.json'] fixtures = ['model.json']
VIEWS_STAFF_ONLY = [ VIEWS_STAFF_ONLY = [
# Views
('admin:schedule', {'event_slug': 'kif42'}), ('admin:schedule', {'event_slug': 'kif42'}),
('admin:slots_unscheduled', {'event_slug': 'kif42'}), ('admin:slots_unscheduled', {'event_slug': 'kif42'}),
('admin:constraint-violations', {'slug': 'kif42'}), ('admin:constraint-violations', {'slug': 'kif42'}),
...@@ -14,4 +23,66 @@ class ModelViewTests(BasicViewTests, TestCase): ...@@ -14,4 +23,66 @@ class ModelViewTests(BasicViewTests, TestCase):
('admin:autocreate-availabilities', {'event_slug': 'kif42'}), ('admin:autocreate-availabilities', {'event_slug': 'kif42'}),
('admin:tracks_manage', {'event_slug': 'kif42'}), ('admin:tracks_manage', {'event_slug': 'kif42'}),
('admin:enter-interest', {'event_slug': 'kif42', 'pk': 1}), ('admin:enter-interest', {'event_slug': 'kif42', 'pk': 1}),
# API (Read)
('model:scheduling-resources-list', {'event_slug': 'kif42'}, 403),
('model:scheduling-constraint-violations-list', {'event_slug': 'kif42'}, 403),
('model:scheduling-events', {'event_slug': 'kif42'}),
('model:scheduling-room-availabilities', {'event_slug': 'kif42'}),
('model:scheduling-default-slots', {'event_slug': 'kif42'}),
] ]
def test_scheduling_of_slot_update(self):
"""
Test rescheduling a slot to a different time or room
"""
self.client.force_login(self.admin_user)
event = Event.get_by_slug('kif42')
# Get the first already scheduled slot belonging to this event
slot = event.akslot_set.filter(start__isnull=False).first()
pk = slot.pk
room_id = slot.room_id
events_api_url = f"/kif42/api/scheduling-event/{pk}/"
# Create updated time
offset = timedelta(hours=1)
new_start_time = slot.start + offset
new_end_time = slot.end + offset
new_start_time_string = timezone.localtime(new_start_time, event.timezone).strftime("%Y-%m-%d %H:%M:%S")
new_end_time_string = timezone.localtime(new_end_time, event.timezone).strftime("%Y-%m-%d %H:%M:%S")
# Try API call
response = self.client.put(
events_api_url,
json.dumps({
'start': new_start_time_string,
'end': new_end_time_string,
'roomId': room_id,
}),
content_type = 'application/json'
)
self.assertEqual(response.status_code, 200, "PUT to API endpoint did not work")
# Make sure API call did update the slot as expected
slot = AKSlot.objects.get(pk=pk)
self.assertEqual(new_start_time, slot.start, "Update did not work")
# Test updating room
new_room = Room.objects.exclude(pk=room_id).first()
# Try second API call
response = self.client.put(
events_api_url,
json.dumps({
'start': new_start_time_string,
'end': new_end_time_string,
'roomId': new_room.pk,
}),
content_type = 'application/json'
)
self.assertEqual(response.status_code, 200, "Second PUT to API endpoint did not work")
# Make sure API call did update the slot as expected
slot = AKSlot.objects.get(pk=pk)
self.assertEqual(new_room.pk, slot.room.pk, "Update did not work")
...@@ -5,13 +5,17 @@ from django.urls import reverse_lazy ...@@ -5,13 +5,17 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView, DetailView, UpdateView from django.views.generic import ListView, DetailView, UpdateView
from AKModel.metaviews import status_manager
from AKModel.metaviews.status import TemplateStatusWidget
from AKModel.models import AKSlot, AKTrack, Event, AK, AKCategory from AKModel.models import AKSlot, AKTrack, Event, AK, AKCategory
from AKModel.views import AdminViewMixin, FilterByEventSlugMixin, EventSlugMixin, IntermediateAdminView from AKModel.metaviews.admin import EventSlugMixin, FilterByEventSlugMixin, AdminViewMixin, IntermediateAdminView
from AKScheduling.forms import AKInterestForm from AKScheduling.forms import AKInterestForm, AKAddSlotForm
from AKSubmission.forms import AKAddSlotForm
class UnscheduledSlotsAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): class UnscheduledSlotsAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
"""
Admin view: Get a list of all unscheduled slots
"""
template_name = "admin/AKScheduling/unscheduled.html" template_name = "admin/AKScheduling/unscheduled.html"
model = AKSlot model = AKSlot
context_object_name = "akslots" context_object_name = "akslots"
...@@ -26,12 +30,20 @@ class UnscheduledSlotsAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView ...@@ -26,12 +30,20 @@ class UnscheduledSlotsAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView
class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
"""
Admin view: Scheduler
View and adapt the schedule of an event. This view heavily uses JavaScript to display a calendar view plus
a list of unscheduled slots and to allow dragging slots in and into the calendar
"""
template_name = "admin/AKScheduling/scheduling.html" template_name = "admin/AKScheduling/scheduling.html"
model = AKSlot model = AKSlot
context_object_name = "slots_unscheduled" context_object_name = "slots_unscheduled"
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(start__isnull=True).select_related().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): 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)
...@@ -47,17 +59,29 @@ class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): ...@@ -47,17 +59,29 @@ class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
class TrackAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView): class TrackAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
"""
Admin view: Distribute AKs to tracks
Again using JavaScript, the user can here see a list of all AKs split-up by tracks and can move them to other or
even new tracks using drag and drop. The state is then automatically synchronized via API calls in the background
"""
template_name = "admin/AKScheduling/manage_tracks.html" template_name = "admin/AKScheduling/manage_tracks.html"
model = AKTrack model = AKTrack
context_object_name = "tracks" context_object_name = "tracks"
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["aks_without_track"] = self.event.ak_set.filter(track=None) context["aks_without_track"] = self.event.ak_set.select_related('category').filter(track=None)
return context return context
class ConstraintViolationsAdminView(AdminViewMixin, DetailView): class ConstraintViolationsAdminView(AdminViewMixin, DetailView):
"""
Admin view: Inspect and adjust all constraint violations of the event
This view populates a table of constraint violations via background API call (JavaScript), offers the option to
see details or edit each of them and provides an auto-reload feature.
"""
template_name = "admin/AKScheduling/constraint_violations.html" template_name = "admin/AKScheduling/constraint_violations.html"
model = Event model = Event
context_object_name = "event" context_object_name = "event"
...@@ -69,6 +93,10 @@ class ConstraintViolationsAdminView(AdminViewMixin, DetailView): ...@@ -69,6 +93,10 @@ class ConstraintViolationsAdminView(AdminViewMixin, DetailView):
class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView): class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
"""
Admin view: List all AKs that require special attention via scheduling, e.g., because of free-form comments,
since there are slots even though it is a wish, or no slots even though it is an AK etc.
"""
template_name = "admin/AKScheduling/special_attention.html" template_name = "admin/AKScheduling/special_attention.html"
model = Event model = Event
context_object_name = "event" context_object_name = "event"
...@@ -77,12 +105,16 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView): ...@@ -77,12 +105,16 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["title"] = f"{_('AKs requiring special attention for')} {context['event']}" context["title"] = f"{_('AKs requiring special attention for')} {context['event']}"
aks = AK.objects.filter(event=context["event"]).annotate(Count('owners', distinct=True)).annotate(Count('akslot', distinct=True)).annotate(Count('availabilities', distinct=True)) # Load all "special" AKs from the database using annotations to reduce the amount of necessary queries
aks = (AK.objects.filter(event=context["event"]).annotate(Count('owners', distinct=True))
.annotate(Count('akslot', distinct=True)).annotate(Count('availabilities', distinct=True)))
aks_with_comment = [] aks_with_comment = []
ak_wishes_with_slots = [] ak_wishes_with_slots = []
aks_without_availabilities = [] aks_without_availabilities = []
aks_without_slots = [] aks_without_slots = []
# Loop over all AKs of this event and identify all relevant factors that make the AK "special" and add them to
# the respective lists if the AK fullfills an condition
for ak in aks: for ak in aks:
if ak.notes != "": if ak.notes != "":
aks_with_comment.append(ak) aks_with_comment.append(ak)
...@@ -105,6 +137,14 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView): ...@@ -105,6 +137,14 @@ class SpecialAttentionAKsAdminView(AdminViewMixin, DetailView):
class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMixin, UpdateView): class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMixin, UpdateView):
"""
Admin view: Form view to quickly store information about the interest in an AK
(e.g., during presentation of the AK list)
The view offers a field to update interest and manually set a comment for the current AK, but also features links
to the AKs before and probably coming up next, as well as links to other AKs sorted by category, for quick
and hazzle-free navigation during the AK presentation
"""
template_name = "admin/AKScheduling/interest.html" template_name = "admin/AKScheduling/interest.html"
model = AK model = AK
context_object_name = "ak" context_object_name = "ak"
...@@ -114,6 +154,13 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi ...@@ -114,6 +154,13 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
def get_success_url(self): def get_success_url(self):
return self.request.path 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["title"] = f"{_('Enter interest')}" context["title"] = f"{_('Enter interest')}"
...@@ -127,13 +174,16 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi ...@@ -127,13 +174,16 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
last_ak = None last_ak = None
next_is_next = False next_is_next = False
# Building the right navigation is a bit tricky since wishes have to be treated as an own category here
# Hence, depending on the AK we are currently at (displaying the form for) we need to either:
# Find other AK wishes (regardless of the category)... # Find other AK wishes (regardless of the category)...
if context['ak'].wish: if context['ak'].wish:
other_aks = [ak for ak in context['event'].ak_set.all() if ak.wish] other_aks = [ak for ak in context['event'].ak_set.prefetch_related('owners').all() if ak.wish]
# or other AKs of this category # or other AKs of this category
else: else:
other_aks = [ak for ak in context['ak'].category.ak_set.all() if not ak.wish] other_aks = [ak for ak in context['ak'].category.ak_set.prefetch_related('owners').all() if not ak.wish]
# Use that list of other AKs belonging to this category to identify the previous and next AK (if any)
for other_ak in other_aks: for other_ak in other_aks:
if next_is_next: if next_is_next:
context['next_ak'] = other_ak context['next_ak'] = other_ak
...@@ -143,15 +193,18 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi ...@@ -143,15 +193,18 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
next_is_next = True next_is_next = True
last_ak = other_ak last_ak = other_ak
for category in context['event'].akcategory_set.all(): # Gather information for link lists for all categories (and wishes)
for category in context['event'].akcategory_set.prefetch_related('ak_set').all():
aks_for_category = [] aks_for_category = []
for ak in category.ak_set.all(): for ak in category.ak_set.prefetch_related('owners').all():
if ak.wish: if ak.wish:
ak_wishes.append(ak) ak_wishes.append(ak)
else: else:
aks_for_category.append(ak) aks_for_category.append(ak)
categories_with_aks.append((category, aks_for_category)) categories_with_aks.append((category, aks_for_category))
# Make sure wishes have the right order (since the list was filled category by category before, this requires
# explicitly reordering them by their primary key)
ak_wishes.sort(key=lambda x: x.pk) ak_wishes.sort(key=lambda x: x.pk)
categories_with_aks.append( categories_with_aks.append(
(AKCategory(name=_("Wishes"), pk=0, description="-"), ak_wishes)) (AKCategory(name=_("Wishes"), pk=0, description="-"), ak_wishes))
...@@ -162,6 +215,16 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi ...@@ -162,6 +215,16 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView): class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView):
"""
Admin action view: Allow to delete all unscheduled slots for wishes
The view will render a preview of all slots that are affected by this. It is not possible to manually choose
which slots should be deleted (either all or none) and the functionality will therefore delete slots that were
created in the time between rendering of the preview and running the action ofter confirmation as well.
Due to the automated slot cleanup functionality for wishes in the AKSubmission app, this functionality should be
rarely needed/used
"""
title = _('Cleanup: Delete unscheduled slots for wishes') title = _('Cleanup: Delete unscheduled slots for wishes')
def get_success_url(self): def get_success_url(self):
...@@ -181,6 +244,15 @@ class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView): ...@@ -181,6 +244,15 @@ class WishSlotCleanupView(EventSlugMixin, IntermediateAdminView):
class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView): class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView):
"""
Admin action view: Allow to automatically create default availabilities (event start to end) for all AKs without
any manually specified availability information
The view will render a preview of all AKs that are affected by this. It is not possible to manually choose
which AKs should be affected (either all or none) and the functionality will therefore create availability entries
for AKs that were created in the time between rendering of the preview and running the action ofter confirmation
as well.
"""
title = _('Create default availabilities for AKs') title = _('Create default availabilities for AKs')
def get_success_url(self): def get_success_url(self):
...@@ -195,6 +267,8 @@ class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView): ...@@ -195,6 +267,8 @@ class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView):
) )
def form_valid(self, form): def form_valid(self, form):
# Local import to prevent cyclic imports
# pylint: disable=import-outside-toplevel
from AKModel.availability.models import Availability from AKModel.availability.models import Availability
success_count = 0 success_count = 0
...@@ -203,7 +277,7 @@ class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView): ...@@ -203,7 +277,7 @@ class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView):
availability = Availability.with_event_length(event=self.event, ak=ak) availability = Availability.with_event_length(event=self.event, ak=ak)
availability.save() availability.save()
success_count += 1 success_count += 1
except: except: # pylint: disable=bare-except
messages.add_message( messages.add_message(
self.request, messages.WARNING, self.request, messages.WARNING,
_("Could not create default availabilities for AK: {ak}").format(ak=ak) _("Could not create default availabilities for AK: {ak}").format(ak=ak)
...@@ -214,3 +288,22 @@ class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView): ...@@ -214,3 +288,22 @@ class AvailabilityAutocreateView(EventSlugMixin, IntermediateAdminView):
_("Created default availabilities for {count} AKs").format(count=success_count) _("Created default availabilities for {count} AKs").format(count=success_count)
) )
return super().form_valid(form) return super().form_valid(form)
@status_manager.register(name="scheduling_constraint_violations")
class CVWidget(TemplateStatusWidget):
"""
Status page widget: Constraint violations
"""
required_context_type = "event"
title = _("Constraint Violations")
template_name = "admin/AKScheduling/status/cvs.html"
def render_status(self, context: {}) -> str:
return "success" if context["constraint_violations_count"] == 0 else "warning"
def get_context_data(self, context) -> dict:
context = super().get_context_data(context)
context["constraint_violations_count"] = (context["event"].constraintviolation_set
.filter(manually_resolved=False).count())
return context
# Register your models here.
from datetime import datetime
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from rest_framework.response import Response from rest_framework.response import Response
from django.utils.datetime_safe import datetime
from AKModel.models import AK from AKModel.models import AK
...@@ -17,17 +18,21 @@ def ak_interest_indication_active(event, current_timestamp): ...@@ -17,17 +18,21 @@ def ak_interest_indication_active(event, current_timestamp):
:return: True if indication is allowed, False if not :return: True if indication is allowed, False if not
:rtype: Bool :rtype: Bool
""" """
return event.active and (event.interest_start is None or (event.interest_start <= current_timestamp and ( return (event.active and event.interest_start is not None and event.interest_end is not None
event.interest_end is None or current_timestamp <= event.interest_end))) and event.interest_start <= current_timestamp <= event.interest_end)
@api_view(['POST']) @api_view(['POST'])
def increment_interest_counter(request, event_slug, pk, **kwargs): def increment_interest_counter(request, event_slug, pk, **kwargs):
""" """
Increment interest counter for AK Increment interest counter for AK
This view either returns an HTTP 200 if the counter was incremented,
an HTTP 403 if indicating interest is currently not allowed,
or an HTTP 404 if there is no matching AK for the given primary key and event slug.
""" """
try: try:
ak = AK.objects.get(pk=pk) ak = AK.objects.get(pk=pk, event__slug=event_slug)
# Check whether interest indication is currently allowed # Check whether interest indication is currently allowed
current_timestamp = datetime.now().astimezone(ak.event.timezone) current_timestamp = datetime.now().astimezone(ak.event.timezone)
if ak_interest_indication_active(ak.event, current_timestamp): if ak_interest_indication_active(ak.event, current_timestamp):
......
...@@ -2,4 +2,7 @@ from django.apps import AppConfig ...@@ -2,4 +2,7 @@ from django.apps import AppConfig
class AksubmissionConfig(AppConfig): class AksubmissionConfig(AppConfig):
"""
App configuration (default, only specifies name of the app)
"""
name = 'AKSubmission' name = 'AKSubmission'
"""
Submission-specific forms
"""
import itertools import itertools
import re import re
...@@ -7,10 +11,21 @@ from django.utils.translation import gettext_lazy as _ ...@@ -7,10 +11,21 @@ from django.utils.translation import gettext_lazy as _
from AKModel.availability.forms import AvailabilitiesFormMixin from AKModel.availability.forms import AvailabilitiesFormMixin
from AKModel.availability.models import Availability from AKModel.availability.models import Availability
from AKModel.models import AK, AKOwner, AKCategory, AKRequirement, AKSlot, AKOrgaMessage, Event from AKModel.models import AK, AKOwner, AKCategory, AKRequirement, AKSlot, AKOrgaMessage, AKType
class AKForm(AvailabilitiesFormMixin, forms.ModelForm): class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
"""
Base form to add and edit AKs
Contains suitable widgets for the different data types, restricts querysets (e.g., of requirements) to entries
belonging to the event this AK belongs to.
Prepares initial slot creation (by accepting multiple input formats and a list of slots to generate),
automatically generate short names and wiki links if necessary
Will be modified/used by :class:`AKSubmissionForm` (that allows to add slots and excludes links)
and :class:`AKWishForm`
"""
required_css_class = 'required' required_css_class = 'required'
split_string = re.compile('[,;]') split_string = re.compile('[,;]')
...@@ -18,11 +33,11 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -18,11 +33,11 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
model = AK model = AK
fields = ['name', fields = ['name',
'short_name', 'short_name',
'link',
'protocol_link', 'protocol_link',
'owners', 'owners',
'description', 'description',
'category', 'category',
'types',
'reso', 'reso',
'present', 'present',
'requirements', 'requirements',
...@@ -34,6 +49,7 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -34,6 +49,7 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
widgets = { widgets = {
'requirements': forms.CheckboxSelectMultiple, 'requirements': forms.CheckboxSelectMultiple,
'types': forms.CheckboxSelectMultiple,
'event': forms.HiddenInput, 'event': forms.HiddenInput,
} }
...@@ -46,16 +62,15 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -46,16 +62,15 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
self.fields["conflicts"].widget.attrs = {'class': 'chosen-select'} self.fields["conflicts"].widget.attrs = {'class': 'chosen-select'}
self.fields["prerequisites"].widget.attrs = {'class': 'chosen-select'} self.fields["prerequisites"].widget.attrs = {'class': 'chosen-select'}
help_tags_addition = _('Separate multiple tags with comma or semicolon')
# Add text fields for tags
self.fields["tags_raw"] = forms.CharField(
required=False,
label=AK.tags.field.verbose_name,
help_text=f"{AK.tags.field.help_text} ({help_tags_addition})")
self.fields['category'].queryset = AKCategory.objects.filter(event=self.initial.get('event')) 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')) 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')
self.fields['prerequisites'].queryset = AK.objects.filter(event=self.initial.get('event')).exclude( self.fields['prerequisites'].queryset = AK.objects.filter(event=self.initial.get('event')).exclude(
pk=self.instance.pk) pk=self.instance.pk)
self.fields['conflicts'].queryset = AK.objects.filter(event=self.initial.get('event')).exclude( self.fields['conflicts'].queryset = AK.objects.filter(event=self.initial.get('event')).exclude(
...@@ -65,7 +80,14 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -65,7 +80,14 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
@staticmethod @staticmethod
def _clean_duration(duration): def _clean_duration(duration):
# Handle different duration formats (h:mm and decimal comma instead of point) """
Clean/convert input format for the duration(s) of the slot(s)
Handle different duration formats (h:mm and decimal comma instead of point)
:param duration: raw input, either with ":", "," or "."
:return: normalized duration (point-separated hour float)
"""
if ":" in duration: if ":" in duration:
h, m = duration.split(":") h, m = duration.split(":")
duration = int(h) + int(m) / 60 duration = int(h) + int(m) / 60
...@@ -73,45 +95,47 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -73,45 +95,47 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
duration = float(duration.replace(",", ".")) duration = float(duration.replace(",", "."))
try: try:
float(duration) duration = float(duration)
except ValueError: except ValueError as exc:
raise ValidationError( raise ValidationError(
_('"%(duration)s" is not a valid duration'), _('"%(duration)s" is not a valid duration'),
code='invalid', code='invalid',
params={'duration': duration}, params={'duration': duration},
) ) from exc
return duration return duration
def clean(self): def clean(self):
"""
Normalize/clean inputs
Generate a (not yet used) short name if field was left blank,
create a list of normalized slot durations
:return: cleaned inputs
"""
cleaned_data = super().clean() cleaned_data = super().clean()
# Generate short name if not given # Generate short name if not given
short_name = self.cleaned_data["short_name"] short_name = self.cleaned_data["short_name"]
if len(short_name) == 0: if len(short_name) == 0:
short_name = self.cleaned_data['name'] short_name = self.cleaned_data['name']
# First try to split AK name at positions with semantic value (e.g., where the full name is separated
# by a ':'), if not possible, do a hard cut at the maximum specified length
short_name = short_name.partition(':')[0] short_name = short_name.partition(':')[0]
short_name = short_name.partition(' - ')[0] short_name = short_name.partition(' - ')[0]
short_name = short_name.partition(' (')[0] short_name = short_name.partition(' (')[0]
short_name = short_name[:AK._meta.get_field('short_name').max_length] short_name = short_name[:AK._meta.get_field('short_name').max_length]
# Check whether this short name already exists...
for i in itertools.count(1): for i in itertools.count(1):
# ...and either use it...
if not AK.objects.filter(short_name=short_name, event=self.cleaned_data["event"]).exists(): if not AK.objects.filter(short_name=short_name, event=self.cleaned_data["event"]).exists():
break break
# ... or postfix a number starting at 1 and growing until an unused short name is found
digits = len(str(i)) digits = len(str(i))
short_name = '{}-{}'.format(short_name[:-(digits + 1)], i) short_name = f'{short_name[:-(digits + 1)]}-{i}'
cleaned_data["short_name"] = short_name cleaned_data["short_name"] = short_name
# Generate wiki link
if self.cleaned_data["event"].base_url:
link = self.cleaned_data["event"].base_url + self.cleaned_data["name"].replace(" ", "_")
# Truncate links longer than 200 characters (default length of URL fields in django)
self.cleaned_data["link"] = link[:200]
# Get tag names from raw tags
cleaned_data["tag_names"] = [name.strip().lower() for name
in self.split_string.split(cleaned_data["tags_raw"])
if name.strip() != '']
# Get durations from raw durations field # Get durations from raw durations field
if "durations" in cleaned_data: if "durations" in cleaned_data:
cleaned_data["durations"] = [self._clean_duration(d) for d in self.cleaned_data["durations"].split()] cleaned_data["durations"] = [self._clean_duration(d) for d in self.cleaned_data["durations"].split()]
...@@ -119,44 +143,63 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm): ...@@ -119,44 +143,63 @@ class AKForm(AvailabilitiesFormMixin, forms.ModelForm):
class AKSubmissionForm(AKForm): class AKSubmissionForm(AKForm):
"""
Form for Submitting new AKs
Is a special variant of :class:`AKForm` that does not allow to manually edit wiki and protocol links and enforces
the generation of at least one slot.
"""
class Meta(AKForm.Meta): class Meta(AKForm.Meta):
exclude = ['link', 'protocol_link'] # 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): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Add field for durations # Add field for durations (cleaning will be handled by parent class)
self.fields["durations"] = forms.CharField( self.fields["durations"] = forms.CharField(
widget=forms.Textarea, widget=forms.Textarea,
label=_("Duration(s)"), label=_("Duration(s)"),
help_text=_( help_text=_(
"Enter at least one planned duration (in hours). If your AK should have multiple slots, use multiple lines"), "Enter at least one planned duration (in hours). "
initial= "If your AK should have multiple slots, use multiple lines"),
self.initial.get('event').default_slot initial=self.initial.get('event').default_slot
) )
def clean_availabilities(self): 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() availabilities = super().clean_availabilities()
# If the user did not specify availabilities assume the full event duration is possible
if len(availabilities) == 0: if len(availabilities) == 0:
availabilities.append(Availability.with_event_length(event=self.cleaned_data["event"])) availabilities.append(Availability.with_event_length(event=self.cleaned_data["event"]))
return availabilities return availabilities
class AKEditForm(AKForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Add existing tags to tag raw field
self.fields["tags_raw"].initial = "; ".join(str(tag) for tag in self.instance.tags.all())
class AKWishForm(AKForm): class AKWishForm(AKForm):
"""
Form for submitting or editing wishes
Is a special variant of :class:`AKForm` that does not allow to specify owner(s) or
manually edit wiki and protocol links
"""
class Meta(AKForm.Meta): class Meta(AKForm.Meta):
exclude = ['owners', 'link', 'protocol_link'] # 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): class AKOwnerForm(forms.ModelForm):
"""
Form to create/edit AK owners
"""
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
...@@ -168,6 +211,9 @@ class AKOwnerForm(forms.ModelForm): ...@@ -168,6 +211,9 @@ class AKOwnerForm(forms.ModelForm):
class AKDurationForm(forms.ModelForm): class AKDurationForm(forms.ModelForm):
"""
Form to add an additional slot to a given AK
"""
class Meta: class Meta:
model = AKSlot model = AKSlot
fields = ['duration', 'ak', 'event'] fields = ['duration', 'ak', 'event']
...@@ -178,6 +224,9 @@ class AKDurationForm(forms.ModelForm): ...@@ -178,6 +224,9 @@ class AKDurationForm(forms.ModelForm):
class AKOrgaMessageForm(forms.ModelForm): class AKOrgaMessageForm(forms.ModelForm):
"""
Form to create a confidential message to the organizers belonging to a given AK
"""
class Meta: class Meta:
model = AKOrgaMessage model = AKOrgaMessage
fields = ['ak', 'text', 'event'] fields = ['ak', 'text', 'event']
...@@ -186,14 +235,3 @@ class AKOrgaMessageForm(forms.ModelForm): ...@@ -186,14 +235,3 @@ class AKOrgaMessageForm(forms.ModelForm):
'event': forms.HiddenInput, 'event': forms.HiddenInput,
'text': forms.Textarea, 'text': forms.Textarea,
} }
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)
def __init__(self, event):
super().__init__()
self.fields['ak'] = forms.ModelChoiceField(event.ak_set.all(), label=_("AK"))
...@@ -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: 2022-12-27 00:42+0100\n" "POT-Creation-Date: 2025-03-25 15:58+0100\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,20 +17,16 @@ msgstr "" ...@@ -17,20 +17,16 @@ 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"
#: AKSubmission/forms.py:49 #: AKSubmission/forms.py:101
msgid "Separate multiple tags with comma or semicolon"
msgstr "Mehrere Tags mit Komma oder Semikolon trennen"
#: AKSubmission/forms.py:79
#, python-format #, python-format
msgid "\"%(duration)s\" is not a valid duration" msgid "\"%(duration)s\" is not a valid duration"
msgstr "\"%(duration)s\" ist keine gültige Dauer" msgstr "\"%(duration)s\" ist keine gültige Dauer"
#: AKSubmission/forms.py:130 #: AKSubmission/forms.py:164
msgid "Duration(s)" msgid "Duration(s)"
msgstr "Dauer(n)" msgstr "Dauer(n)"
#: AKSubmission/forms.py:132 #: AKSubmission/forms.py:166
msgid "" msgid ""
"Enter at least one planned duration (in hours). If your AK should have " "Enter at least one planned duration (in hours). If your AK should have "
"multiple slots, use multiple lines" "multiple slots, use multiple lines"
...@@ -38,37 +34,10 @@ msgstr "" ...@@ -38,37 +34,10 @@ msgstr ""
"Mindestens eine geplante Dauer (in Stunden) angeben. Wenn der AK mehrere " "Mindestens eine geplante Dauer (in Stunden) angeben. Wenn der AK mehrere "
"Slots haben soll, mehrere Zeilen verwenden" "Slots haben soll, mehrere Zeilen verwenden"
#: AKSubmission/forms.py:192
#: AKSubmission/templates/AKSubmission/ak_detail.html:315
msgid "Start"
msgstr "Start"
#: AKSubmission/forms.py:193
#: AKSubmission/templates/AKSubmission/ak_detail.html:316
msgid "End"
msgstr "Ende"
#: AKSubmission/forms.py:194
#: AKSubmission/templates/AKSubmission/ak_detail.html:245
#: AKSubmission/templates/AKSubmission/akslot_delete.html:35
msgid "Duration"
msgstr "Dauer"
#: AKSubmission/forms.py:195
#: AKSubmission/templates/AKSubmission/ak_detail.html:247
msgid "Room"
msgstr "Raum"
#: AKSubmission/forms.py:199
#: AKSubmission/templates/AKSubmission/ak_history.html:11
#: AKSubmission/templates/AKSubmission/akslot_delete.html:31
msgid "AK"
msgstr "AK"
#: AKSubmission/templates/AKSubmission/ak_detail.html:22 #: AKSubmission/templates/AKSubmission/ak_detail.html:22
#: AKSubmission/templates/AKSubmission/ak_edit.html:13 #: AKSubmission/templates/AKSubmission/ak_edit.html:13
#: AKSubmission/templates/AKSubmission/ak_history.html:16 #: AKSubmission/templates/AKSubmission/ak_history.html:16
#: AKSubmission/templates/AKSubmission/ak_overview.html:29 #: AKSubmission/templates/AKSubmission/ak_overview.html:22
#: AKSubmission/templates/AKSubmission/akmessage_add.html:13 #: AKSubmission/templates/AKSubmission/akmessage_add.html:13
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:12 #: AKSubmission/templates/AKSubmission/akowner_create_update.html:12
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:12 #: AKSubmission/templates/AKSubmission/akslot_add_update.html:12
...@@ -77,191 +46,211 @@ msgstr "AK" ...@@ -77,191 +46,211 @@ msgstr "AK"
#: AKSubmission/templates/AKSubmission/submission_not_configured.html:11 #: AKSubmission/templates/AKSubmission/submission_not_configured.html:11
#: AKSubmission/templates/AKSubmission/submission_overview.html:7 #: AKSubmission/templates/AKSubmission/submission_overview.html:7
#: AKSubmission/templates/AKSubmission/submission_overview.html:11 #: AKSubmission/templates/AKSubmission/submission_overview.html:11
#: AKSubmission/templates/AKSubmission/submission_overview.html:41 #: AKSubmission/templates/AKSubmission/submission_overview.html:36
#: AKSubmission/templates/AKSubmission/submit_new.html:34 #: AKSubmission/templates/AKSubmission/submit_new.html:38
#: AKSubmission/templates/AKSubmission/submit_new_wish.html:13 #: AKSubmission/templates/AKSubmission/submit_new_wish.html:13
msgid "AK Submission" msgid "AK Submission"
msgstr "AK-Eintragung" 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 #: AKSubmission/templates/AKSubmission/ak_interest_script.html:50
msgid "Interest indication currently not allowed. Sorry." msgid "Interest indication currently not allowed. Sorry."
msgstr "Interessenangabe aktuell nicht erlaubt. 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 #: AKSubmission/templates/AKSubmission/ak_interest_script.html:52
msgid "Could not save your interest. Sorry." msgid "Could not save your interest. Sorry."
msgstr "Interesse konnte nicht gespeichert werden. Sorry." msgstr "Interesse konnte nicht gespeichert werden. Sorry."
#: AKSubmission/templates/AKSubmission/ak_detail.html:100 #: AKSubmission/templates/AKSubmission/ak_detail.html:149
msgid "Interest" msgid "Interest"
msgstr "Interesse" msgstr "Interesse"
#: AKSubmission/templates/AKSubmission/ak_detail.html:102 #: AKSubmission/templates/AKSubmission/ak_detail.html:151
#: AKSubmission/templates/AKSubmission/ak_table.html:57 #: AKSubmission/templates/AKSubmission/ak_table.html:65
msgid "Show Interest" msgid "Show Interest"
msgstr "Interesse bekunden" msgstr "Interesse bekunden"
#: AKSubmission/templates/AKSubmission/ak_detail.html:108 #: AKSubmission/templates/AKSubmission/ak_detail.html:157
#: AKSubmission/templates/AKSubmission/ak_table.html:48 #: AKSubmission/templates/AKSubmission/ak_table.html:56
msgid "Open external link" msgid "Open external link"
msgstr "Externen Link öffnen" msgstr "Externen Link öffnen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:113 #: AKSubmission/templates/AKSubmission/ak_detail.html:162
msgid "Open protocol link" msgid "Open protocol link"
msgstr "Protokolllink öffnen" 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:19
#: AKSubmission/templates/AKSubmission/ak_history.html:31 #: AKSubmission/templates/AKSubmission/ak_history.html:31
msgid "History" msgid "History"
msgstr "Versionsgeschichte" 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:8
#: AKSubmission/templates/AKSubmission/akmessage_add.html:16 #: AKSubmission/templates/AKSubmission/akmessage_add.html:16
#: AKSubmission/templates/AKSubmission/akmessage_add.html:22 #: AKSubmission/templates/AKSubmission/akmessage_add.html:22
msgid "Add confidential message to organizers" msgid "Add confidential message to organizers"
msgstr "Sende eine private Nachricht an das Organisationsteam" msgstr "Sende eine private Nachricht an das Organisationsteam"
#: AKSubmission/templates/AKSubmission/ak_detail.html:124 #: AKSubmission/templates/AKSubmission/ak_detail.html:173
#: AKSubmission/templates/AKSubmission/ak_detail.html:275 #: AKSubmission/templates/AKSubmission/ak_detail.html:326
#: AKSubmission/templates/AKSubmission/ak_edit.html:16 #: AKSubmission/templates/AKSubmission/ak_edit.html:16
#: AKSubmission/templates/AKSubmission/ak_table.html:53 #: AKSubmission/templates/AKSubmission/ak_table.html:61
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" 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_history.html:31
#: AKSubmission/templates/AKSubmission/ak_table.html:35 #: AKSubmission/templates/AKSubmission/ak_table.html:37
msgid "AK Wish" msgid "AK Wish"
msgstr "AK-Wunsch" msgstr "AK-Wunsch"
#: AKSubmission/templates/AKSubmission/ak_detail.html:136 #: AKSubmission/templates/AKSubmission/ak_detail.html:186
#, python-format #, python-format
msgid "" msgid ""
"\n" "This AK currently takes place for another <span v-html=\"timeUntilEnd\">"
" This AK currently takes place for another " "%(featured_slot_remaining)s</span> minute(s) in %(room)s.&nbsp;"
"%(featured_slot_remaining)s minute(s) in %(room)s.\n"
" &nbsp;\n"
" "
msgstr "" msgstr ""
"\n" "Dieser AK findet noch <span v-html=\"timeUntilEnd\">"
" Dieser AK findet noch %(featured_slot_remaining)s " "%(featured_slot_remaining)s</span> Minute(n) in %(room)s statt.&nbsp;\n"
"Minute(n) in %(room)s statt.&nbsp;\n"
" " " "
#: AKSubmission/templates/AKSubmission/ak_detail.html:142 #: AKSubmission/templates/AKSubmission/ak_detail.html:189
#, python-format #, python-format
msgid "" msgid ""
"\n" "This AK starts in <span v-html=\"timeUntilStart\">"
" This AK starts in %(featured_slot_remaining)s " "%(featured_slot_remaining)s</span> minute(s) in %(room)s.&nbsp;"
"minute(s) in %(room)s.&nbsp;\n"
" "
msgstr "" msgstr ""
"\n" "Dieser AK beginnt in <span v-html=\"timeUntilStart\">"
" This AK beginnt in %(featured_slot_remaining)s " "%(featured_slot_remaining)s</span> Minute(n) in %(room)s.&nbsp;\n"
"Minute(n) in %(room)s.&nbsp;\n"
" " " "
#: AKSubmission/templates/AKSubmission/ak_detail.html:149 #: AKSubmission/templates/AKSubmission/ak_detail.html:194
#: AKSubmission/templates/AKSubmission/ak_detail.html:283 #: AKSubmission/templates/AKSubmission/ak_detail.html:334
msgid "Go to virtual room" msgid "Go to virtual room"
msgstr "Zum virtuellen Raum" 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 #: AKSubmission/templates/AKSubmission/ak_table.html:10
msgid "Who?" msgid "Who?"
msgstr "Wer?" 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_history.html:36
#: AKSubmission/templates/AKSubmission/ak_table.html:11 #: AKSubmission/templates/AKSubmission/ak_table.html:11
msgid "Category" msgid "Category"
msgstr "Kategorie" 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 #: AKSubmission/templates/AKSubmission/ak_history.html:37
msgid "Track" msgid "Track"
msgstr "Track" msgstr "Track"
#: AKSubmission/templates/AKSubmission/ak_detail.html:177 #: AKSubmission/templates/AKSubmission/ak_detail.html:234
#, fuzzy
#| msgid "Present results of this AK"
msgid "Present this AK" 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)" msgid "(Category Default)"
msgstr "(Kategorievoreinstellung)" msgstr "(Kategorievoreinstellung)"
#: AKSubmission/templates/AKSubmission/ak_detail.html:188 #: AKSubmission/templates/AKSubmission/ak_detail.html:245
#: AKSubmission/templates/AKSubmission/ak_table.html:12
msgid "Tags"
msgstr "Tags"
#: AKSubmission/templates/AKSubmission/ak_detail.html:194
msgid "Reso intention?" msgid "Reso intention?"
msgstr "Resoabsicht?" msgstr "Resoabsicht?"
#: AKSubmission/templates/AKSubmission/ak_detail.html:201 #: AKSubmission/templates/AKSubmission/ak_detail.html:252
msgid "Requirements" msgid "Requirements"
msgstr "Anforderungen" msgstr "Anforderungen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:214 #: AKSubmission/templates/AKSubmission/ak_detail.html:265
msgid "Conflicting AKs" msgid "Conflicting AKs"
msgstr "AK-Konflikte" msgstr "AK-Konflikte"
#: AKSubmission/templates/AKSubmission/ak_detail.html:222 #: AKSubmission/templates/AKSubmission/ak_detail.html:273
msgid "Prerequisite AKs" msgid "Prerequisite AKs"
msgstr "Vorausgesetzte AKs" msgstr "Vorausgesetzte AKs"
#: AKSubmission/templates/AKSubmission/ak_detail.html:230 #: AKSubmission/templates/AKSubmission/ak_detail.html:281
msgid "Notes" msgid "Notes"
msgstr "Notizen" msgstr "Notizen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:243 #: AKSubmission/templates/AKSubmission/ak_detail.html:294
msgid "When?" msgid "When?"
msgstr "Wann?" msgstr "Wann?"
#: AKSubmission/templates/AKSubmission/ak_detail.html:278 #: AKSubmission/templates/AKSubmission/ak_detail.html:296
#: AKSubmission/templates/AKSubmission/akslot_delete.html:35
msgid "Duration"
msgstr "Dauer"
#: AKSubmission/templates/AKSubmission/ak_detail.html:298
msgid "Room"
msgstr "Raum"
#: AKSubmission/templates/AKSubmission/ak_detail.html:329
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:289 #: AKSubmission/templates/AKSubmission/ak_detail.html:340
msgid "Schedule" msgid "Schedule"
msgstr "Schedule" msgstr "Schedule"
#: AKSubmission/templates/AKSubmission/ak_detail.html:301 #: AKSubmission/templates/AKSubmission/ak_detail.html:352
msgid "Add another slot" msgid "Add another slot"
msgstr "Einen neuen AK-Slot hinzufügen" msgstr "Einen neuen AK-Slot hinzufügen"
#: AKSubmission/templates/AKSubmission/ak_detail.html:311 #: AKSubmission/templates/AKSubmission/ak_detail.html:362
msgid "Possible Times" msgid "Possible Times"
msgstr "Mögliche Zeiten" msgstr "Mögliche Zeiten"
#: AKSubmission/templates/AKSubmission/ak_detail.html:366
msgid "Start"
msgstr "Start"
#: AKSubmission/templates/AKSubmission/ak_detail.html:367
msgid "End"
msgstr "Ende"
#: AKSubmission/templates/AKSubmission/ak_edit.html:8 #: AKSubmission/templates/AKSubmission/ak_edit.html:8
#: AKSubmission/templates/AKSubmission/ak_history.html:11 #: AKSubmission/templates/AKSubmission/ak_history.html:11
#: AKSubmission/templates/AKSubmission/ak_overview.html:8 #: AKSubmission/templates/AKSubmission/ak_overview.html:8
#: AKSubmission/templates/AKSubmission/ak_overview.html:12 #: AKSubmission/templates/AKSubmission/ak_overview.html:12
#: AKSubmission/templates/AKSubmission/ak_overview.html:40 #: AKSubmission/templates/AKSubmission/ak_overview.html:33
#: AKSubmission/templates/AKSubmission/akmessage_add.html:7 #: AKSubmission/templates/AKSubmission/akmessage_add.html:7
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:7 #: AKSubmission/templates/AKSubmission/akowner_create_update.html:7
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:7 #: AKSubmission/templates/AKSubmission/akslot_add_update.html:7
#: AKSubmission/templates/AKSubmission/akslot_delete.html:7 #: AKSubmission/templates/AKSubmission/akslot_delete.html:7
#: AKSubmission/templates/AKSubmission/submission_not_configured.html:7 #: AKSubmission/templates/AKSubmission/submission_not_configured.html:7
#: AKSubmission/templates/AKSubmission/submission_overview.html:7 #: AKSubmission/templates/AKSubmission/submission_overview.html:7
#: AKSubmission/templates/AKSubmission/submission_overview.html:45 #: AKSubmission/templates/AKSubmission/submission_overview.html:40
#: AKSubmission/templates/AKSubmission/submit_new.html:9 #: AKSubmission/templates/AKSubmission/submit_new.html:9
#: AKSubmission/templates/AKSubmission/submit_new_wish.html:7 #: AKSubmission/templates/AKSubmission/submit_new_wish.html:7
msgid "AKs" msgid "AKs"
msgstr "AKs" msgstr "AKs"
#: AKSubmission/templates/AKSubmission/ak_edit.html:8 #: AKSubmission/templates/AKSubmission/ak_edit.html:8
#: AKSubmission/templates/AKSubmission/ak_edit.html:21 #: AKSubmission/templates/AKSubmission/ak_edit.html:32
msgid "Edit AK" msgid "Edit AK"
msgstr "AK bearbeiten" msgstr "AK bearbeiten"
#: AKSubmission/templates/AKSubmission/ak_edit.html:24
msgid ""
"Add person not in the list yet. Unsaved changes in this form will be lost."
msgstr ""
"Person hinzufügen, die noch nicht in der Liste ist. Ungespeicherte "
"Änderungen in diesem Formular gehen verloren."
#: AKSubmission/templates/AKSubmission/ak_history.html:11
#: AKSubmission/templates/AKSubmission/akslot_delete.html:31
msgid "AK"
msgstr "AK"
#: AKSubmission/templates/AKSubmission/ak_history.html:27 #: AKSubmission/templates/AKSubmission/ak_history.html:27
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
...@@ -276,16 +265,16 @@ msgid "Time" ...@@ -276,16 +265,16 @@ msgid "Time"
msgstr "Zeit" msgstr "Zeit"
#: AKSubmission/templates/AKSubmission/ak_history.html:48 #: AKSubmission/templates/AKSubmission/ak_history.html:48
#: AKSubmission/templates/AKSubmission/ak_table.html:26 #: AKSubmission/templates/AKSubmission/ak_table.html:28
msgid "Present results of this AK" msgid "Present results of this AK"
msgstr "Die Ergebnisse dieses AKs vorstellen" msgstr "Die Ergebnisse dieses AKs vorstellen"
#: AKSubmission/templates/AKSubmission/ak_history.html:52 #: AKSubmission/templates/AKSubmission/ak_history.html:52
#: AKSubmission/templates/AKSubmission/ak_table.html:30 #: AKSubmission/templates/AKSubmission/ak_table.html:32
msgid "Intends to submit a resolution" msgid "Intends to submit a resolution"
msgstr "Beabsichtigt eine Resolution einzureichen" msgstr "Beabsichtigt eine Resolution einzureichen"
#: AKSubmission/templates/AKSubmission/ak_list.html:6 AKSubmission/views.py:40 #: AKSubmission/templates/AKSubmission/ak_list.html:6 AKSubmission/views.py:82
msgid "All AKs" msgid "All AKs"
msgstr "Alle AKs" msgstr "Alle AKs"
...@@ -293,38 +282,38 @@ msgstr "Alle AKs" ...@@ -293,38 +282,38 @@ msgstr "Alle AKs"
msgid "Tracks" msgid "Tracks"
msgstr "Tracks" msgstr "Tracks"
#: AKSubmission/templates/AKSubmission/ak_overview.html:30 #: AKSubmission/templates/AKSubmission/ak_overview.html:23
msgid "AK List" msgid "AK List"
msgstr "AK-Liste" msgstr "AK-Liste"
#: AKSubmission/templates/AKSubmission/ak_overview.html:36 #: AKSubmission/templates/AKSubmission/ak_overview.html:29
msgid "Add AK" msgid "Add AK"
msgstr "AK hinzufügen" msgstr "AK hinzufügen"
#: AKSubmission/templates/AKSubmission/ak_table.html:44 #: AKSubmission/templates/AKSubmission/ak_table.html:52
msgid "Details" msgid "Details"
msgstr "Details" msgstr "Details"
#: AKSubmission/templates/AKSubmission/ak_table.html:68 #: AKSubmission/templates/AKSubmission/ak_table.html:76
msgid "There are no AKs in this category yet" msgid "There are no AKs in this category yet"
msgstr "Es gibt noch keine AKs in dieser Kategorie" msgstr "Es gibt noch keine AKs in dieser Kategorie"
#: AKSubmission/templates/AKSubmission/akmessage_add.html:28 #: AKSubmission/templates/AKSubmission/akmessage_add.html:27
msgid "Send" msgid "Send"
msgstr "Senden" msgstr "Senden"
#: AKSubmission/templates/AKSubmission/akmessage_add.html:32 #: AKSubmission/templates/AKSubmission/akmessage_add.html:31
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:27 #: AKSubmission/templates/AKSubmission/akowner_create_update.html:26
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:30 #: AKSubmission/templates/AKSubmission/akslot_add_update.html:29
#: AKSubmission/templates/AKSubmission/submit_new.html:54 #: AKSubmission/templates/AKSubmission/submit_new.html:59
msgid "Reset Form" msgid "Reset Form"
msgstr "Formular leeren" msgstr "Formular leeren"
#: AKSubmission/templates/AKSubmission/akmessage_add.html:36 #: AKSubmission/templates/AKSubmission/akmessage_add.html:35
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:31 #: AKSubmission/templates/AKSubmission/akowner_create_update.html:30
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:34 #: AKSubmission/templates/AKSubmission/akslot_add_update.html:33
#: AKSubmission/templates/AKSubmission/akslot_delete.html:46 #: AKSubmission/templates/AKSubmission/akslot_delete.html:45
#: AKSubmission/templates/AKSubmission/submit_new.html:58 #: AKSubmission/templates/AKSubmission/submit_new.html:63
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
...@@ -334,8 +323,8 @@ msgstr "Abbrechen" ...@@ -334,8 +323,8 @@ msgstr "Abbrechen"
msgid "AK Owner" msgid "AK Owner"
msgstr "AK-Leitung" msgstr "AK-Leitung"
#: AKSubmission/templates/AKSubmission/akowner_create_update.html:24 #: AKSubmission/templates/AKSubmission/akowner_create_update.html:23
#: AKSubmission/templates/AKSubmission/akslot_add_update.html:26 #: AKSubmission/templates/AKSubmission/akslot_add_update.html:25
msgid "Continue" msgid "Continue"
msgstr "Weiter" msgstr "Weiter"
...@@ -352,7 +341,7 @@ msgstr "AK-Dauer(n)" ...@@ -352,7 +341,7 @@ msgstr "AK-Dauer(n)"
msgid "Do you really want to delete this AK Slot?" msgid "Do you really want to delete this AK Slot?"
msgstr "Willst du diesen AK-Slot wirklich löschen?" msgstr "Willst du diesen AK-Slot wirklich löschen?"
#: AKSubmission/templates/AKSubmission/akslot_delete.html:42 #: AKSubmission/templates/AKSubmission/akslot_delete.html:41
msgid "Confirm" msgid "Confirm"
msgstr "Bestätigen" msgstr "Bestätigen"
...@@ -368,104 +357,123 @@ msgstr "" ...@@ -368,104 +357,123 @@ msgstr ""
"Das System ist bisher nicht für Eintragen und Anzeige von AKs konfiguriert. " "Das System ist bisher nicht für Eintragen und Anzeige von AKs konfiguriert. "
"Bitte versuche es später wieder." "Bitte versuche es später wieder."
#: AKSubmission/templates/AKSubmission/submission_overview.html:49 #: AKSubmission/templates/AKSubmission/submission_overview.html:44
msgid "" msgid ""
"On this page you can see a list of current AKs, change them and add new ones." "On this page you can see a list of current AKs, change them and add new ones."
msgstr "" msgstr ""
"Auf dieser Seite kannst du eine Liste von aktuellen AKs sehen, diese " "Auf dieser Seite kannst du eine Liste von aktuellen AKs sehen, diese "
"bearbeiten und neue hinzufügen." "bearbeiten und neue hinzufügen."
#: AKSubmission/templates/AKSubmission/submission_overview.html:56 #: AKSubmission/templates/AKSubmission/submission_overview.html:52
#: AKSubmission/templates/AKSubmission/submit_new_wish.html:7 #: AKSubmission/templates/AKSubmission/submit_new_wish.html:7
#: AKSubmission/templates/AKSubmission/submit_new_wish.html:14 #: AKSubmission/templates/AKSubmission/submit_new_wish.html:14
#: AKSubmission/templates/AKSubmission/submit_new_wish.html:18 #: AKSubmission/templates/AKSubmission/submit_new_wish.html:18
msgid "New AK Wish" msgid "New AK Wish"
msgstr "Neuer AK-Wunsch" msgstr "Neuer AK-Wunsch"
#: AKSubmission/templates/AKSubmission/submission_overview.html:60 #: AKSubmission/templates/AKSubmission/submission_overview.html:56
msgid "Who" msgid "Who"
msgstr "Wer" msgstr "Wer"
#: AKSubmission/templates/AKSubmission/submission_overview.html:63 #: AKSubmission/templates/AKSubmission/submission_overview.html:59
msgid "I do not own AKs yet" msgid "I do not own AKs yet"
msgstr "Ich leite bisher keine AKs" msgstr "Ich leite bisher keine AKs"
#: AKSubmission/templates/AKSubmission/submission_overview.html:71 #: AKSubmission/templates/AKSubmission/submission_overview.html:67
#: AKSubmission/templates/AKSubmission/submit_new.html:9 #: AKSubmission/templates/AKSubmission/submit_new.html:9
#: AKSubmission/templates/AKSubmission/submit_new.html:37 #: AKSubmission/templates/AKSubmission/submit_new.html:41
#: AKSubmission/templates/AKSubmission/submit_new.html:44 #: AKSubmission/templates/AKSubmission/submit_new.html:48
msgid "New AK" msgid "New AK"
msgstr "Neuer AK" msgstr "Neuer AK"
#: AKSubmission/templates/AKSubmission/submission_overview.html:77 #: AKSubmission/templates/AKSubmission/submission_overview.html:73
msgid "Edit Person Info" msgid "Edit Person Info"
msgstr "Personen-Info bearbeiten" msgstr "Personen-Info bearbeiten"
#: AKSubmission/templates/AKSubmission/submission_overview.html:84 #: AKSubmission/templates/AKSubmission/submission_overview.html:81
msgid "This event is not active. You cannot add or change AKs" msgid "This event is not active. You cannot add or change AKs"
msgstr "" msgstr ""
"Dieses Event is nicht aktiv. Es können keine AKs hinzugefügt oder bearbeitet " "Dieses Event is nicht aktiv. Es können keine AKs hinzugefügt oder bearbeitet "
"werden" "werden"
#: AKSubmission/templates/AKSubmission/submit_new.html:50 #: 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" msgid "Submit"
msgstr "Eintragen" msgstr "Eintragen"
#: AKSubmission/views.py:71 #: AKSubmission/views.py:125
msgid "Wishes" msgid "Wishes"
msgstr "Wünsche" msgstr "Wünsche"
#: AKSubmission/views.py:71 #: AKSubmission/views.py:125
msgid "AKs one would like to have" msgid "AKs one would like to have"
msgstr "" msgstr ""
"AKs die sich gewünscht wurden, aber bei denen noch nicht klar ist, wer sie " "AKs die sich gewünscht wurden, aber bei denen noch nicht klar ist, wer sie "
"macht. Falls du dir das vorstellen kannst, trag dich einfach ein" "macht. Falls du dir das vorstellen kannst, trag dich einfach ein"
#: AKSubmission/views.py:91 #: AKSubmission/views.py:167
msgid "Currently planned AKs" msgid "Currently planned AKs"
msgstr "Aktuell geplante AKs" msgstr "Aktuell geplante AKs"
#: AKSubmission/views.py:193 #: AKSubmission/views.py:305
msgid "Event inactive. Cannot create or update." msgid "Event inactive. Cannot create or update."
msgstr "Event inaktiv. Hinzufügen/Bearbeiten nicht möglich." msgstr "Event inaktiv. Hinzufügen/Bearbeiten nicht möglich."
#: AKSubmission/views.py:209 #: AKSubmission/views.py:330
msgid "AK successfully created" msgid "AK successfully created"
msgstr "AK erfolgreich angelegt" msgstr "AK erfolgreich angelegt"
#: AKSubmission/views.py:264 #: AKSubmission/views.py:404
msgid "AK successfully updated" msgid "AK successfully updated"
msgstr "AK erfolgreich aktualisiert" msgstr "AK erfolgreich aktualisiert"
#: AKSubmission/views.py:344 #: AKSubmission/views.py:455
msgid "Person Info successfully updated" #, python-brace-format
msgstr "Personen-Info erfolgreich aktualisiert" msgid "Added '{owner}' as new owner of '{ak.name}'"
msgstr "'{owner}' als neue Leitung von '{ak.name}' hinzugefügt"
#: AKSubmission/views.py:364 #: AKSubmission/views.py:558
msgid "No user selected" msgid "No user selected"
msgstr "Keine Person ausgewählt" msgstr "Keine Person ausgewählt"
#: AKSubmission/views.py:391 #: AKSubmission/views.py:574
msgid "Person Info successfully updated"
msgstr "Personen-Info erfolgreich aktualisiert"
#: AKSubmission/views.py:610
msgid "AK Slot successfully added" msgid "AK Slot successfully added"
msgstr "AK-Slot erfolgreich angelegt" msgstr "AK-Slot erfolgreich angelegt"
#: AKSubmission/views.py:405 #: AKSubmission/views.py:629
msgid "You cannot edit a slot that has already been scheduled" msgid "You cannot edit a slot that has already been scheduled"
msgstr "Bereits geplante AK-Slots können nicht mehr bearbeitet werden" msgstr "Bereits geplante AK-Slots können nicht mehr bearbeitet werden"
#: AKSubmission/views.py:415 #: AKSubmission/views.py:639
msgid "AK Slot successfully updated" msgid "AK Slot successfully updated"
msgstr "AK-Slot erfolgreich aktualisiert" msgstr "AK-Slot erfolgreich aktualisiert"
#: AKSubmission/views.py:428 #: AKSubmission/views.py:657
msgid "You cannot delete a slot that has already been scheduled" msgid "You cannot delete a slot that has already been scheduled"
msgstr "Bereits geplante AK-Slots können nicht mehr gelöscht werden" msgstr "Bereits geplante AK-Slots können nicht mehr gelöscht werden"
#: AKSubmission/views.py:438 #: AKSubmission/views.py:667
msgid "AK Slot successfully deleted" msgid "AK Slot successfully deleted"
msgstr "AK-Slot erfolgreich angelegt" msgstr "AK-Slot erfolgreich angelegt"
#: AKSubmission/views.py:460 #: AKSubmission/views.py:679
msgid "Messages"
msgstr "Nachrichten"
#: AKSubmission/views.py:689
msgid "Delete all messages"
msgstr "Alle Nachrichten löschen"
#: AKSubmission/views.py:716
msgid "Message to organizers successfully saved" msgid "Message to organizers successfully saved"
msgstr "Nachricht an die Organisator*innen erfolgreich gespeichert" msgstr "Nachricht an die Organisator*innen erfolgreich gespeichert"
......
...@@ -3,18 +3,19 @@ from django.conf import settings ...@@ -3,18 +3,19 @@ from django.conf import settings
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.urls import reverse_lazy
from AKModel.models import AKOrgaMessage, AKSlot from AKModel.models import AKOrgaMessage, AKSlot
@receiver(post_save, sender=AKOrgaMessage) @receiver(post_save, sender=AKOrgaMessage)
def orga_message_saved_handler(sender, instance: AKOrgaMessage, created, **kwargs): def orga_message_saved_handler(sender, instance: AKOrgaMessage, created, **kwargs): # pylint: disable=unused-argument
# React to newly created Orga message by sending an email """
React to newly created Orga message by sending an email
"""
if created and settings.SEND_MAILS: if created and settings.SEND_MAILS:
host = 'https://' + settings.ALLOWED_HOSTS[0] if len(settings.ALLOWED_HOSTS) > 0 else 'http://127.0.0.1:8000' host = 'https://' + settings.ALLOWED_HOSTS[0] if len(settings.ALLOWED_HOSTS) > 0 else 'http://127.0.0.1:8000'
url = f"{host}{reverse_lazy('submit:ak_detail', kwargs={'pk': instance.ak.pk, 'event_slug': instance.ak.event.slug})}" url = f"{host}{instance.ak.detail_url}"
mail = EmailMessage( mail = EmailMessage(
f"[AKPlanning] New message for AK '{instance.ak}' ({instance.ak.event})", f"[AKPlanning] New message for AK '{instance.ak}' ({instance.ak.event})",
...@@ -26,12 +27,14 @@ def orga_message_saved_handler(sender, instance: AKOrgaMessage, created, **kwarg ...@@ -26,12 +27,14 @@ def orga_message_saved_handler(sender, instance: AKOrgaMessage, created, **kwarg
@receiver(post_save, sender=AKSlot) @receiver(post_save, sender=AKSlot)
def slot_created_handler(sender, instance: AKSlot, created, **kwargs): def slot_created_handler(sender, instance: AKSlot, created, **kwargs): # pylint: disable=unused-argument
# React to slots that are created after the plan was already published by sending an email """
React to slots that are created after the plan was already published by sending an email
if created and settings.SEND_MAILS and apps.is_installed("AKPlan") and not instance.event.plan_hidden and instance.room is None and instance.start is None: """
if created and settings.SEND_MAILS and apps.is_installed("AKPlan") \
and not instance.event.plan_hidden and instance.room is None and instance.start is None: # pylint: disable=too-many-boolean-expressions,line-too-long
host = 'https://' + settings.ALLOWED_HOSTS[0] if len(settings.ALLOWED_HOSTS) > 0 else 'http://127.0.0.1:8000' host = 'https://' + settings.ALLOWED_HOSTS[0] if len(settings.ALLOWED_HOSTS) > 0 else 'http://127.0.0.1:8000'
url = f"{host}{reverse_lazy('submit:ak_detail', kwargs={'pk': instance.ak.pk, 'event_slug': instance.ak.event.slug})}" url = f"{host}{instance.ak.detail_url}"
mail = EmailMessage( mail = EmailMessage(
f"[AKPlanning] New slot for AK '{instance.ak}' ({instance.ak.event}) added", f"[AKPlanning] New slot for AK '{instance.ak}' ({instance.ak.event}) added",
......
{% extends 'AKSubmission/submission_base.html' %} {% extends 'AKSubmission/submission_base.html' %}
{% load i18n %} {% load i18n %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load tz %} {% load tz %}
{% load tags_AKSubmission %} {% load tags_AKSubmission %}
...@@ -27,6 +27,55 @@ ...@@ -27,6 +27,55 @@
{% block imports %} {% block imports %}
{% include "AKPlan/plan_akslot.html" %} {% 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> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// CSRF Protection/Authentication // CSRF Protection/Authentication
...@@ -68,7 +117,7 @@ ...@@ -68,7 +117,7 @@
data: { data: {
}, },
success: function (response) { success: function (response) {
btn.html('{% fa5_icon 'check' 'fas' %}'); btn.html('{% fa6_icon 'check' 'fas' %}');
btn.off('click'); btn.off('click');
$('#interest-counter').html(response.interest_counter); $('#interest-counter').html(response.interest_counter);
}, },
...@@ -94,64 +143,62 @@ ...@@ -94,64 +143,62 @@
{% block content %} {% block content %}
{% include "messages.html" %} {% include "messages.html" %}
<div class="text-right"> <div class="text-end">
{% if ak.interest_counter >= 0 %} {% if ak.interest_counter >= 0 %}
{% if ak.event.active and interest_indication_active %} {% if ak.event.active and interest_indication_active %}
{% trans 'Interest' %}: <b class='mx-1 text-muted' id="interest-counter">{{ ak.interest_counter }}</b> {% trans 'Interest' %}: <b class='mx-1 text-muted' id="interest-counter">{{ ak.interest_counter }}</b>
<a href="#" data-toggle="tooltip" <a href="#" data-bs-toggle="tooltip"
title="{% trans 'Show Interest' %}" title="{% trans 'Show Interest' %}"
class="btn btn-primary" id="btn-indicate-interest">{% fa5_icon 'thumbs-up' 'fas' %}</a> class="btn btn-primary" id="btn-indicate-interest">{% fa6_icon 'thumbs-up' 'fas' %}</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if ak.link != "" %} {% if ak.link != "" %}
<a href="{{ ak.link }}" data-toggle="tooltip" <a href="{{ ak.link }}" data-bs-toggle="tooltip"
title="{% trans 'Open external link' %}" title="{% trans 'Open external link' %}"
class="btn btn-info">{% fa5_icon 'external-link-alt' 'fas' %}</a> class="btn btn-info">{% fa6_icon 'external-link-alt' 'fas' %}</a>
{% endif %} {% endif %}
{% if ak.protocol_link != "" %} {% if ak.protocol_link != "" %}
<a href="{{ ak.protocol_link }}" data-toggle="tooltip" <a href="{{ ak.protocol_link }}" data-bs-toggle="tooltip"
title="{% trans 'Open protocol link' %}" title="{% trans 'Open protocol link' %}"
class="btn btn-warning">{% fa5_icon 'file-alt' 'far' %}</a> class="btn btn-warning">{% fa6_icon 'file-alt' 'far' %}</a>
{% endif %} {% endif %}
<a href="{% url 'submit:ak_history' event_slug=ak.event.slug pk=ak.pk %}" <a href="{% url 'submit:ak_history' event_slug=ak.event.slug pk=ak.pk %}"
data-toggle="tooltip" data-bs-toggle="tooltip"
title="{% trans 'History' %}" class="btn btn-light">{% fa5_icon 'clock' 'fas' %}</a> title="{% trans 'History' %}" class="btn btn-light">{% fa6_icon 'clock' 'fas' %}</a>
{% if ak.event.active %} {% if ak.event.active %}
<a href="{% url 'submit:akmessage_add' event_slug=ak.event.slug pk=ak.pk %}" data-toggle="tooltip" <a href="{% url 'submit:akmessage_add' event_slug=ak.event.slug pk=ak.pk %}" data-bs-toggle="tooltip"
title="{% trans 'Add confidential message to organizers' %}" title="{% trans 'Add confidential message to organizers' %}"
class="btn btn-warning">{% fa5_icon 'envelope' 'fas' %}</a> class="btn btn-warning">{% fa6_icon 'envelope' 'fas' %}</a>
<a href="{% url 'submit:ak_edit' event_slug=ak.event.slug pk=ak.pk %}" data-toggle="tooltip" <a href="{{ ak.edit_url }}" data-bs-toggle="tooltip"
title="{% trans 'Edit' %}" title="{% trans 'Edit' %}"
class="btn btn-success">{% fa5_icon 'pencil-alt' 'fas' %}</a> class="btn btn-success">{% fa6_icon 'pencil-alt' 'fas' %}</a>
{% endif %} {% endif %}
</div> </div>
<h2>{% if ak.wish %}{% trans "AK Wish" %}: {% endif %}{{ ak.name }}</h2> <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 #} <div id="app">
{% if featured_slot_type != "NONE" %} {# Show current or upcoming slot featured in a box on top of the plage #}
<div class="card border-success mt-3 mb-3"> {% if featured_slot_type != "NONE" %}
<div class="card-body font-weight-bold"> <div class="card border-success mt-3 mb-3" v-show="showFeatured">
{% if featured_slot_type == "CURRENT" %} <div class="card-body font-weight-bold">
{% blocktrans with room=featured_slot.room %} <span v-show="isDuring" style="{% if not featured_slot_type == "CURRENT" %}display:none;{% endif %}">
This AK currently takes place for another {{ featured_slot_remaining }} minute(s) in {{ room }}. {% 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 %}
&nbsp; </span>
{% endblocktrans %} <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 %}
{% elif featured_slot_type == "UPCOMING" %} </span>
{% blocktrans with room=featured_slot.room %}
This AK starts in {{ featured_slot_remaining }} minute(s) in {{ room }}.&nbsp;
{% endblocktrans %}
{% endif %}
{% if "AKOnline"|check_app_installed and featured_slot.room.virtualroom and featured_slot.room.virtualroom.url != '' %} {% 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.virtualroom.url }}"> <a class="btn btn-success" target="_parent" href="{{ featured_slot.room.virtual.url }}">
{% fa5_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %} {% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a> </a>
{% endif %} {% endif %}
</div>
</div> </div>
</div> {% endif %}
{% endif %} </div>
<table class="table table-borderless"> <table class="table table-borderless">
<tr> <tr>
...@@ -166,6 +213,16 @@ ...@@ -166,6 +213,16 @@
{% category_linked_badge ak.category ak.event.slug %} {% category_linked_badge ak.category ak.event.slug %}
</td> </td>
</tr> </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 %} {% if ak.track %}
<tr> <tr>
<td>{% trans 'Track' %}</td> <td>{% trans 'Track' %}</td>
...@@ -184,12 +241,6 @@ ...@@ -184,12 +241,6 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
<tr>
<td>{% trans "Tags" %}</td>
<td>
{% tag_list ak.tags.all ak.event.slug %}
</td>
</tr>
<tr> <tr>
<td>{% trans "Reso intention?" %}</td> <td>{% trans "Reso intention?" %}</td>
<td> <td>
...@@ -272,22 +323,22 @@ ...@@ -272,22 +323,22 @@
<td> <td>
{% if not slot.start %} {% if not slot.start %}
<a href="{% url 'submit:akslot_edit' event_slug=ak.event.slug pk=slot.pk %}" <a href="{% url 'submit:akslot_edit' event_slug=ak.event.slug pk=slot.pk %}"
data-toggle="tooltip" title="{% trans 'Edit' %}" data-bs-toggle="tooltip" title="{% trans 'Edit' %}"
class="btn btn-success">{% fa5_icon 'pencil-alt' 'fas' %}</a> class="btn btn-success">{% fa6_icon 'pencil-alt' 'fas' %}</a>
<a href="{% url 'submit:akslot_delete' event_slug=ak.event.slug pk=slot.pk %}" <a href="{% url 'submit:akslot_delete' event_slug=ak.event.slug pk=slot.pk %}"
data-toggle="tooltip" title="{% trans 'Delete' %}" data-bs-toggle="tooltip" title="{% trans 'Delete' %}"
class="btn btn-danger">{% fa5_icon 'times' 'fas' %}</a> class="btn btn-danger">{% fa6_icon 'times' 'fas' %}</a>
{% else %} {% else %}
{% if "AKOnline"|check_app_installed and slot.room and slot.room.virtualroom and slot.room.virtualroom.url != '' %} {% if "AKOnline"|check_app_installed and slot.room and slot.room.virtual and slot.room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ slot.room.virtualroom.url }}"> <a class="btn btn-success" target="_parent" href="{{ slot.room.virtual.url }}">
{% fa5_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %} {% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a> </a>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if user.is_staff %} {% if user.is_staff %}
<a href="{% url 'admin:AKModel_akslot_change' slot.pk %}" <a href="{% url 'admin:AKModel_akslot_change' slot.pk %}"
data-toggle="tooltip" title="{% trans 'Schedule' %}" data-bs-toggle="tooltip" title="{% trans 'Schedule' %}"
class="btn btn-outline-success">{% fa5_icon 'stream' 'fas' %}</a> class="btn btn-outline-success">{% fa6_icon 'stream' 'fas' %}</a>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
...@@ -298,7 +349,7 @@ ...@@ -298,7 +349,7 @@
{% if ak.event.active %} {% if ak.event.active %}
<div class=""> <div class="">
<a href="{% url 'submit:akslot_add' event_slug=ak.event.slug pk=ak.pk %}" <a href="{% url 'submit:akslot_add' event_slug=ak.event.slug pk=ak.pk %}"
class="btn btn-success">{% fa5_icon 'plus' 'fas' %} {% trans "Add another slot" %}</a> class="btn btn-success">{% fa6_icon 'plus' 'fas' %} {% trans "Add another slot" %}</a>
</div> </div>
{% endif %} {% endif %}
......
{% extends 'AKSubmission/submit_new.html' %} {% extends 'AKSubmission/submit_new.html' %}
{% load i18n %} {% load i18n %}
{% load bootstrap4 %} {% load django_bootstrap5 %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load static %} {% load static %}
{% block title %}{% trans "AKs" %}: {{ event.name }} - {% trans "Edit AK" %}: {{ ak.name }}{% endblock %} {% block title %}{% trans "AKs" %}: {{ event.name }} - {% trans "Edit AK" %}: {{ ak.name }}{% endblock %}
...@@ -12,10 +12,21 @@ ...@@ -12,10 +12,21 @@
<li class="breadcrumb-item"><a <li class="breadcrumb-item"><a
href="{% url 'submit:submission_overview' event_slug=event.slug %}">{% trans "AK Submission" %}</a></li> href="{% url 'submit:submission_overview' event_slug=event.slug %}">{% trans "AK Submission" %}</a></li>
<li class="breadcrumb-item"><a <li class="breadcrumb-item"><a
href="{% url 'submit:ak_detail' event_slug=event.slug pk=ak.pk %}">{{ ak.short_name }}</a></li> href="{{ ak.detail_url }}">{{ ak.short_name }}</a></li>
<li class="breadcrumb-item active">{% trans "Edit" %}</li> <li class="breadcrumb-item active">{% trans "Edit" %}</li>
{% endblock %} {% endblock %}
{% block form_contents %}
{% bootstrap_field form.name %}
<div class="form-group">
{% bootstrap_field form.owners form_group_class="" %}
<a href="{% url 'submit:akowner_create' event_slug=event.slug %}?add_to_existing_ak={{ ak.pk }}">
{% fa6_icon "plus" "fas" %} {% trans "Add person not in the list yet. Unsaved changes in this form will be lost." %}
</a>
</div>
{% bootstrap_form form exclude='name,owners' %}
{% endblock %}
{% block headline %} {% block headline %}
<h2>{% trans 'Edit AK' %}</h2> <h2>{% trans 'Edit AK' %}</h2>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{% load tz %} {% load tz %}
{% load i18n %} {% load i18n %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load tags_AKSubmission %} {% load tags_AKSubmission %}
{% load tags_AKModel %} {% load tags_AKModel %}
...@@ -15,17 +15,17 @@ ...@@ -15,17 +15,17 @@
<li class="breadcrumb-item"><a <li class="breadcrumb-item"><a
href="{% url 'submit:submission_overview' event_slug=ak.event.slug %}">{% trans "AK Submission" %}</a></li> href="{% url 'submit:submission_overview' event_slug=ak.event.slug %}">{% trans "AK Submission" %}</a></li>
<li class="breadcrumb-item"><a <li class="breadcrumb-item"><a
href='{% url 'submit:ak_detail' event_slug=ak.event.slug pk=ak.pk %}'>{{ ak.short_name }}</a></li> href='{{ ak.detail_url }}'>{{ ak.short_name }}</a></li>
<li class="breadcrumb-item active">{% trans 'History' %}</li> <li class="breadcrumb-item active">{% trans 'History' %}</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% include "messages.html" %} {% include "messages.html" %}
<div class="float-right"> <div class="float-end">
<a href='{% url 'submit:ak_detail' event_slug=ak.event.slug pk=ak.pk %}' data-toggle="tooltip" <a href='{{ ak.detail_url }}' data-bs-toggle="tooltip"
title="{% trans 'Back' %}" title="{% trans 'Back' %}"
class="btn btn-info">{% fa5_icon 'arrow-circle-left' 'fas' %}</a> class="btn btn-info">{% fa6_icon 'arrow-circle-left' 'fas' %}</a>
</div> </div>
<h2>{% if ak.wish %}{% trans "AK Wish" %}: {% endif %}{{ ak.name }} ({% trans 'History' %})</h2> <h2>{% if ak.wish %}{% trans "AK Wish" %}: {% endif %}{{ ak.name }} ({% trans 'History' %})</h2>
...@@ -44,16 +44,16 @@ ...@@ -44,16 +44,16 @@
<td> <td>
<b>{{ h.name }}</b> <b>{{ h.name }}</b>
{% if h.present %} {% if h.present %}
<span class="badge badge-dark badge-pill" <span class="badge bg-dark rounded-pill"
title="{% trans 'Present results of this AK' %}">{% fa5_icon "bullhorn" 'fas' %}</span> title="{% trans 'Present results of this AK' %}">{% fa6_icon "bullhorn" 'fas' %}</span>
{% endif %} {% endif %}
{% if h.reso %} {% if h.reso %}
<span class="badge badge-dark badge-pill" <span class="badge bg-dark rounded-pill"
title="{% trans 'Intends to submit a resolution' %}">{% fa5_icon "scroll" 'fas' %}</span> title="{% trans 'Intends to submit a resolution' %}">{% fa6_icon "scroll" 'fas' %}</span>
{% endif %} {% endif %}
</td> </td>
<td>{% category_linked_badge h.category event.slug %}</td> <td>{% category_linked_badge h.category event.slug %}</td>
<td><span class="badge badge-success badge-pill">{{ h.track }}</span></td> <td><span class="badge bg-success rounded-pill">{{ h.track }}</span></td>
<td>{{ h.history_date | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}</td> <td>{{ h.history_date | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}</td>
</tr> </tr>
<tr> <tr>
......
{% load i18n %} {% load i18n %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
data: { data: {
}, },
success: function (response) { success: function (response) {
btn.html('{% fa5_icon 'check' 'fas' %}'); btn.html('{% fa6_icon 'check' 'fas' %}');
btn.off('click'); btn.off('click');
}, },
error: function (response) { error: function (response) {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{% if forloop.counter0 > 0 %} {% if forloop.counter0 > 0 %}
,&nbsp; ,&nbsp;
{% endif %} {% endif %}
<a href="{% url 'submit:ak_detail' event_slug=slug pk=ak.pk %}">{{ ak }}</a> <a href="{{ ak.detail_url }}">{{ ak }}</a>
{% empty %} {% empty %}
- -
{% endfor %} {% endfor %}
{% load i18n %} {% load i18n %}
<div class="float-right"> <div class="float-end">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'submit:ak_list' event_slug=event.slug %}">{% trans "All AKs" %}</a> <a class="nav-link" href="{% url 'submit:ak_list' event_slug=event.slug %}">{% trans "All AKs" %}</a>
</li> </li>
{% if event.aktrack_set.count > 0 %} {% if event.aktrack_set.count > 0 %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true"
aria-expanded="false">{% trans "Tracks" %}</a> aria-expanded="false">{% trans "Tracks" %}</a>
<div class="dropdown-menu" style=""> <div class="dropdown-menu" style="">
{% for track in event.aktrack_set.all %} {% for track in event.aktrack_set.all %}
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<ul class="nav nav-tabs" style="margin-bottom:15px"> <ul class="nav nav-tabs" style="margin-bottom:15px">
{% for category, _ in categories_with_aks %} {% for category, _ in categories_with_aks %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if category.name == active_category %}active{% endif %}" data-toggle="tab" <a class="nav-link {% if category.name == active_category %}active{% endif %}" data-bs-toggle="tab"
href="#category_{{ category.pk }}">{{ category.name }}</a> href="#category_{{ category.pk }}">{{ category.name }}</a>
</li> </li>
{% endfor %} {% endfor %}
......