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
  • koma/feature/preference-polling-form
  • komasolver
  • main
  • renovate/django-5.x
  • renovate/django_csp-4.x
  • renovate/uwsgi-2.x
6 results

Target

Select target project
  • konstantin/akplanning
  • matedealer/akplanning
  • kif/akplanning
  • mirco/akplanning
  • lordofthevoid/akplanning
  • voidptr/akplanning
  • xayomer/akplanning-fork
  • mollux/akplanning
  • neumantm/akplanning
  • mmarx/akplanning
  • nerf/akplanning
  • felix_bonn/akplanning
  • sebastian.uschmann/akplanning
13 results
Select Git revision
  • 520-akowner
  • 520-fix-event-wizard-datepicker
  • 520-fix-scheduling
  • 520-improve-scheduling
  • 520-improve-scheduling-2
  • 520-improve-submission
  • 520-improve-trackmanager
  • 520-improve-wall
  • 520-message-resolved
  • 520-status
  • 520-upgrades
  • add_express_interest_to_ak_overview
  • admin-production-color
  • bugfixes
  • csp
  • featire-ical-export
  • feature-ak-requirement-lists
  • feature-akslide-export-better-filename
  • feature-akslides
  • feature-better-admin
  • feature-better-cv-list
  • feature-colors
  • feature-constraint-checking
  • feature-constraint-checking-wip
  • feature-dashboard-history-button
  • feature-event-status
  • feature-event-wizard
  • feature-export-flag
  • feature-improve-admin
  • feature-improve-filters
  • feature-improved-user-creation-workflow
  • feature-interest-view
  • feature-mails
  • feature-modular-status
  • feature-plan-autoreload
  • feature-present-default
  • feature-register-link
  • feature-remaining-constraint-validation
  • feature-room-import
  • feature-scheduler-improve
  • feature-scheduling-2.0
  • feature-special-attention
  • feature-time-input
  • feature-tracker
  • feature-wiki-wishes
  • feature-wish-slots
  • feature-wizard-buttons
  • features-availabilities
  • fix-ak-times-above-folg
  • fix-api
  • fix-constraint-violation-string
  • fix-cv-checking
  • fix-default-slot-length
  • fix-default-slot-localization
  • fix-doc-minor
  • fix-duration-display
  • fix-event-tz-pytz-update
  • fix-history-interest
  • fix-interest-view
  • fix-js
  • fix-pipeline
  • fix-plan-timezone-now
  • fix-room-add
  • fix-scheduling-drag
  • fix-slot-defaultlength
  • fix-timezone
  • fix-translation-scheduling
  • fix-virtual-room-admin
  • fix-wizard-csp
  • font-locally
  • improve-admin
  • improve-online
  • improve-slides
  • improve-submission-coupling
  • interest_restriction
  • main
  • master
  • meta-debug-toolbar
  • meta-export
  • meta-makemessages
  • meta-performance
  • meta-tests
  • meta-tests-gitlab-test
  • meta-upgrades
  • mollux-master-patch-02906
  • port-availabilites-fullcalendar
  • qs
  • remove-tags
  • renovate/configure
  • renovate/django-4.x
  • renovate/django-5.x
  • renovate/django-bootstrap-datepicker-plus-5.x
  • renovate/django-bootstrap5-23.x
  • renovate/django-bootstrap5-24.x
  • renovate/django-compressor-4.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-registration-redux-2.x
  • renovate/django-simple-history-3.x
  • renovate/django-split-settings-1.x
  • renovate/django-timezone-field-5.x
