Skip to content
Snippets Groups Projects
Commit ff119c0d authored by Benjamin Hättasch's avatar Benjamin Hättasch
Browse files

Add basic schedule views (desktop/mobile and screen/projector)

Add views
Add templates
Add urls
Add translations
Add three corresponding settings to central settings file
Minor: Fix typo in plan_akslot
parent 87ee2d0f
No related branches found
No related tags found
No related merge requests found
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-02 01:08+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: templates/AKPlan/plan_index.html:47
msgid "Day"
msgstr "Tag"
#: templates/AKPlan/plan_index.html:57
msgid "Event"
msgstr "Veranstaltung"
#: templates/AKPlan/plan_index.html:66 templates/AKPlan/plan_screen.html:64
msgid "Room"
msgstr "Raum"
#: templates/AKPlan/plan_index.html:106
msgid "AK Plan"
msgstr "AK-Plan"
#: templates/AKPlan/plan_index.html:112
msgid "screen view"
msgstr "Anzeigeansicht"
#: templates/AKPlan/plan_index.html:120 templates/AKPlan/plan_screen.html:99
msgid "Current AKs"
msgstr "Aktuelle AKs"
#: templates/AKPlan/plan_index.html:127 templates/AKPlan/plan_screen.html:104
msgid "Next AKs"
msgstr "Nächste AKs"
#: templates/AKPlan/plan_index.html:144
msgid "Write to organizers of this event for questions and comments"
msgstr "Fragen oder Kommentare? Schreib den Orgas dieses Events eine Mail"
#: templates/AKPlan/slots_table.html:12
msgid "No AKs"
msgstr "Keine AKs"
......@@ -25,7 +25,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Adapt to timezone of the connected event
timeZone: '{{ ak.event.timezone }}',
defaultView: 'timeGrid',
// Adapt to user selected loclae
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
// No header, not buttons
header: {
......
{% extends "base.html" %}
{% load fontawesome %}
{% load i18n %}
{% load tags_AKModel %}
{% load static %}
{% load tz %}
{% block imports %}
{% get_current_language as LANGUAGE_CODE %}
<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>
{% 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>
document.addEventListener('DOMContentLoaded', function() {
var planEl = document.getElementById('planCalendar');
var plan = new FullCalendar.Calendar(planEl, {
plugins: ['resourceTimeline', 'bootstrap'],
timeZone: '{{ event.timezone }}',
header: {
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: {
type: 'resourceTimeline',
visibleRange: {
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"}}',
},
buttonText: '{% trans "Event" %}',
}
},
editable: false,
allDaySlot: false,
nowIndicator: true,
eventTextColor: '#fff',
eventColor: '#127ba3',
resourceAreaWidth: '15%',
resourceLabelText: '{% trans "Room" %}',
resources: [
{% for room in rooms %}
{
'id': '{{ room.title }}',
'title': '{{ room.title }}'
},
{% endfor %}
],
events: [
{% for slot in akslots %}
{% if slot.room and slot.start %}
{
'title': '{{ slot.ak.name }}',
'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" }}',
'resourceId': '{{ slot.room.title }}',
},
{% endif %}
{% endfor %}
],
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
});
plan.render();
});
</script>
{% endblock imports %}
{% block breadcrumbs %}
<li class="breadcrumb-item">
{% if 'AKDashboard'|check_app_installed %}
<a href="{% url 'dashboard:dashboard' %}">AKPlanning</a>
{% else %}
AKPlanning
{% endif %}
</li>
<li class="breadcrumb-item">{{ event.slug }}</li>
<li class="breadcrumb-item"><a
href="{% url 'plan:plan_overview' event_slug=event.slug %}">{% trans "AK Plan" %}</a></li>
{% endblock %}
{% block content %}
<div class="float-right">
<a href="{% url 'plan:plan_screen' event_slug=event.slug %}" class="btn btn-success">{% fontawesome_icon 'desktop' %}&nbsp;&nbsp;{% trans "screen view" %}</a>
</div>
<h1>Plan: {{ event }}</h1>
{% timezone event.timezone %}
<div class="row" style="margin-top:30px;">
<div class="col-md-6">
<h2><a name="currentAKs">{% trans "Current AKs" %}:</a></h2>
{% with akslots_now as slots %}
{% include "AKPlan/slots_table.html" %}
{% endwith %}
</div>
<div class="col-md-6">
<h2><a name="currentAKs">{% trans "Next AKs" %}:</a></h2>
{% with akslots_next as slots %}
{% include "AKPlan/slots_table.html" %}
{% endwith %}
</div>
<div class="col-md-12">
<div id="planCalendar" style="margin-top:30px;"></div>
</div>
</div>
{% endtimezone %}
{% endblock %}
{% block footer_custom %}
{% if event.contact_email %}
<h4>
<a href="mailto:{{ event.contact_email }}">{% fontawesome_icon "envelope" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
</h4>
{% endif %}
{% endblock %}
{% load static %}
{% load i18n %}
{% load bootstrap4 %}
{% load fontawesome %}
{% load tags_AKModel %}
{% load tz %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}AK Planning{% endblock %}</title>
{# Load Bootstrap CSS and JavaScript as well as font awesome #}
{% bootstrap_css %}
{% bootstrap_javascript jquery='slim' %}
{% fontawesome_stylesheet %}
<link rel="stylesheet" href="{% static 'common/css/custom.css' %}">
{% get_current_language as LANGUAGE_CODE %}
<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>
{% 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>
document.addEventListener('DOMContentLoaded', function() {
var planEl = document.getElementById('planCalendar');
var plan = new FullCalendar.Calendar(planEl, {
plugins: [ 'resourceTimeline', 'bootstrap'],
timeZone: '{{ event.timezone }}',
header: false,
themeSystem: 'bootstrap',
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
type: 'resourceTimeline',
slotDuration: '01:00',
defaultView: 'resourceTimeline',
visibleRange: {
start: '{{ start | 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" }}',
editable: false,
allDaySlot: false,
nowIndicator: true,
eventTextColor: '#fff',
eventColor: '#127ba3',
height: 'parent',
resourceAreaWidth: '15%',
resourceLabelText: '{% trans "Room" %}',
resources: [
{% for room in rooms %}
{
'id': '{{ room.title }}',
'title': '{{ room.title }}'
},
{% endfor %}
],
events: [
{% for slot in akslots %}
{% if slot.room and slot.start %}
{
'title': '{{ slot.ak.name }}',
'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" }}',
'resourceId': '{{ slot.room.title }}',
},
{% endif %}
{% endfor %}
],
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
});
plan.render();
});
</script>
</head>
<body>
{% timezone event.timezone %}
<div class="row" style="height:100vh;margin:0;padding:1vh;">
<div class="col-md-3">
<h1>Plan: {{ event }}</h1>
<h2><a name="currentAKs">{% trans "Current AKs" %}:</a></h2>
{% with akslots_now as slots %}
{% include "AKPlan/slots_table.html" %}
{% endwith %}
<h2><a name="currentAKs">{% trans "Next AKs" %}:</a></h2>
{% with akslots_next as slots %}
{% include "AKPlan/slots_table.html" %}
{% endwith %}
</div>
<div class="col-md-9" style="height:98vh;">
<div id="planCalendar"></div>
</div>
</div>
{% endtimezone %}
</body>
</html>
{% load i18n %}
<table class="table table-striped">
{% for akslot in slots %}
<tr>
<td><b><a href="{% url 'submit:ak_detail' event_slug=event.slug pk=akslot.ak.pk %}">{{ akslot.ak.name }}</a></b></td>
<td>{{ akslot.start | time:"H:i" }} - {{ akslot.end | time:"H:i" }}</td>
<td>{{ akslot.room }}</td>
</tr>
{% empty %}
{% trans "No AKs" %}
{% endfor %}
</table>
from django.urls import path, include
from . import views
app_name = "plan"
urlpatterns = [
path(
'<slug:event_slug>/plan/',
include([
path('', views.PlanIndexView.as_view(), name='plan_overview'),
path('screen/', views.PlanScreenView.as_view(), name='plan_screen'),
])
),
]
# Create your views here.
from datetime import timedelta
from django.conf import settings
from django.utils.datetime_safe import datetime
from django.views.generic import ListView
from AKModel.models import AKSlot
from AKModel.views import FilterByEventSlugMixin
class PlanIndexView(FilterByEventSlugMixin, ListView):
model = AKSlot
template_name = "AKPlan/plan_index.html"
context_object_name = "akslots"
ordering = "start"
def get_queryset(self):
# Ignore slots not scheduled yet
return super().get_queryset().filter(start__isnull=False)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context["event"] = self.event
current_timestamp = datetime.now().astimezone(self.event.timezone)
context["akslots_now"] = []
context["akslots_next"] = []
rooms = set()
# Get list of current and next slots
for akslot in context["akslots"]:
# Construct a list of all rooms used by these slots on the fly
if akslot.room is not None:
rooms.add(akslot.room)
# Recent AKs: Started but not ended yet
if akslot.start <= current_timestamp <= akslot.end:
context["akslots_now"].append(akslot)
# Next AKs: Not started yet, list will be filled in order until threshold is reached
elif akslot.start > current_timestamp:
if len(context["akslots_next"]) < settings.PLAN_MAX_NEXT_AKS:
context["akslots_next"].append(akslot)
# Sort list of rooms by title
context["rooms"] = sorted(rooms, key=lambda x: x.title)
return context
class PlanScreenView(PlanIndexView):
template_name = "AKPlan/plan_screen.html"
def get_queryset(self):
# Determine interesting range (some hours ago until some hours in the future as specified in the settings)
self.start = datetime.now().astimezone(self.event.timezone) - timedelta(hours=settings.PLAN_BEAMER_HOURS_RETROSPECT)
self.end = self.start + timedelta(hours=(settings.PLAN_BEAMER_HOURS_RETROSPECT + settings.PLAN_BEAMER_HOURS_FUTURE))
# Restrict AK slots to relevant ones
# 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)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context["start"] = self.start
context["end"] = self.end
return context
......@@ -36,7 +36,7 @@ INSTALLED_APPS = [
'AKDashboard.apps.AkdashboardConfig',
'AKSubmission.apps.AksubmissionConfig',
'AKScheduling.apps.AkschedulingConfig',
# 'AKPlan.apps.AkplanConfig',
'AKPlan.apps.AkplanConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
......@@ -46,6 +46,7 @@ INSTALLED_APPS = [
'bootstrap4',
'fontawesome',
'timezone_field',
'rest_framework',
]
MIDDLEWARE = [
......@@ -156,4 +157,10 @@ FOOTER_INFO = {
"impress_url": ""
}
# How many AKs should be visible as next AKs
PLAN_MAX_NEXT_AKS = 10
# Specify range of plan for screen/projector view
PLAN_SCREEN_HOURS_RETROSPECT = 3
PLAN_SCREEN_HOURS_FUTURE = 18
include(optional("settings/*.py"))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment