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_csp-4.x
  • renovate/jsonschema-4.x
4 results

Target

Select target project
  • konstantin/akplanning
  • matedealer/akplanning
  • kif/akplanning
  • mirco/akplanning
  • lordofthevoid/akplanning
  • voidptr/akplanning
  • xayomer/akplanning-fork
  • mollux/akplanning
  • neumantm/akplanning
  • mmarx/akplanning
  • nerf/akplanning
  • felix_bonn/akplanning
  • sebastian.uschmann/akplanning
13 results
Select Git revision
  • ak-import
  • feature/clear-schedule-button
  • feature/json-export-via-rest-framework
  • feature/json-export-via-rest-framework-rebased
  • 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
16 results
Show changes
Showing
with 670 additions and 271 deletions
{% extends "base.html" %} {% extends "base.html" %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% block imports %} {% block meta %}
{% get_current_language as LANGUAGE_CODE %} <meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="{{ event.name }} - {% trans "Plan" %}" />
<link href='{% static 'AKPlan/fullcalendar/core/main.css' %}' rel='stylesheet' /> {% endblock %}
<script src='{% static 'AKPlan/fullcalendar/core/main.js' %}'></script> {% block imports %}
{% with 'AKPlan/fullcalendar/core/locales/'|add:LANGUAGE_CODE|add:'.js' as locale_file %} {% include "AKModel/load_fullcalendar.html" %}
<script src="{% static locale_file %}"></script>
{% endwith %}
<script src='{% static 'AKPlan/fullcalendar/bootstrap/main.js' %}'></script>
{% block fullcalendar %}{% endblock %} {% block fullcalendar %}{% endblock %}
{% endblock imports %} {% endblock imports %}
...@@ -21,7 +18,7 @@ ...@@ -21,7 +18,7 @@
{% block footer_custom %} {% block footer_custom %}
{% if event.contact_email %} {% if event.contact_email %}
<h4> <h4>
<a href="mailto:{{ event.contact_email }}">{% fa5_icon "envelope" "far" %} {% trans "Write to organizers of this event for questions and comments" %}</a> <a href="mailto:{{ event.contact_email }}">{% fa6_icon "envelope" "far" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
</h4> </h4>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
...@@ -8,7 +8,10 @@ ...@@ -8,7 +8,10 @@
AKPlanning AKPlanning
{% endif %} {% endif %}
</li> </li>
<li class="breadcrumb-item">{{ event.slug }}</li>
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a href="{% url 'plan:plan_overview' event_slug=event.slug %}">{% trans "AK Plan" %}</a> {% if 'AKDashboard'|check_app_installed %}
<a href="{% url 'dashboard:dashboard_event' slug=event.slug %}">{{ event }}</a>
{% else %}
{{ event }}
{% endif %}
</li> </li>
{% extends "AKPlan/plan_base.html" %} {% extends "AKPlan/plan_base.html" %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load tz %} {% load tz %}
{% load tags_AKPlan %}
{% block fullcalendar %} {% block fullcalendar %}
{% get_current_language as LANGUAGE_CODE %} {% if not event.plan_hidden or user.is_staff %}
{% get_current_language as LANGUAGE_CODE %}
<link href='{% static 'AKPlan/fullcalendar/daygrid/main.min.css' %}' rel='stylesheet' />
<link href='{% static 'AKPlan/fullcalendar/timegrid/main.min.css' %}' rel='stylesheet' /> <script>
document.addEventListener('DOMContentLoaded', function () {
<script src='{% static 'AKPlan/fullcalendar/daygrid/main.min.js' %}'></script> var calendarEl = document.getElementById('planCalendar');
<script src='{% static 'AKPlan/fullcalendar/timegrid/main.min.js' %}'></script>
var calendar = new FullCalendar.Calendar(calendarEl, {
<script> // Adapt to timezone of the connected event
document.addEventListener('DOMContentLoaded', function() { timeZone: '{{ event.timezone }}',
var calendarEl = document.getElementById('planCalendar'); initialView: 'timeGrid',
// Adapt to user selected locale
var calendar = new FullCalendar.Calendar(calendarEl, { locale: '{{ LANGUAGE_CODE }}',
plugins: [ 'timeGrid', 'bootstrap' ], // No header, not buttons
// Adapt to timezone of the connected event headerToolbar: {
timeZone: '{{ event.timezone }}', left: '',
defaultView: 'timeGrid', center: '',
// Adapt to user selected locale right: ''
locale: '{{ LANGUAGE_CODE }}', },
// No header, not buttons aspectRatio: 2,
header: { themeSystem: 'bootstrap5',
left: '', buttonIcons: {
center: '', prev: 'ignore fa-solid fa-angle-left',
right: '' next: 'ignore fa-solid fa-angle-right',
}, },
aspectRatio: 2, // Only show calendar view for the dates of the connected event
themeSystem: 'bootstrap', visibleRange: {
// Only show calendar view for the dates of the connected event start: '{{ event.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
visibleRange: { end: '{{ event.end | timezone:event.timezone | date:"Y-m-d H:i:s"}}',
start: '{{ event.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', },
end: '{{ event.end | timezone:event.timezone | date:"Y-m-d H:i:s"}}', scrollTime: '08:00:00',
}, allDaySlot: false,
allDaySlot: false, nowIndicator: true,
nowIndicator: true, now: "{% timestamp_now event.timezone %}",
eventTextColor: '#fff', eventTextColor: '#fff',
eventColor: '#127ba3', eventColor: '#127ba3',
// Create entries for all scheduled slots // Create entries for all scheduled slots
events: {% block encode %}{% endblock %} events: {% block encode %}{% endblock %},
}); schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
});
calendar.render();
}); calendar.render();
</script> });
</script>
{% endif %}
{% endblock %} {% endblock %}
{% extends "AKPlan/plan_base.html" %} {% extends "AKPlan/plan_base.html" %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load tz %} {% load tz %}
{% load tags_AKPlan %}
{% block fullcalendar %} {% block fullcalendar %}
{% get_current_language as LANGUAGE_CODE %} {% if not event.plan_hidden or user.is_staff %}
{% get_current_language as LANGUAGE_CODE %}
<link href='{% static 'AKPlan/fullcalendar/timeline/main.css' %}' rel='stylesheet' />
<link href='{% static 'AKPlan/fullcalendar/resource-timeline/main.css' %}' rel='stylesheet' /> <script>
<link href='{% static 'AKPlan/fullcalendar/resource-timeline/main.min.css' %}' rel='stylesheet' /> document.addEventListener('DOMContentLoaded', function () {
var planEl = document.getElementById('planCalendar');
<script src='{% static 'AKPlan/fullcalendar/timeline/main.js' %}'></script>
<script src='{% static 'AKPlan/fullcalendar/resource-common/main.js' %}'></script> var plan = new FullCalendar.Calendar(planEl, {
<script src='{% static 'AKPlan/fullcalendar/resource-timeline/main.js' %}'></script> timeZone: '{{ event.timezone }}',
headerToolbar: {
<script> left: 'today prev,next',
document.addEventListener('DOMContentLoaded', function() { center: 'title',
var planEl = document.getElementById('planCalendar'); right: 'resourceTimelineDay,resourceTimelineEvent'
},
var plan = new FullCalendar.Calendar(planEl, { themeSystem: 'bootstrap5',
plugins: ['resourceTimeline', 'bootstrap'], buttonIcons: {
timeZone: '{{ event.timezone }}', prev: 'ignore fa-solid fa-angle-left',
header: { next: 'ignore fa-solid fa-angle-right',
left: 'today prev,next',
center: 'title',
right: 'resourceTimelineDay,resourceTimelineEvent'
},
aspectRatio: 2,
themeSystem: 'bootstrap',
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
defaultView: 'resourceTimelineEvent',
views: {
resourceTimelineDay: {
type: 'resourceTimeline',
buttonText: '{% trans "Day" %}',
slotDuration: '01:00',
scrollTime: '08:00',
}, },
resourceTimelineEvent: { // Adapt to user selected locale
type: 'resourceTimeline', locale: '{{ LANGUAGE_CODE }}',
visibleRange: { initialView: 'resourceTimelineEvent',
start: '{{ event.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', views: {
end: '{{ event.end | timezone:event.timezone | date:"Y-m-d H:i:s"}}', resourceTimelineDay: {
type: 'resourceTimeline',
buttonText: '{% trans "Day" %}',
slotDuration: '01:00',
scrollTime: '08:00',
}, },
buttonText: '{% trans "Event" %}', resourceTimelineEvent: {
} type: 'resourceTimeline',
}, visibleRange: {
editable: false, start: '{{ event.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
allDaySlot: false, end: '{{ event.end | timezone:event.timezone | date:"Y-m-d H:i:s"}}',
nowIndicator: true, },
eventTextColor: '#fff', buttonText: '{% trans "Event" %}',
eventColor: '#127ba3', }
resourceAreaWidth: '15%', },
resourceLabelText: '{% trans "Room" %}', eventDidMount: function(info) {
resources: {% include "AKPlan/encode_rooms.html" %}, $(info.el).tooltip({title: info.event.extendedProps.description});
events: {% with akslots as slots %}{% include "AKPlan/encode_events.html" %}{% endwith %}, },
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', editable: false,
allDaySlot: false,
nowIndicator: true,
now: "{% timestamp_now event.timezone %}",
eventTextColor: '#fff',
eventColor: '#127ba3',
resourceAreaWidth: '15%',
resourceAreaHeaderContent: '{% trans "Room" %}',
resources: {% include "AKPlan/encode_rooms.html" %},
events: {% with akslots as slots %}{% include "AKPlan/encode_events.html" %}{% endwith %},
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
});
plan.render();
// Scroll to current time
if($(".fc-timeline-now-indicator-line").length) {
$('.fc-scroller').scrollLeft($('.fc-timeline-now-indicator-line').position().left);
}
}); });
</script>
plan.render(); {% endif %}
});
</script>
{% endblock %} {% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
{% include "AKPlan/plan_breadcrumbs.html" %} {% include "AKPlan/plan_breadcrumbs.html" %}
<li class="breadcrumb-item">
{% trans "AK Plan" %}
</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="float-right"> <div class="float-end">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<li class="nav-item dropdown"> {% if rooms|length > 0 %}
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Rooms" %}</a> <li class="nav-item dropdown">
<div class="dropdown-menu" style=""> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button"
{% for r in event.room_set.all %} aria-haspopup="true"
<a class="dropdown-item" href="{% url "plan:plan_room" event_slug=event.slug pk=r.pk %}">{{ r }}</a> aria-expanded="false">{% trans "Rooms" %}</a>
{% endfor %} <div class="dropdown-menu" style="">
</div> {% for r in event.room_set.all %}
</li> <a class="dropdown-item"
<li class="nav-item dropdown"> href="{% url "plan:plan_room" event_slug=event.slug pk=r.pk %}">{{ r }}</a>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Tracks" %}</a> {% endfor %}
<div class="dropdown-menu"> </div>
{% for t in event.aktrack_set.all %} </li>
<a class="dropdown-item" href="{% url "plan:plan_track" event_slug=event.slug pk=t.pk %}">{{ t }}</a> {% endif %}
{% endfor %} {% if tracks|length > 0 %}
</div> <li class="nav-item dropdown">
</li> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button"
<li class="nav-item"> aria-haspopup="true"
<a class="nav-link active" href="{% url 'plan:plan_wall' event_slug=event.slug %}">{% fa5_icon 'desktop' 'fas' %}&nbsp;&nbsp;{% trans "AK Wall" %}</a> aria-expanded="false">{% trans "Tracks" %}</a>
</li> <div class="dropdown-menu">
{% for t in tracks %}
<a class="dropdown-item"
href="{% url "plan:plan_track" event_slug=event.slug pk=t.pk %}">{{ t }}</a>
{% endfor %}
</div>
</li>
{% endif %}
{% if event.active %}
<li class="nav-item">
<a class="nav-link active"
href="{% url 'plan:plan_wall' event_slug=event.slug %}">{% fa6_icon 'desktop' 'fas' %}&nbsp;&nbsp;{% trans "AK Wall" %}</a>
</li>
{% endif %}
</ul> </ul>
</div> </div>
...@@ -102,23 +124,41 @@ ...@@ -102,23 +124,41 @@
{% timezone event.timezone %} {% timezone event.timezone %}
<div class="row" style="margin-top:30px;"> <div class="row" style="margin-top:30px;">
<div class="col-md-6"> {% if not event.plan_hidden or user.is_staff %}
<h2><a name="currentAKs">{% trans "Current AKs" %}:</a></h2> {% if event.active %}
{% with akslots_now as slots %} <div class="col-md-6">
{% include "AKPlan/slots_table.html" %} <h2><a name="currentAKs">{% trans "Current AKs" %}:</a></h2>
{% endwith %} {% with akslots_now as slots %}
</div> {% include "AKPlan/slots_table.html" %}
{% endwith %}
<div class="col-md-6"> </div>
<h2><a name="currentAKs">{% trans "Next AKs" %}:</a></h2>
{% with akslots_next as slots %} <div class="col-md-6">
{% include "AKPlan/slots_table.html" %} <h2><a name="currentAKs">{% trans "Next AKs" %}:</a></h2>
{% endwith %} {% with akslots_next as slots %}
</div> {% include "AKPlan/slots_table.html" %}
{% endwith %}
<div class="col-md-12"> </div>
<div id="planCalendar" style="margin-top:30px;"></div> {% else %}
</div> <div class="col-md-12">
<div class="alert alert-warning">
<p class="mb-0">{% trans "This event is not active." %}</p>
</div>
</div>
{% endif %}
<div class="col-md-12">
<div style="margin-top:30px;margin-bottom: 70px;">
<div id="planCalendar"></div>
</div>
</div>
{% else %}
<div class="col-md-12">
<div class="alert alert-warning">
<p class="mb-0">{% trans "Plan is not visible (yet)." %}</p>
</div>
</div>
{% endif %}
</div> </div>
{% endtimezone %} {% endtimezone %}
{% endblock %} {% endblock %}
{% extends "AKPlan/plan_detail.html" %} {% extends "AKPlan/plan_detail.html" %}
{% load fontawesome_6 %}
{% load tags_AKModel %}
{% load tz %} {% load tz %}
{% load i18n %} {% load i18n %}
{% block breadcrumbs %} {% block breadcrumbs %}
{% include "AKPlan/plan_breadcrumbs.html" %} {% include "AKPlan/plan_breadcrumbs.html" %}
<li class="breadcrumb-item">{% trans "Room" %}</li> <li class="breadcrumb-item">
<li class="breadcrumb-item">{{ room.title }}</li> <a href="{% url 'plan:plan_overview' event_slug=event.slug %}">{% trans "AK Plan" %}</a>
</li>
<li class="breadcrumb-item">{% trans "Room" %}: {{ room.title }}</li>
{% endblock %} {% endblock %}
{% block encode %} {% block encode %}
[ [
{% for slot in room.akslot_set.all %} {% for slot in slots %}
{% if slot.start %} {% if slot.start %}
{'title': '{{ slot.ak }}', {'title': '{{ slot.ak }}',
'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', 'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
'end': '{{ slot.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}', 'end': '{{ slot.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
'url': '{% url 'submit:ak_detail' event_slug=event.slug pk=slot.ak.pk %}', 'url': '{{ slot.ak.detail_url }}',
'borderColor': '{{ slot.ak.track.color }}', 'borderColor': '{{ slot.ak.track.color }}',
'color': '{{ slot.ak.category.color }}', 'color': '{{ slot.ak.category.color }}',
}, },
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% for a in room.availabilities.all %}
{
title: '',
start: '{{ a.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
end: '{{ a.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
'resourceId': '{{ a.room.title }}',
backgroundColor: '#28B62C',
display: 'background',
groupId: 'roomAvailable',
},
{% endfor %}
] ]
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="float-right"> <div class="float-end">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<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" aria-expanded="false">{% trans "Rooms" %}</a> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Rooms" %}</a>
<div class="dropdown-menu" style=""> <div class="dropdown-menu" style="">
{% for r in event.room_set.all %} {% for r in event.room_set.all %}
<a class="dropdown-item" href="{% url "plan:plan_room" event_slug=event.slug pk=r.pk %}">{{ r }}</a> <a class="dropdown-item" href="{% url "plan:plan_room" event_slug=event.slug pk=r.pk %}">{{ r }}</a>
...@@ -41,13 +56,46 @@ ...@@ -41,13 +56,46 @@
</ul> </ul>
</div> </div>
<h1>Plan: {{ event }} -- {% trans "Room" %}: {{ room }}</h1> <h1>{% trans "Room" %}: {{ room.name }} {% if room.location != '' %}({{ room.location }}){% endif %}</h1>
{% if "AKOnline"|check_app_installed and room.virtual and room.virtual.url != '' %}
<a class="btn btn-success" target="_parent" href="{{ room.virtual.url }}">
{% fa6_icon 'external-link-alt' 'fas' %} {% trans "Go to virtual room" %}
</a>
{% endif %}
{% timezone event.timezone %} {% if not event.plan_hidden or user.is_staff %}
<div class="row" style="margin-top:30px;clear:both;"> {% timezone event.timezone %}
<div class="col-md-12"> <div class="row" style="margin-top:30px;clear:both;">
<div id="planCalendar"></div> <div class="col-md-12">
<div id="planCalendar"></div>
</div>
</div> </div>
{% endtimezone %}
{% else %}
<div class="alert alert-warning mt-3">
<p class="mb-0">{% trans "Plan is not visible (yet)." %}</p>
</div> </div>
{% endtimezone %} {% endif %}
<table class="table table-borderless" style="margin-top: 30px;">
<tbody>
<tr>
<td>{% trans "Capacity" %}:</td><td>{{ room.capacity }}</td>
</tr>
{% if room.properties.count > 0 %}
<tr>
<td>{% trans "Properties" %}:</td>
<td>
{% for property in room.properties.all %}
{% if forloop.counter0 > 0 %}
,&nbsp;
{% endif %}
{{ property }}
{% endfor %}
</td>
</tr>
{% endif %}
</tbody>
</table>
{% endblock %} {% endblock %}
...@@ -5,8 +5,10 @@ ...@@ -5,8 +5,10 @@
{% block breadcrumbs %} {% block breadcrumbs %}
{% include "AKPlan/plan_breadcrumbs.html" %} {% include "AKPlan/plan_breadcrumbs.html" %}
<li class="breadcrumb-item">{% trans "Track" %}</li> <li class="breadcrumb-item">
<li class="breadcrumb-item">{{ track }}</li> <a href="{% url 'plan:plan_overview' event_slug=event.slug %}">{% trans "AK Plan" %}</a>
</li>
<li class="breadcrumb-item">{% trans "Track" %}: {{ track }}</li>
{% endblock %} {% endblock %}
...@@ -17,7 +19,7 @@ ...@@ -17,7 +19,7 @@
{'title': '{{ slot.ak }} @ {{ slot.room }}', {'title': '{{ slot.ak }} @ {{ slot.room }}',
'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', 'start': '{{ slot.start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
'end': '{{ slot.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}', 'end': '{{ slot.end | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
'url': '{% url 'submit:ak_detail' event_slug=event.slug pk=slot.ak.pk %}', 'url': '{{ slot.ak.detail_url }}',
'color': '{{ track.color }}', 'color': '{{ track.color }}',
'borderColor': '{{ slot.ak.category.color }}', 'borderColor': '{{ slot.ak.category.color }}',
}, },
...@@ -28,10 +30,10 @@ ...@@ -28,10 +30,10 @@
{% block content %} {% block content %}
<div class="float-right"> <div class="float-end">
<ul class="nav nav-pills"> <ul class="nav nav-pills">
<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" aria-expanded="false">{% trans "Tracks" %}</a> <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Tracks" %}</a>
<div class="dropdown-menu"> <div class="dropdown-menu">
{% for t in event.aktrack_set.all %} {% for t in event.aktrack_set.all %}
<a class="dropdown-item" href="{% url "plan:plan_track" event_slug=event.slug pk=t.pk %}">{{ t }}</a> <a class="dropdown-item" href="{% url "plan:plan_track" event_slug=event.slug pk=t.pk %}">{{ t }}</a>
...@@ -41,13 +43,19 @@ ...@@ -41,13 +43,19 @@
</ul> </ul>
</div> </div>
<h1>Plan: {{ event }} -- {% trans "Track" %}: {{ track }}</h1> <h1>Plan: {{ event }} ({% trans "Track" %}: {{ track }})</h1>
{% timezone event.timezone %} {% if not event.plan_hidden or user.is_staff %}
<div class="row" style="margin-top:30px;clear: both;"> {% timezone event.timezone %}
<div class="col-md-12"> <div class="row" style="margin-top:30px;clear:both;">
<div id="planCalendar"></div> <div class="col-md-12">
<div id="planCalendar"></div>
</div>
</div> </div>
{% endtimezone %}
{% else %}
<div class="alert alert-warning mt-3">
<p class="mb-0">{% trans "Plan is not visible (yet)." %}</p>
</div> </div>
{% endtimezone %} {% endif %}
{% endblock %} {% endblock %}
{% load compress %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load bootstrap4 %} {% load django_bootstrap5 %}
{% load fontawesome_5 %} {% load fontawesome_6 %}
{% load tags_AKModel %} {% load tags_AKModel %}
{% load tags_AKPlan %}
{% load tz %} {% load tz %}
...@@ -13,55 +15,56 @@ ...@@ -13,55 +15,56 @@
<title>{% block title %}AK Planning{% endblock %}</title> <title>{% block title %}AK Planning{% 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='slim' %} <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 %}
<link rel="stylesheet" href="{% static 'common/css/custom.css' %}"> {% compress js %}
{% bootstrap_javascript %}
<script src="{% static 'common/vendor/jquery/jquery-3.6.3.min.js' %}"></script>
{% fontawesome_6_js %}
{% endcompress %}
{% get_current_language as LANGUAGE_CODE %} {% include "AKModel/load_fullcalendar.html" %}
<link href='{% static 'AKPlan/fullcalendar/core/main.css' %}' rel='stylesheet' />
<link href='{% static 'AKPlan/fullcalendar/timeline/main.css' %}' rel='stylesheet' />
<link href='{% static 'AKPlan/fullcalendar/resource-timeline/main.css' %}' rel='stylesheet' />
<link href='{% static 'AKPlan/fullcalendar/resource-timeline/main.min.css' %}' rel='stylesheet' />
<script src='{% static 'AKPlan/fullcalendar/core/main.js' %}'></script> {% get_current_language as LANGUAGE_CODE %}
{% with 'AKPlan/fullcalendar/core/locales/'|add:LANGUAGE_CODE|add:'.js' as locale_file %}
<script src="{% static locale_file %}"></script>
{% endwith %}
<script src='{% static 'AKPlan/fullcalendar/timeline/main.js' %}'></script>
<script src='{% static 'AKPlan/fullcalendar/resource-common/main.js' %}'></script>
<script src='{% static 'AKPlan/fullcalendar/resource-timeline/main.js' %}'></script>
<script src='{% static 'AKPlan/fullcalendar/bootstrap/main.js' %}'></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
var planEl = document.getElementById('planCalendar'); var planEl = document.getElementById('planCalendar');
var plan = new FullCalendar.Calendar(planEl, { var plan = new FullCalendar.Calendar(planEl, {
plugins: [ 'resourceTimeline', 'bootstrap'],
timeZone: '{{ event.timezone }}', timeZone: '{{ event.timezone }}',
header: false, headerToolbar: false,
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 }}',
type: 'resourceTimeline',
slotDuration: '01:00', slotDuration: '01:00',
defaultView: 'resourceTimeline', initialView: 'resourceTimeline',
visibleRange: { visibleRange: {
start: '{{ start | timezone:event.timezone | date:"Y-m-d H:i:s" }}', start: '{{ start | timezone:event.timezone | date:"Y-m-d H:i:s" }}',
end: '{{ end | timezone:event.timezone | date:"Y-m-d H:i:s"}}', end: '{{ end | timezone:event.timezone | date:"Y-m-d H:i:s"}}',
}, },
scrollTime: '{{ start | timezone:event.timezone | date:"H:i:s" }}', slotMinTime: '{{ earliest_start_hour }}:00:00',
slotMaxTime: '{{ latest_end_hour }}:00:00',
eventDidMount: function(info) {
$(info.el).tooltip({title: info.event.extendedProps.description});
},
editable: false, editable: false,
allDaySlot: false, allDaySlot: false,
nowIndicator: true, nowIndicator: true,
now: "{% timestamp_now event.timezone %}",
eventTextColor: '#fff', eventTextColor: '#fff',
eventColor: '#127ba3', eventColor: '#127ba3',
height: 'parent', height: '90%',
resourceAreaWidth: '15%', resourceAreaWidth: '15%',
resourceLabelText: '{% trans "Room" %}', resourceAreaHeaderContent: '{% trans "Room" %}',
resources: [ resources: [
{% for room in rooms %} {% for room in rooms %}
{ {
...@@ -75,31 +78,75 @@ ...@@ -75,31 +78,75 @@
}); });
plan.render(); plan.render();
// Scroll to current time
if($(".fc-timeline-now-indicator-line").length) {
$('.fc-scroller').scrollLeft($('.fc-timeline-now-indicator-line').position().left);
}
// == Auto Reload ==
// function from: https://stackoverflow.com/questions/5448545/how-to-retrieve-get-parameters-from-javascript/
function findGetParameter(parameterName) {
var result = null,
tmp = [];
location.search
.substr(1)
.split("&")
.forEach(function (item) {
tmp = item.split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
});
return result;
}
// Check whether an autoreload frequency was specified and treat it as full minutes
const autoreload_frequency = Math.ceil(findGetParameter("autoreload"));
const cbxAutoReload = $('#cbxAutoReload');
if(autoreload_frequency>0) {
window.setTimeout ( function() { window.location.reload(); }, autoreload_frequency * 60 * 1000);
console.log("Autoreload active");
cbxAutoReload.prop('checked', true);
}
else {
cbxAutoReload.prop('checked', false);
}
cbxAutoReload.change(function () {
let url = window.location.href.split('?')[0];
if(cbxAutoReload.prop('checked')) {
url = url + "?autoreload=5";
}
window.location.replace(url);
});
}); });
</script> </script>
</head> </head>
<body> <body>
{% timezone event.timezone %} {% timezone event.timezone %}
<div class="row" style="height:100vh;margin:0;padding:1vh;"> <div class="row" style="height:100vh;margin:0;padding:1vh;">
<div class="col-md-3"> <div class="col-md-3">
<h1>Plan: {{ event }}</h1> <h1>Plan: {{ event }}</h1>
<h2><a name="currentAKs">{% trans "Current AKs" %}:</a></h2> <h2><a name="currentAKs">{% trans "Current AKs" %}:</a></h2>
{% with akslots_now as slots %} {% with akslots_now as slots %}
{% include "AKPlan/slots_table.html" %} {% include "AKPlan/slots_table.html" %}
{% endwith %} {% endwith %}
<h2><a name="currentAKs">{% trans "Next AKs" %}:</a></h2> <h2><a name="currentAKs">{% trans "Next AKs" %}:</a></h2>
{% with akslots_next as slots %} {% with akslots_next as slots %}
{% include "AKPlan/slots_table.html" %} {% include "AKPlan/slots_table.html" %}
{% endwith %} {% endwith %}
</div> </div>
<div class="col-md-9" style="height:98vh;"> <div class="col-md-9" style="height:98vh;">
<div id="planCalendar"></div> <div id="planCalendar"></div>
</div> </div>
</div> </div>
{% endtimezone %} <div style="position: absolute;bottom: 1vh;left:1vw;background-color: #FFFFFF;padding: 1vh;">
<input type="checkbox" name="autoreload" id="cbxAutoReload"> <label for="cbxAutoReload">{% trans "Reload page automatically?" %}</label>
</div>
{% endtimezone %}
</body> </body>
</html> </html>
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
<table class="table table-striped"> <table class="table table-striped">
{% for akslot in slots %} {% for akslot in slots %}
<tr> <tr>
<td><b><a href="{% url 'submit:ak_detail' event_slug=event.slug pk=akslot.ak.pk %}">{{ akslot.ak.name }}</a></b></td> <td class="breakWord"><b><a href="{{ akslot.ak.detail_url }}">{{ akslot.ak.name }}</a></b></td>
<td>{{ akslot.start | time:"H:i" }} - {{ akslot.end | time:"H:i" }}</td> <td>{{ akslot.start | time:"H:i" }} - {{ akslot.end | time:"H:i" }}</td>
<td>{{ akslot.room }}</td> <td class="breakWord">{% if akslot.room and akslot.room.pk != '' %}
<a href="{% url 'plan:plan_room' event_slug=event.slug pk=akslot.room.pk %}">{{ akslot.room }}</a>
{% endif %}</td>
</tr> </tr>
{% empty %} {% empty %}
{% trans "No AKs" %} {% trans "No AKs" %}
......
# gradients based on http://bsou.io/posts/color-gradients-with-python # gradients based on http://bsou.io/posts/color-gradients-with-python
def hex_to_rgb(hex): def hex_to_rgb(hex): #pylint: disable=redefined-builtin
""" """
Convert hex color to RGB color code Convert hex color to RGB color code
:param hex: hex encoded color :param hex: hex encoded color
...@@ -23,8 +23,7 @@ def rgb_to_hex(rgb): ...@@ -23,8 +23,7 @@ def rgb_to_hex(rgb):
""" """
# Components need to be integers for hex to make sense # Components need to be integers for hex to make sense
rgb = [int(x) for x in rgb] rgb = [int(x) for x in rgb]
return "#"+"".join(["0{0:x}".format(v) if v < 16 else return "#"+"".join([f"0{v:x}" if v < 16 else f"{v:x}" for v in rgb])
"{0:x}".format(v) for v in rgb])
def linear_blend(start_hex, end_hex, position): def linear_blend(start_hex, end_hex, position):
......
from datetime import datetime
from django import template from django import template
from django.utils.formats import date_format
from AKPlan.templatetags.color_gradients import darken from AKPlan.templatetags.color_gradients import darken
from AKPlanning import settings from AKPlanning import settings
...@@ -8,6 +11,19 @@ register = template.Library() ...@@ -8,6 +11,19 @@ register = template.Library()
@register.filter @register.filter
def highlight_change_colors(akslot): def highlight_change_colors(akslot):
"""
Adjust color to highlight recent changes if needed
:param akslot: akslot to determine color for
:type akslot: AKSlot
:return: color that should be used (either default color of the category or some kind of red)
:rtype: str
"""
# Do not highlight in preview mode or when changes occurred before the plan was published
if akslot.event.plan_hidden or (akslot.event.plan_published_at is not None
and akslot.event.plan_published_at > akslot.updated):
return akslot.ak.category.color
seconds_since_update = akslot.seconds_since_last_update seconds_since_update = akslot.seconds_since_last_update
# Last change long ago? Use default color # Last change long ago? Use default color
...@@ -17,4 +33,14 @@ def highlight_change_colors(akslot): ...@@ -17,4 +33,14 @@ def highlight_change_colors(akslot):
# Recent change? Calculate gradient blend between red and # Recent change? Calculate gradient blend between red and
recentness = seconds_since_update / settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS recentness = seconds_since_update / settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS
return darken("#b71540", recentness) return darken("#b71540", recentness)
# return linear_blend("#b71540", "#000000", recentness)
@register.simple_tag
def timestamp_now(tz):
"""
Get the current timestamp for the given timezone
:param tz: timezone to be used for the timestamp
:return: current timestamp in given timezone
"""
return date_format(datetime.now().astimezone(tz), "c")
# Create your tests here. from django.test import TestCase
from AKModel.tests.test_views import BasicViewTests
class PlanViewTests(BasicViewTests, TestCase):
"""
Tests for AKPlan
"""
fixtures = ['model.json']
APP_NAME = 'plan'
VIEWS = [
('plan_overview', {'event_slug': 'kif42'}),
('plan_wall', {'event_slug': 'kif42'}),
('plan_room', {'event_slug': 'kif42', 'pk': 2}),
('plan_track', {'event_slug': 'kif42', 'pk': 1}),
]
def test_plan_hidden(self):
"""
Test correct handling of plan visibility
"""
_, url = self._name_and_url(('plan_overview', {'event_slug': 'kif23'}))
self.client.logout()
response = self.client.get(url)
self.assertContains(response, "Plan is not visible (yet).",
msg_prefix="Plan is visible even though it shouldn't be")
self.client.force_login(self.staff_user)
response = self.client.get(url)
self.assertNotContains(response, "Plan is not visible (yet).",
msg_prefix="Plan is not visible for staff user")
def test_wall_redirect(self):
"""
Test: Make sure that user is redirected from wall to overview when plan is hidden
"""
_, url_wall = self._name_and_url(('plan_wall', {'event_slug': 'kif23'}))
_, url_plan = self._name_and_url(('plan_overview', {'event_slug': 'kif23'}))
response = self.client.get(url_wall)
self.assertRedirects(response, url_plan,
msg_prefix=f"Redirect away from wall not working ({url_wall} -> {url_plan})")
from csp.decorators import csp_replace
from django.urls import path, include from django.urls import path, include
from . import views from . import views
app_name = "plan" app_name = "plan"
...@@ -8,7 +10,7 @@ urlpatterns = [ ...@@ -8,7 +10,7 @@ urlpatterns = [
'<slug:event_slug>/plan/', '<slug:event_slug>/plan/',
include([ include([
path('', views.PlanIndexView.as_view(), name='plan_overview'), path('', views.PlanIndexView.as_view(), name='plan_overview'),
path('wall/', views.PlanScreenView.as_view(), name='plan_wall'), path('wall/', csp_replace(FRAME_ANCESTORS="*")(views.PlanScreenView.as_view()), name='plan_wall'),
path('room/<int:pk>/', views.PlanRoomView.as_view(), name='plan_room'), path('room/<int:pk>/', views.PlanRoomView.as_view(), name='plan_room'),
path('track/<int:pk>/', views.PlanTrackView.as_view(), name='plan_track'), path('track/<int:pk>/', views.PlanTrackView.as_view(), name='plan_track'),
]) ])
......
from datetime import timedelta from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.utils.datetime_safe import datetime from django.shortcuts import redirect
from django.views.generic import ListView, DetailView from django.urls import reverse_lazy
from django.views.generic import DetailView, ListView
from AKModel.models import AKSlot, Room, AKTrack from AKModel.metaviews.admin import FilterByEventSlugMixin
from AKModel.views import FilterByEventSlugMixin from AKModel.models import AKSlot, AKTrack, Room
class PlanIndexView(FilterByEventSlugMixin, ListView): class PlanIndexView(FilterByEventSlugMixin, ListView):
"""
Default plan view
Shows two lists of current and upcoming AKs and a graphical full plan below
"""
model = AKSlot model = AKSlot
template_name = "AKPlan/plan_index.html" template_name = "AKPlan/plan_index.html"
context_object_name = "akslots" context_object_name = "akslots"
...@@ -16,7 +22,7 @@ class PlanIndexView(FilterByEventSlugMixin, ListView): ...@@ -16,7 +22,7 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
def get_queryset(self): def get_queryset(self):
# Ignore slots not scheduled yet # Ignore slots not scheduled yet
return super().get_queryset().filter(start__isnull=False) return super().get_queryset().filter(start__isnull=False).select_related('ak', 'room', 'ak__category')
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)
...@@ -36,8 +42,8 @@ class PlanIndexView(FilterByEventSlugMixin, ListView): ...@@ -36,8 +42,8 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
if akslot.room is not None: if akslot.room is not None:
rooms.add(akslot.room) rooms.add(akslot.room)
# Store buildings for hierarchical view # Store buildings for hierarchical view
if akslot.room.building != '': if akslot.room.location != '':
buildings.add(akslot.room.building) buildings.add(akslot.room.location)
# Recent AKs: Started but not ended yet # Recent AKs: Started but not ended yet
if akslot.start <= current_timestamp <= akslot.end: if akslot.start <= current_timestamp <= akslot.end:
...@@ -52,44 +58,100 @@ class PlanIndexView(FilterByEventSlugMixin, ListView): ...@@ -52,44 +58,100 @@ class PlanIndexView(FilterByEventSlugMixin, ListView):
if settings.PLAN_SHOW_HIERARCHY: if settings.PLAN_SHOW_HIERARCHY:
context["buildings"] = sorted(buildings) context["buildings"] = sorted(buildings)
context["tracks"] = self.event.aktrack_set.all()
return context return context
class PlanScreenView(PlanIndexView): class PlanScreenView(PlanIndexView):
"""
Plan view optimized for screens and projectors
This again shows current and upcoming AKs as well as a graphical plan,
but no navigation elements and trys to use the available space as best as possible
such that no scrolling is needed.
The view contains a frontend functionality for auto-reload.
"""
template_name = "AKPlan/plan_wall.html" template_name = "AKPlan/plan_wall.html"
def get(self, request, *args, **kwargs):
s = super().get(request, *args, **kwargs)
# Don't show wall when event is not active -> redirect to normal schedule
if not self.event.active or (self.event.plan_hidden and not request.user.is_staff):
return redirect(reverse_lazy("plan:plan_overview", kwargs={"event_slug": self.event.slug}))
return s
# pylint: disable=attribute-defined-outside-init
def get_queryset(self): def get_queryset(self):
# Determine interesting range (some hours ago until some hours in the future as specified in the settings) now = datetime.now().astimezone(self.event.timezone)
self.start = datetime.now().astimezone(self.event.timezone) - timedelta(hours=settings.PLAN_WALL_HOURS_RETROSPECT) # Wall during event: Adjust, show only parts in the future
self.end = self.start + timedelta(hours=(settings.PLAN_WALL_HOURS_RETROSPECT + settings.PLAN_WALL_HOURS_FUTURE)) if self.event.start < now < self.event.end:
# Determine interesting range (some hours ago until some hours in the future as specified in the settings)
self.start = now - timedelta(hours=settings.PLAN_WALL_HOURS_RETROSPECT)
else:
self.start = self.event.start
self.end = self.event.end
# Restrict AK slots to relevant ones # Restrict AK slots to relevant ones
# This will automatically filter all rooms not needed for the selected range in the orginal get_context method # This will automatically filter all rooms not needed for the selected range in the orginal get_context method
return super().get_queryset().filter(start__gt=self.start, start__lt=self.end) akslots = super().get_queryset().filter(start__gt=self.start)
# Find the earliest hour AKs start and end (handle 00:00 as 24:00)
self.earliest_start_hour = 23
self.latest_end_hour = 1
for akslot in akslots.all():
start_hour = akslot.start.astimezone(self.event.timezone).hour
if start_hour < self.earliest_start_hour:
# Use hour - 1 to improve visibility of date change
self.earliest_start_hour = max(start_hour - 1, 0)
end_hour = akslot.end.astimezone(self.event.timezone).hour
# Special case: AK starts before but ends after midnight -- show until midnight
if end_hour < start_hour:
self.latest_end_hour = 24
elif end_hour > self.latest_end_hour:
# Always use hour + 1, since AK may end at :xy and not always at :00
self.latest_end_hour = min(end_hour + 1, 24)
return akslots
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
context["start"] = self.start context["start"] = self.start
context["end"] = self.end context["end"] = self.event.end
context["earliest_start_hour"] = self.earliest_start_hour
context["latest_end_hour"] = self.latest_end_hour
return context return context
class PlanRoomView(FilterByEventSlugMixin, DetailView): class PlanRoomView(FilterByEventSlugMixin, DetailView):
"""
Plan view for a single room
"""
template_name = "AKPlan/plan_room.html" template_name = "AKPlan/plan_room.html"
model = Room model = Room
context_object_name = "room" context_object_name = "room"
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
# Restrict AKSlot list to the given room
# while joining AK, room and category information to reduce the amount of necessary SQL queries
context["slots"] = AKSlot.objects.filter(room=context['room']).select_related('ak', 'ak__category', 'ak__track')
return context
class PlanTrackView(FilterByEventSlugMixin, DetailView): class PlanTrackView(FilterByEventSlugMixin, DetailView):
"""
Plan view for a single track
"""
template_name = "AKPlan/plan_track.html" template_name = "AKPlan/plan_track.html"
model = AKTrack model = AKTrack
context_object_name = "track" context_object_name = "track"
def get_context_data(self, *, object_list=None, **kwargs): def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs) context = super().get_context_data(object_list=object_list, **kwargs)
# Restrict AKSlot list to given track
context["slots"] = [] # while joining AK, room and category information to reduce the amount of necessary SQL queries
for ak in context["track"].ak_set.all(): context["slots"] = AKSlot.objects. \
context["slots"].extend(ak.akslot_set.all()) filter(event=self.event, ak__track=context['track']). \
select_related('ak', 'room', 'ak__category')
return context return context
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" 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: 2019-10-29 14:50+0000\n" "POT-Creation-Date: 2023-08-16 16:30+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -17,10 +17,10 @@ msgstr "" ...@@ -17,10 +17,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: AKPlanning/settings.py:126 #: AKPlanning/settings.py:148
msgid "German" msgid "German"
msgstr "Deutsch" msgstr "Deutsch"
#: AKPlanning/settings.py:127 #: AKPlanning/settings.py:149
msgid "English" msgid "English"
msgstr "Englisch" msgstr "Englisch"
...@@ -37,18 +37,29 @@ INSTALLED_APPS = [ ...@@ -37,18 +37,29 @@ INSTALLED_APPS = [
'AKSubmission.apps.AksubmissionConfig', 'AKSubmission.apps.AksubmissionConfig',
'AKScheduling.apps.AkschedulingConfig', 'AKScheduling.apps.AkschedulingConfig',
'AKPlan.apps.AkplanConfig', 'AKPlan.apps.AkplanConfig',
'django.contrib.admin', 'AKOnline.apps.AkonlineConfig',
'AKModel.apps.AKAdminConfig',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'bootstrap4', 'debug_toolbar',
'fontawesome_5', 'django_bootstrap5',
'fontawesomefree',
'fontawesome_6',
'timezone_field', 'timezone_field',
'rest_framework',
'simple_history',
'registration',
'django_tex',
'compressor',
'docs',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.gzip.GZipMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
...@@ -56,7 +67,9 @@ MIDDLEWARE = [ ...@@ -56,7 +67,9 @@ MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'csp.middleware.CSPMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
] ]
ROOT_URLCONF = 'AKPlanning.urls' ROOT_URLCONF = 'AKPlanning.urls'
...@@ -77,10 +90,20 @@ TEMPLATES = [ ...@@ -77,10 +90,20 @@ TEMPLATES = [
], ],
}, },
}, },
{
'NAME': 'tex',
'BACKEND': 'django_tex.engine.TeXEngine',
'APP_DIRS': True,
'OPTIONS': {
'environment': 'AKModel.environment.improved_tex_environment',
},
},
] ]
WSGI_APPLICATION = 'AKPlanning.wsgi.application' WSGI_APPLICATION = 'AKPlanning.wsgi.application'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Database # Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
...@@ -118,8 +141,6 @@ TIME_ZONE = 'UTC' ...@@ -118,8 +141,6 @@ TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = True
USE_L10N = True
USE_TZ = True USE_TZ = True
LANGUAGES = [ LANGUAGES = [
...@@ -127,6 +148,11 @@ LANGUAGES = [ ...@@ -127,6 +148,11 @@ LANGUAGES = [
('en', _('English')), ('en', _('English')),
] ]
INTERNAL_IPS = ['127.0.0.1', '::1']
LATEX_INTERPRETER = 'lualatex'
LATEX_RUN_COUNT = 2
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/ # https://docs.djangoproject.com/en/2.2/howto/static-files/
...@@ -136,17 +162,38 @@ STATICFILES_DIRS = ( ...@@ -136,17 +162,38 @@ STATICFILES_DIRS = (
'static_common', 'static_common',
) )
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
)
# Settings for Bootstrap # Settings for Bootstrap
BOOTSTRAP4 = { BOOTSTRAP5 = {
# Use custom CSS "javascript_url": {
"css_url": { "url": STATIC_URL + "common/vendor/bootstrap/bootstrap-5.0.2.bundle.min.js",
"href": STATIC_URL + "common/css/bootstrap.css",
}, },
} }
# Settings for FontAwesome # Settings for FontAwesome
FONTAWESOME_5_CSS_URL = "//cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.css" FONTAWESOME_6_CSS_URL = STATIC_URL + "fontawesomefree/css/all.min.css"
FONTAWESOME_5_PREFIX = "fa" FONTAWESOME_6_PREFIX = "fa"
# Compressor and minifier config
COMPRESS_ENABLED = True
COMPRESS_CSS_HASHING_METHOD = 'content'
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'),
)
COMPRESS_FILTERS = {
'css': [
'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.rCSSMinFilter',
],
'js': [
'compressor.filters.jsmin.JSMinFilter',
]
}
# Treat wishes as seperate category in submission views? # Treat wishes as seperate category in submission views?
WISHES_AS_CATEGORY = True WISHES_AS_CATEGORY = True
...@@ -161,10 +208,38 @@ FOOTER_INFO = { ...@@ -161,10 +208,38 @@ FOOTER_INFO = {
PLAN_MAX_NEXT_AKS = 10 PLAN_MAX_NEXT_AKS = 10
# Specify range of plan for screen/projector view # Specify range of plan for screen/projector view
PLAN_WALL_HOURS_RETROSPECT = 3 PLAN_WALL_HOURS_RETROSPECT = 3
PLAN_WALL_HOURS_FUTURE = 18
# Should the plan use a hierarchy of buildings and rooms? # Should the plan use a hierarchy of buildings and rooms?
PLAN_SHOW_HIERARCHY = True PLAN_SHOW_HIERARCHY = True
# For which time (in seconds) should changes of akslots be highlighted in plan? # For which time (in seconds) should changes of akslots be highlighted in plan?
PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS = 2 * 60 * 60 PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS = 2 * 60 * 60
# Show feed of recent changes in dashboard
DASHBOARD_SHOW_RECENT = True
# How many entries max?
DASHBOARD_RECENT_MAX = 25
# How many events should be featured in the dashboard
# (active events will always be featured, even if their number is higher than this threshold)
DASHBOARD_MAX_FEATURED_EVENTS = 3
# Registration/login behavior
SIMPLE_BACKEND_REDIRECT_URL = "/user/"
LOGIN_REDIRECT_URL = SIMPLE_BACKEND_REDIRECT_URL
# Content Security Policy
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", "'unsafe-eval'")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", "fonts.googleapis.com")
CSP_IMG_SRC = ("'self'", "data:")
CSP_FRAME_SRC = ("'self'", )
CSP_FONT_SRC = ("'self'", "data:", "fonts.gstatic.com")
# Emails
SEND_MAILS = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Documentation
DOCS_ROOT = os.path.join(BASE_DIR, 'docs/_build/html')
DOCS_ACCESS = 'public'
include(optional("settings/*.py")) include(optional("settings/*.py"))
# noinspection PyUnresolvedReferences
from AKPlanning.settings import *
DEBUG = False
SECRET_KEY = '+7#&=$grg7^x62m#3cuv)k$)tqx!xkj_o&y9sm)@@sgj7_7-!+'
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'mysql',
'NAME': 'test',
'USER': 'django',
'PASSWORD': 'mysql',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
'TEST': {
'NAME': 'tests',
'CHARSET': "utf8mb4",
'COLLATION': 'utf8mb4_unicode_ci',
},
}
}
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
TEST_OUTPUT_FILE_NAME = 'unit.xml'
...@@ -25,7 +25,7 @@ CSRF_COOKIE_SECURE = True ...@@ -25,7 +25,7 @@ CSRF_COOKIE_SECURE = True
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'HOST': 'localhost', 'HOST': getattr(secrets, "DB_HOST", "localhost"),
'NAME': secrets.DB_NAME, 'NAME': secrets.DB_NAME,
'USER': secrets.DB_USER, 'USER': secrets.DB_USER,
'PASSWORD': secrets.DB_PASSWORD, 'PASSWORD': secrets.DB_PASSWORD,
...@@ -35,4 +35,8 @@ DATABASES = { ...@@ -35,4 +35,8 @@ DATABASES = {
} }
} }
### EMAILS ###
SEND_MAILS = True
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# TODO: caching # TODO: caching
...@@ -6,4 +6,7 @@ DB_NAME = '' ...@@ -6,4 +6,7 @@ DB_NAME = ''
DB_USER = '' DB_USER = ''
DB_PASSWORD = '' DB_PASSWORD = ''
\ No newline at end of file
# Optional, if not set, localhost is assumed
# DB_HOST = ''
"""AKPlanning URL Configuration """
AKPlanning URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see: The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/ https://docs.djangoproject.com/en/2.2/topics/http/urls/
...@@ -13,13 +14,20 @@ Including another URLconf ...@@ -13,13 +14,20 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
import debug_toolbar
from django.apps import apps from django.apps import apps
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include, re_path
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('i18n/', include('django.conf.urls.i18n')) re_path(r'^docs/', include('docs.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('accounts/', include('registration.backends.simple.urls')),
path('', include('AKModel.urls', namespace='model')),
path('i18n/', include('django.conf.urls.i18n')),
path('__debug__/', include(debug_toolbar.urls)),
] ]
# Load URLs dynamically (only if components are active) # Load URLs dynamically (only if components are active)
......
# Register your models here.