100 results
Show changes
Showing
with 1362 additions and 15 deletions
{% load static %}
{% load tz %}
{% load i18n %}
{% load tags_AKPlan %}
{% include "AKModel/load_fullcalendar.html" %}
<script>
{% get_current_language as LANGUAGE_CODE %}
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('akSlotCalendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
// Adapt to timezone of the connected event
timeZone: '{{ ak.event.timezone }}',
initialView: 'timeGrid',
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
// No header, not buttons
headerToolbar: false,
aspectRatio: 2.5,
themeSystem: 'bootstrap5',
buttonIcons: {
prev: 'ignore fa-solid fa-angle-left',
next: 'ignore fa-solid fa-angle-right',
},
// Only show calendar view for the dates of the connected event
visibleRange: {
start: '{{ ak.event.start | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}',
end: '{{ ak.event.end | timezone:ak.event.timezone | date:"Y-m-d H:i:s"}}',
},
scrollTime: '08:00:00',
allDaySlot: false,
nowIndicator: true,
now: "{% timestamp_now event.timezone %}",
eventTextColor: '#fff',
eventColor: '#127ba3',
// Create entries for all scheduled slots
events: [
{% if not ak.event.plan_hidden or user.is_staff %}
{% for slot in ak.akslot_set.all %}
{% if slot.start %}
{
'title': '{{ slot.room }}',
'start': '{{ slot.start | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}',
'end': '{{ slot.end | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}',
'url' : '{% if slot.room %}{% url "plan:plan_room" event_slug=ak.event.slug pk=slot.room.pk %}{% else %}#{% endif %}'
},
{% endif %}
{% endfor %}
{% endif %}
{% for a in availabilities %}
{
title: '{{ Verfuegbarkeit }}',
start: '{{ a.start | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}',
end: '{{ a.end | timezone:ak.event.timezone | date:"Y-m-d H:i:s" }}',
backgroundColor: '#28B62C',
display: 'background'
},
{% endfor %}
],
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
});
calendar.render();
});
</script>
{% extends "base.html" %}
{% load fontawesome_6 %}
{% load i18n %}
{% load static %}
{% block meta %}
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="{{ event.name }} - {% trans "Plan" %}" />
{% endblock %}
{% block imports %}
{% include "AKModel/load_fullcalendar.html" %}
{% block fullcalendar %}{% endblock %}
{% endblock imports %}
{% block footer_custom %}
{% if event.contact_email %}
<h4>
<a href="mailto:{{ event.contact_email }}">{% fa6_icon "envelope" "far" %} {% trans "Write to organizers of this event for questions and comments" %}</a>
</h4>
{% endif %}
{% endblock %}
{% load i18n %}
{% load tags_AKModel %}
<li class="breadcrumb-item">
{% if 'AKDashboard'|check_app_installed %}
<a href="{% url 'dashboard:dashboard' %}">AKPlanning</a>
{% else %}
AKPlanning
{% endif %}
</li>
<li class="breadcrumb-item">
{% if 'AKDashboard'|check_app_installed %}
<a href="{% url 'dashboard:dashboard_event' slug=event.slug %}">{{ event }}</a>
{% else %}
{{ event }}
{% endif %}
</li>
{% extends "AKPlan/plan_base.html" %}
{% load fontawesome_6 %}
{% load i18n %}
{% load static %}
{% load tz %}
{% load tags_AKPlan %}
{% block fullcalendar %}
{% if not event.plan_hidden or user.is_staff %}
{% get_current_language as LANGUAGE_CODE %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var calendarEl = document.getElementById('planCalendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
// Adapt to timezone of the connected event
timeZone: '{{ event.timezone }}',
initialView: 'timeGrid',
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
// No header, not buttons
headerToolbar: {
left: '',
center: '',
right: ''
},
aspectRatio: 2,
themeSystem: 'bootstrap5',
buttonIcons: {
prev: 'ignore fa-solid fa-angle-left',
next: 'ignore fa-solid fa-angle-right',
},
// Only show calendar view for the dates of the connected event
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"}}',
},
scrollTime: '08:00:00',
allDaySlot: false,
nowIndicator: true,
now: "{% timestamp_now event.timezone %}",
eventTextColor: '#fff',
eventColor: '#127ba3',
// Create entries for all scheduled slots
events: {% block encode %}{% endblock %},
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
});
calendar.render();
});
</script>
{% endif %}
{% endblock %}
{% extends "AKPlan/plan_base.html" %}
{% load fontawesome_6 %}
{% load i18n %}
{% load static %}
{% load tz %}
{% load tags_AKPlan %}
{% block fullcalendar %}
{% if not event.plan_hidden or user.is_staff %}
{% get_current_language as LANGUAGE_CODE %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var planEl = document.getElementById('planCalendar');
var plan = new FullCalendar.Calendar(planEl, {
timeZone: '{{ event.timezone }}',
headerToolbar: {
left: 'today prev,next',
center: 'title',
right: 'resourceTimelineDay,resourceTimelineEvent'
},
themeSystem: 'bootstrap5',
buttonIcons: {
prev: 'ignore fa-solid fa-angle-left',
next: 'ignore fa-solid fa-angle-right',
},
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
initialView: '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" %}',
}
},
eventDidMount: function(info) {
$(info.el).tooltip({title: info.event.extendedProps.description});
},
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>
{% if type_filtering_active %}
{# Type filter script #}
<script type="module">
const { createApp } = Vue
createApp({
delimiters: ["[[", "]]"],
data() {
return {
types: JSON.parse("{{ types | escapejs }}"),
strict: {{ types_filter_strict|yesno:"true,false" }},
empty: {{ types_filter_empty|yesno:"true,false" }}
}
},
methods: {
onFilterChange(type) {
// Re-generate filter url
const typeString = "types="
+ this.types
.map(t => `${t.slug}:${t.state ? 'yes' : 'no'}`)
.join(',')
+ `&strict=${this.strict ? 'yes' : 'no'}`
+ `&empty=${this.empty ? 'yes' : 'no'}`;
// Redirect to the new url including the adjusted filters
const baseUrl = window.location.origin + window.location.pathname;
window.location.href = `${baseUrl}?${typeString}`;
}
}
}).mount('#filter');
// Prevent showing of cached form values for filter inputs when using broswer navigation
window.addEventListener('pageshow', function(event) {
if (event.persisted) {
window.location.reload();
}
});
</script>
{% endif %}
{% endif %}
{% endblock %}
{% block breadcrumbs %}
{% include "AKPlan/plan_breadcrumbs.html" %}
<li class="breadcrumb-item">
{% trans "AK Plan" %}
</li>
{% endblock %}
{% block content %}
{% include "messages.html" %}
<div class="float-end">
<ul class="nav nav-pills">
{% if rooms|length > 0 %}
<li class="nav-item dropdown">
<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="">
{% 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>
{% endfor %}
</div>
</li>
{% endif %}
{% if tracks|length > 0 %}
<li class="nav-item dropdown">
<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">
{% 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 %}?{{ query_string }}">{% fa6_icon 'desktop' 'fas' %}&nbsp;&nbsp;{% trans "AK Wall" %}</a>
</li>
{% endif %}
</ul>
</div>
<h1 class="mb-3">{% trans "Plan:" %} {{ event }}</h1>
{% if type_filtering_active %}
{# Type filter HTML #}
<div class="card border-primary mb-3">
<div class="card-header">
{% trans 'Filter by types' %}
</div>
<div class="card-body d-flex" id="filter">
{% trans "Types:" %}
<div id="filterTypes" class="d-flex">
<div class="form-check form-switch ms-3" v-for="type in types">
<label class="form-check-label" :for="'typeFilterType' + type.slug">[[ type.name ]]</label>
<input class="form-check-input" type="checkbox" :id="'typeFilterType' + type.slug " v-model="type.state" @change="onFilterChange()">
</div>
</div>
<div class="form-check form-switch ms-5">
<label class="form-check-label" for="typeFilterEmpty">{% trans "AKs without type" %}</label>
<input class="form-check-input" type="checkbox" id="typeFilterEmpty" v-model="empty" @change="onFilterChange()">
</div>
<div class="form-check form-switch ms-5">
<label class="form-check-label" for="typeFilterStrict">{% trans "No AKs with additional other types" %}</label>
<input class="form-check-input" type="checkbox" id="typeFilterStrict" v-model="strict" @change="onFilterChange()">>
</div>
</div>
</div>
{% endif %}
{% timezone event.timezone %}
<div class="row" style="margin-top:30px;">
{% if not event.plan_hidden or user.is_staff %}
{% if event.active %}
<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>
{% else %}
<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>
{% endtimezone %}
{% endblock %}
{% extends "AKPlan/plan_detail.html" %}
{% load fontawesome_6 %}
{% load tags_AKModel %}
{% load tz %}
{% load i18n %}
{% block breadcrumbs %}
{% include "AKPlan/plan_breadcrumbs.html" %}
<li class="breadcrumb-item">
<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 %}
{% block encode %}
[
{% for slot in slots %}
{% if slot.start %}
{'title': '{{ slot.ak }}',
'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" }}',
'url': '{{ slot.ak.detail_url }}',
'borderColor': '{{ slot.ak.track.color }}',
'color': '{{ slot.ak.category.color }}',
},
{% endif %}
{% 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 %}
{% block content %}
<div class="float-end">
<ul class="nav nav-pills">
<li class="nav-item dropdown">
<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="">
{% 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>
{% endfor %}
</div>
</li>
</ul>
</div>
<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 %}
{% if not event.plan_hidden or user.is_staff %}
{% timezone event.timezone %}
<div class="row" style="margin-top:30px;clear:both;">
<div class="col-md-12">
<div id="planCalendar"></div>
</div>
</div>
{% endtimezone %}
{% else %}
<div class="alert alert-warning mt-3">
<p class="mb-0">{% trans "Plan is not visible (yet)." %}</p>
</div>
{% 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 %}
{% extends "AKPlan/plan_detail.html" %}
{% load tz %}
{% load i18n %}
{% block breadcrumbs %}
{% include "AKPlan/plan_breadcrumbs.html" %}
<li class="breadcrumb-item">
<a href="{% url 'plan:plan_overview' event_slug=event.slug %}">{% trans "AK Plan" %}</a>
</li>
<li class="breadcrumb-item">{% trans "Track" %}: {{ track }}</li>
{% endblock %}
{% block encode %}
[
{% for slot in slots %}
{% if slot.start %}
{'title': '{{ slot.ak }} @ {{ slot.room }}',
'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" }}',
'url': '{{ slot.ak.detail_url }}',
'color': '{{ track.color }}',
'borderColor': '{{ slot.ak.category.color }}',
},
{% endif %}
{% endfor %}
]
{% endblock %}
{% block content %}
<div class="float-end">
<ul class="nav nav-pills">
<li class="nav-item dropdown">
<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">
{% 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>
{% endfor %}
</div>
</li>
</ul>
</div>
<h1>Plan: {{ event }} ({% trans "Track" %}: {{ track }})</h1>
{% if not event.plan_hidden or user.is_staff %}
{% timezone event.timezone %}
<div class="row" style="margin-top:30px;clear:both;">
<div class="col-md-12">
<div id="planCalendar"></div>
</div>
</div>
{% endtimezone %}
{% else %}
<div class="alert alert-warning mt-3">
<p class="mb-0">{% trans "Plan is not visible (yet)." %}</p>
</div>
{% endif %}
{% endblock %}
{% load compress %}
{% load static %}
{% load i18n %}
{% load django_bootstrap5 %}
{% load fontawesome_6 %}
{% load tags_AKModel %}
{% load tags_AKPlan %}
{% 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 #}
{% compress css %}
<link rel="stylesheet" type="text/x-scss" href="{% static 'common/vendor/bootswatch-lumen/theme.scss' %}">
{% 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" %}
{% get_current_language as LANGUAGE_CODE %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var planEl = document.getElementById('planCalendar');
var plan = new FullCalendar.Calendar(planEl, {
timeZone: '{{ event.timezone }}',
headerToolbar: false,
themeSystem: 'bootstrap5',
buttonIcons: {
prev: 'ignore fa-solid fa-angle-left',
next: 'ignore fa-solid fa-angle-right',
},
// Adapt to user selected locale
locale: '{{ LANGUAGE_CODE }}',
slotDuration: '01:00',
initialView: '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"}}',
},
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,
allDaySlot: false,
nowIndicator: true,
now: "{% timestamp_now event.timezone %}",
eventTextColor: '#fff',
eventColor: '#127ba3',
height: '90%',
resourceAreaWidth: '15%',
resourceAreaHeaderContent: '{% trans "Room" %}',
resources: [
{% for room in rooms %}
{
'id': '{{ room.title }}',
'title': '{{ room.title }}'
},
{% endfor %}
],
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);
}
// == 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>
</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>
<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>
</html>
{% load i18n %}
<table class="table table-striped">
{% for akslot in slots %}
<tr>
<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 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>
{% empty %}
{% trans "No AKs" %}
{% endfor %}
</table>
# gradients based on http://bsou.io/posts/color-gradients-with-python
def hex_to_rgb(hex): #pylint: disable=redefined-builtin
"""
Convert hex color to RGB color code
:param hex: hex encoded color
:type hex: str
:return: rgb encoded version of given color
:rtype: list[int]
"""
# Pass 16 to the integer function for change of base
return [int(hex[i:i+2], 16) for i in range(1,6,2)]
def rgb_to_hex(rgb):
"""
Convert rgb color (list) to hex encoding (str)
:param rgb: rgb encoded color
:type rgb: list[int]
:return: hex encoded version of given color
:rtype: str
"""
# Components need to be integers for hex to make sense
rgb = [int(x) for x in rgb]
return "#"+"".join([f"0{v:x}" if v < 16 else f"{v:x}" for v in rgb])
def linear_blend(start_hex, end_hex, position):
"""
Create a linear blend between two colors and return color code on given position of the range from 0 to 1
:param start_hex: hex representation of start color
:type start_hex: str
:param end_hex: hex representation of end color
:type end_hex: str
:param position: position in range from 0 to 1
:type position: float
:return: hex encoded interpolated color
:rtype: str
"""
s = hex_to_rgb(start_hex)
f = hex_to_rgb(end_hex)
blended = [int(s[j] + position * (f[j] - s[j])) for j in range(3)]
return rgb_to_hex(blended)
def darken(start_hex, amount):
"""
Darken the given color by the given amount (sensitivity will be cut in half)
:param start_hex: original color
:type start_hex: str
:param amount: how much to darken (1.0 -> 50% darker)
:type amount: float
:return: darker version of color
:rtype: str
"""
start_rbg = hex_to_rgb(start_hex)
darker = [int(s * (1 - amount * .5)) for s in start_rbg]
return rgb_to_hex(darker)
from datetime import datetime
from django import template
from django.utils.formats import date_format
from AKPlan.templatetags.color_gradients import darken
from AKPlanning import settings
register = template.Library()
@register.filter
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
# Last change long ago? Use default color
if seconds_since_update > settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS:
return akslot.ak.category.color
# Recent change? Calculate gradient blend between red and
recentness = seconds_since_update / settings.PLAN_MAX_HIGHLIGHT_UPDATE_SECONDS
return darken("#b71540", 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 . import views
app_name = "plan"
urlpatterns = [
path(
'<slug:event_slug>/plan/',
include([
path('', views.PlanIndexView.as_view(), name='plan_overview'),
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('track/<int:pk>/', views.PlanTrackView.as_view(), name='plan_track'),
])
),
]
# Create your views here.
import json
from datetime import datetime, timedelta
from django.conf import settings
from django.contrib import messages
from django.db.models import Q
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.views.generic import DetailView, ListView
from django.utils.translation import gettext_lazy as _
from AKModel.metaviews.admin import FilterByEventSlugMixin
from AKModel.models import AKSlot, AKTrack, Room, AKType
class PlanIndexView(FilterByEventSlugMixin, ListView):
"""
Default plan view
Shows two lists of current and upcoming AKs and a graphical full plan below
"""
model = AKSlot
template_name = "AKPlan/plan_index.html"
context_object_name = "akslots"
ordering = "start"
types_filter = None
query_string = ""
def get(self, request, *args, **kwargs):
if 'types' in request.GET:
try:
# Initialize types filter, has to be done here such that it is not reused across requests
self.types_filter = {
"yes": [],
"no": [],
"no_set": set(),
"strict": False,
"empty": False,
}
# If types are given, filter the queryset accordingly
types_raw = request.GET['types'].split(',')
for t in types_raw:
type_slug, type_condition = t.split(':')
if type_condition in ["yes", "no"]:
t = AKType.objects.get(slug=type_slug, event=self.event)
self.types_filter[type_condition].append(t)
if type_condition == "no":
# Store slugs of excluded types in a set for faster lookup
self.types_filter["no_set"].add(t.slug)
else:
raise ValueError(f"Unknown type condition: {type_condition}")
if 'strict' in request.GET:
# If strict is specified and marked as "yes",
# exclude all AKs that have any of the excluded types ("no"),
# even if they have other types that are marked as "yes"
self.types_filter["strict"] = request.GET.get('strict') == 'yes'
if 'empty' in request.GET:
# If empty is specified and marked as "yes", include AKs that have no types at all
self.types_filter["empty"] = request.GET.get('empty') == 'yes'
# Will be used for generating a link to the wall view with the same filter
self.query_string = request.GET.urlencode(safe=",:")
except (ValueError, AKType.DoesNotExist):
# Display an error message if the types parameter is malformed
messages.add_message(request, messages.ERROR, _("Invalid type filter"))
self.types_filter = None
s = super().get(request, *args, **kwargs)
return s
def get_queryset(self):
# Ignore slots not scheduled yet
qs = (super().get_queryset().filter(start__isnull=False).
select_related('event', 'ak', 'room', 'ak__category', 'ak__event'))
# Need to prefetch both event and ak__event
# since django is not aware that the two are always the same
# Apply type filter if necessary
if self.types_filter:
# Either include all AKs with the given types or without any types at all
if self.types_filter["empty"]:
qs = qs.filter(Q(ak__types__in=self.types_filter["yes"]) | Q(ak__types__isnull=True)).distinct()
# Or only those with the given types
else:
qs = qs.filter(ak__types__in=self.types_filter["yes"]).distinct()
# Afterwards, if strict, exclude all AKs that have any of the excluded types,
# even though they were included by the previous filter
if self.types_filter["strict"]:
qs = qs.exclude(ak__types__in=self.types_filter["no"]).distinct()
return qs
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()
buildings = set()
# Get list of current and next slots
for akslot in context["akslots"]:
self._process_slot(akslot)
# Construct a list of all rooms used by these slots on the fly
if akslot.room is not None:
rooms.add(akslot.room)
# Store buildings for hierarchical view
if akslot.room.location != '':
buildings.add(akslot.room.location)
# 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)
if settings.PLAN_SHOW_HIERARCHY:
context["buildings"] = sorted(buildings)
context["tracks"] = self.event.aktrack_set.all()
# Pass query string to template for generating a matching wall link
context["query_string"] = self.query_string
# Generate a list of all types and their current selection state for graphic filtering
types = [{"name": t.name, "slug": t.slug, "state": True} for t in self.event.aktype_set.all()]
if len(types) > 0:
context["type_filtering_active"] = True
if self.types_filter:
for t in types:
if t["slug"] in self.types_filter["no_set"]:
t["state"] = False
# Pass type list as well as filter state for strict filtering and empty types to the template
context["types"] = json.dumps(types)
context["types_filter_strict"] = False
context["types_filter_empty"] = False
if self.types_filter:
context["types_filter_empty"] = self.types_filter["empty"]
context["types_filter_strict"] = self.types_filter["strict"]#
else:
context["type_filtering_active"] = False
return context
def _process_slot(self, akslot):
"""
Function to be called for each slot when looping over the slots
(meant to be overridden in inherited views)
:param akslot: current slot
:type akslot: AKSlot
"""
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"
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):
now = datetime.now().astimezone(self.event.timezone)
# Wall during event: Adjust, show only parts in the future
if self.event.start < now < self.event.end:
# 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
# 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)
def get_context_data(self, *, object_list=None, **kwargs):
# Find the earliest hour AKs start and end (handle 00:00 as 24:00)
self.earliest_start_hour = 23
self.latest_end_hour = 1
context = super().get_context_data(object_list=object_list, **kwargs)
context["start"] = self.start
context["end"] = self.event.end
context["earliest_start_hour"] = self.earliest_start_hour
context["latest_end_hour"] = self.latest_end_hour
return context
def _process_slot(self, akslot):
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)
class PlanRoomView(FilterByEventSlugMixin, DetailView):
"""
Plan view for a single room
"""
template_name = "AKPlan/plan_room.html"
model = 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):
"""
Plan view for a single track
"""
template_name = "AKPlan/plan_track.html"
model = AKTrack
context_object_name = "track"
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
# Restrict AKSlot list to given track
# while joining AK, room and category information to reduce the amount of necessary SQL queries
context["slots"] = AKSlot.objects. \
filter(event=self.event, ak__track=context['track']). \
select_related('ak', 'room', 'ak__category')
return context
# 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: 2023-08-16 16:30+0200\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"
#: AKPlanning/settings.py:148
msgid "German"
msgstr "Deutsch"
#: AKPlanning/settings.py:149
msgid "English"
msgstr "Englisch"
......@@ -10,9 +10,13 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import decimal
import os
from django.utils.translation import gettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
from split_settings.tools import optional, include
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
......@@ -34,17 +38,31 @@ INSTALLED_APPS = [
'AKSubmission.apps.AksubmissionConfig',
'AKScheduling.apps.AkschedulingConfig',
'AKPlan.apps.AkplanConfig',
'django.contrib.admin',
'AKOnline.apps.AkonlineConfig',
'AKPreference.apps.AkpreferenceConfig',
'AKSolverInterface.apps.AksolverinterfaceConfig',
'AKModel.apps.AKAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap4',
'fontawesome',
'debug_toolbar',
'django_bootstrap5',
'fontawesomefree',
'fontawesome_6',
'timezone_field',
'rest_framework',
'simple_history',
'registration',
'django_tex',
'compressor',
'docs',
]
MIDDLEWARE = [
'django.middleware.gzip.GZipMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
......@@ -52,7 +70,9 @@ MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'csp.middleware.CSPMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
]
ROOT_URLCONF = 'AKPlanning.urls'
......@@ -73,10 +93,20 @@ TEMPLATES = [
],
},
},
{
'NAME': 'tex',
'BACKEND': 'django_tex.engine.TeXEngine',
'APP_DIRS': True,
'OPTIONS': {
'environment': 'AKModel.environment.improved_tex_environment',
},
},
]
WSGI_APPLICATION = 'AKPlanning.wsgi.application'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
......@@ -108,16 +138,24 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en-US'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('de', _('German')),
('en', _('English')),
]
INTERNAL_IPS = ['127.0.0.1', '::1']
LATEX_INTERPRETER = 'lualatex'
LATEX_RUN_COUNT = 2
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
......@@ -127,13 +165,90 @@ STATICFILES_DIRS = (
'static_common',
)
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
)
# Settings for Bootstrap
BOOTSTRAP4 = {
# Use custom CSS
"css_url": {
"href": STATIC_URL + "common/css/bootstrap.css",
BOOTSTRAP5 = {
"javascript_url": {
"url": STATIC_URL + "common/vendor/bootstrap/bootstrap-5.0.2.bundle.min.js",
},
}
# Settings for FontAwesome
FONTAWESOME_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_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?
WISHES_AS_CATEGORY = True
FOOTER_INFO = {
"repo_url": "https://gitlab.fachschaften.org/kif/akplanning",
"impress_text": "",
"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_WALL_HOURS_RETROSPECT = 3
# Should the plan use a hierarchy of buildings and rooms?
PLAN_SHOW_HIERARCHY = True
# For which time (in seconds) should changes of akslots be highlighted in plan?
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
# In the export to the solver we need to calculate the integer number
# of discrete time slots covered by an AK. This is done by rounding
# the 'exact' number up. To avoid 'overshooting' by 1
# due to FLOP inaccuracies, we subtract this small epsilon before rounding.
EXPORT_CEIL_OFFSET_EPS = decimal.Decimal(1e-4)
# Registration/login behavior
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"))
# 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'
......@@ -4,9 +4,10 @@ First, it imports all default settings, then overrides respective ones.
Secrets are stored in and imported from an additional file, not set under version control.
"""
from AKPlanning.settings import *
import AKPlanning.settings_secrets as secrets
# noinspection PyUnresolvedReferences
from AKPlanning.settings import *
### SECURITY ###
......@@ -16,4 +17,26 @@ ALLOWED_HOSTS = secrets.HOSTS
SECRET_KEY = secrets.SECRET_KEY
# TODO: DB, chaching, CSRF etc.
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
### DATABASE ###
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': getattr(secrets, "DB_HOST", "localhost"),
'NAME': secrets.DB_NAME,
'USER': secrets.DB_USER,
'PASSWORD': secrets.DB_PASSWORD,
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"
}
}
}
### EMAILS ###
SEND_MAILS = True
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# TODO: caching
SECRET_KEY = ''
HOSTS = []
DB_NAME = ''
DB_USER = ''
DB_PASSWORD = ''
# Optional, if not set, localhost is assumed
# DB_HOST = ''