diff --git a/.gitignore b/.gitignore index a7a328b769adb62645247b5ceef62be67207c732..30fb29663a4f448863b43030a65bccef39cb948f 100755 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ secure* class.pdf class.tex .idea/ -media/ \ No newline at end of file +media/ diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml index 650041871f1e354d1ac794314c705950dbc06f00..a6d51bc655404fbdaca80af14dadb95e3f3d52b1 100755 --- a/.idea/dataSources.local.xml +++ b/.idea/dataSources.local.xml @@ -2,30 +2,15 @@ <project version="4"> <component name="dataSourceStorageLocal"> <data-source name="Django default" uuid="ef6730d6-e849-4772-acac-62469acab4d2"> - <database-info product="MySQL" version="5.7.20-0ubuntu0.16.04.1" jdbc-version="4.0" driver-name="MySQL Connector Java" driver-version="mysql-connector-java-5.1.44 ( Revision: b3cda4f864902ffdde495b9df93937c3e20009be )" dbms="MYSQL" exact-version="5.7.20"> - <extra-name-characters>#@</extra-name-characters> - <identifier-quote-string>`</identifier-quote-string> - </database-info> - <case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" /> - <user-name>www-data</user-name> + <database-info product="" version="" jdbc-version="" driver-name="" driver-version="" /> <introspection-schemas>*:schoolapps</introspection-schemas> </data-source> <data-source name="Django default2" uuid="ea4cff78-5949-410f-aa64-d6daa5fb293d"> - <database-info product="MySQL" version="5.7.21-0ubuntu0.16.04.1" jdbc-version="4.0" driver-name="MySQL Connector Java" driver-version="mysql-connector-java-5.1.44 ( Revision: b3cda4f864902ffdde495b9df93937c3e20009be )" dbms="MYSQL" exact-version="5.7.21"> - <extra-name-characters>#@</extra-name-characters> - <identifier-quote-string>`</identifier-quote-string> - </database-info> - <case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" /> - <user-name>www-data</user-name> + <database-info product="" version="" jdbc-version="" driver-name="" driver-version="" /> <introspection-schemas>*:schoolapps</introspection-schemas> </data-source> <data-source name="Django untis" uuid="ae145b31-953d-4d55-ad07-b49b3287f618"> - <database-info product="MySQL" version="5.7.21-0ubuntu0.16.04.1" jdbc-version="4.0" driver-name="MySQL Connector Java" driver-version="mysql-connector-java-5.1.44 ( Revision: b3cda4f864902ffdde495b9df93937c3e20009be )" dbms="MYSQL" exact-version="5.7.21"> - <extra-name-characters>#@</extra-name-characters> - <identifier-quote-string>`</identifier-quote-string> - </database-info> - <case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" /> - <user-name>www-data</user-name> + <database-info product="" version="" jdbc-version="" driver-name="" driver-version="" /> <introspection-schemas>*:untis</introspection-schemas> </data-source> </component> diff --git a/.idea/misc.xml b/.idea/misc.xml index bfe7e158d899bcf1568343297e964d1a399d335a..5143e4296e9af6591baf4e6271f513d1aa402567 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5 (school-apps)" project-jdk-type="Python SDK" /> + <component name="JavaScriptSettings"> + <option name="languageLevel" value="ES6" /> + </component> + <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (school-apps)" project-jdk-type="Python SDK" /> <component name="PyCharmProfessionalAdvertiser"> <option name="shown" value="true" /> </component> diff --git a/.idea/modules.xml b/.idea/modules.xml index dfea16ff8e1e7177bf23ca639995803556600378..dea7eb1556ba6c1847d4914a9ab84f3e30aa2905 100755 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ <component name="ProjectModuleManager"> <modules> <module fileurl="file://$PROJECT_DIR$/.idea/school-apps.iml" filepath="$PROJECT_DIR$/.idea/school-apps.iml" /> + <module fileurl="file://$PROJECT_DIR$/.idea/school-apps.iml" filepath="$PROJECT_DIR$/.idea/school-apps.iml" /> </modules> </component> </project> \ No newline at end of file diff --git a/.idea/school-apps.iml b/.idea/school-apps.iml index f2e5dbb75cd3c381b4a56daf534b51941dc6cb74..2cf9edf5ce01568cbcf2fccc68ae2d9d8aadc199 100755 --- a/.idea/school-apps.iml +++ b/.idea/school-apps.iml @@ -17,7 +17,7 @@ <sourceFolder url="file://$MODULE_DIR$/schoolapps" isTestSource="false" /> <excludeFolder url="file://$MODULE_DIR$/venv" /> </content> - <orderEntry type="jdk" jdkName="Python 3.5 (school-apps)" jdkType="Python SDK" /> + <orderEntry type="jdk" jdkName="Python 3.6 (school-apps)" jdkType="Python SDK" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="library" name="jquery-3.2.1" level="application" /> <orderEntry type="module" module-name="bwinf-36-2" /> diff --git a/schoolapps/schoolapps/settings.py b/schoolapps/schoolapps/settings.py index 75e7319d6ffec0cdfe3a495e6d464f0f97e91c98..20a26dc8c8180ab524d6a3616f7049774fbe61c0 100755 --- a/schoolapps/schoolapps/settings.py +++ b/schoolapps/schoolapps/settings.py @@ -163,6 +163,7 @@ TIMETABLE_WIDTH = 5 TIMETABLE_HEIGHT = 10 LESSONS = [('8:00', '1.'), ('8:45', '2.'), ('9:45', '3.'), ('10:35', '4.'), ('11:35', '5.'), ('12:25', '6.'), ('13:15', '7.'), ('14:05', '8.'), ('14:50', '9.')] +WEEK_DAYS = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag"] ######## # LDAP # diff --git a/schoolapps/static/common/helper.js b/schoolapps/static/common/helper.js index c803c0a883c717b833060628326111e5bef26cb2..fb71146384e3b77c1d341b2004cef14c5d0eba48 100644 --- a/schoolapps/static/common/helper.js +++ b/schoolapps/static/common/helper.js @@ -90,6 +90,9 @@ $(document).ready(function () { // Initialize tooltip [MAT] $('.tooltipped').tooltip(); + // Initialize select [MAT] + $('select').formSelect(); + // Initalize print button $("#print").click(function () { window.print(); diff --git a/schoolapps/static/common/style.css b/schoolapps/static/common/style.css index 0cf34295b27980a09779490ae1fc0ddbfa608257..5187375e1c2c1e13697e44c26a1d34b7367b89c2 100755 --- a/schoolapps/static/common/style.css +++ b/schoolapps/static/common/style.css @@ -9,11 +9,11 @@ body { } .primary-color { - background-color: #da1f3d; + background-color: #da1f3d !important; } .primary-color-text { - color: #da1f3d; + color: #da1f3d !important; } /**********/ @@ -131,6 +131,7 @@ span.badge.new::after { } .lesson-card .card-content div { + padding: 5px; height: 100%; width: 100%; display: flex; @@ -148,6 +149,15 @@ table.substitutions td, table.substitutions th { padding: 10px 5px; } +.lesson-with-sub { + border: 3px solid red; + border-radius: 3px; +} + +.lesson-with-sub .badge { + margin: 0; +} + /*.timetable-time {*/ /*margin-right: 20px;*/ /*}*/ @@ -179,6 +189,12 @@ table.substitutions td, table.substitutions th { width: 30%; } +.no-margin { + margin: 0; +} + +/* Table*/ + table.striped > tbody > tr:nth-child(odd) { background-color: rgba(208, 208, 208, 0.5); } diff --git a/schoolapps/static/materialize b/schoolapps/static/materialize index 5714e3b8821ba2cc6831064e7cb901e743e19b58..80e8ed370487aaf1e2185b028f7deda40da94eb9 160000 --- a/schoolapps/static/materialize +++ b/schoolapps/static/materialize @@ -1 +1 @@ -Subproject commit 5714e3b8821ba2cc6831064e7cb901e743e19b58 +Subproject commit 80e8ed370487aaf1e2185b028f7deda40da94eb9 diff --git a/schoolapps/templates/partials/header.html b/schoolapps/templates/partials/header.html index 487942b27f2ec59963f30ff437dd1e6d9ce27aae..6a5fa1f6b0c01428bab173f288c357d19026e027 100755 --- a/schoolapps/templates/partials/header.html +++ b/schoolapps/templates/partials/header.html @@ -14,11 +14,10 @@ <!-- CSS --> <!---------> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> - <link rel="stylesheet" type="text/css" media="screen" - href="{% static 'materialize/dist/css/materialize.min.css' %}"> + <link rel="stylesheet" type="text/css" href="{% static 'materialize/dist/css/materialize.min.css' %}"> <!-- django-material --> - {% include 'material/includes/material_css.html' %} + {# {% include 'material/includes/material_css.html' %}#} <link rel="stylesheet" type="text/css" href="{% static 'common/style.css' %}"> <!----------------> @@ -28,7 +27,7 @@ <script src="{% static 'materialize/dist/js/materialize.min.js' %}"></script> <script src="{% static 'common/helper.js' %}"></script> <!-- django-material --> - {% include 'material/includes/material_js.html' %} + {# {% include 'material/includes/material_js.html' %}#} </head> <body> @@ -103,9 +102,14 @@ <li> <a class="subheader grey lighten-3">Stundenplan</a> </li> + <li> + <a href="{% url 'timetable_my_plan' %}"> + <i class="material-icons">person</i> Mein Plan + </a> + </li> <li> <a href="{% url 'timetable_admin_all' %}"> - <i class="material-icons">grid_on</i> Übersicht + <i class="material-icons">grid_on</i> Alle Pläne </a> </li> <li> diff --git a/schoolapps/timetable/templates/timetable/datepicker.html b/schoolapps/timetable/templates/timetable/datepicker.html new file mode 100644 index 0000000000000000000000000000000000000000..ace7c502c7559e30299e7405e65c07f306b57635 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/datepicker.html @@ -0,0 +1,62 @@ +<script type="text/javascript"> + function updateDatepicker() { + $("#date").val(formatDate(activeDate)); + } + + function update() { + console.log("Render new."); + + updateDatepicker(); + } + + function loadNew() { + window.location.href = dest + formatDateForDjango(activeDate); + } + + function onDateBeforeClick() { + activeDate.setDate(activeDate.getDate() - 1); + update(); + loadNew(); + } + + function onDateNextClick() { + activeDate.setDate(activeDate.getDate() + 1); + update(); + loadNew(); + } + + function onDateChanged() { + var str = $("#date").val(); + var split = str.split(".") + activeDate = new Date(split[2], split[1] - 1, split[0]); + update(); + loadNew(); + } + + var activeDate = new Date({{ date_js }}); + + $(document).ready(function () { + $("#date-before").click(onDateBeforeClick); + $("#date-next").click(onDateNextClick); + $("#date").change(onDateChanged); + + update(); + }) +</script> + + +<div class="col s2"> + <a class="waves-effect waves-teal btn-flat btn-flat-medium right" id="date-before"> + <i class="material-icons center">navigate_before</i> + </a> + +</div> +<div class="col s8"> + <input type="text" class="datepicker center-align" id="date"> +</div> +<div class="col s2"> + <a class="waves-effect waves-teal btn-flat btn-flat-medium left" id="date-next"> + <i class="material-icons center">navigate_next</i> + </a> +</div> + diff --git a/schoolapps/timetable/templates/timetable/lesson.html b/schoolapps/timetable/templates/timetable/lesson.html new file mode 100644 index 0000000000000000000000000000000000000000..9c974cbe4c1bf28f1bbd29578b8abe21163b9508 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/lesson.html @@ -0,0 +1,88 @@ +<div class="card lesson-card"> + <div class="card-content"> + + {# Every element of the lesson #} + {% for element_container in col.elements %} + <div style=" + {# Display background color only if no badge exists and it is not the old room #} + {% if not element_container.substitution.table.badge %} + {% if not element_container.is_old or type != 1 %} + background-color: {{ element_container.element.subject.hex_color }}; + {% endif %} + {% endif %}" + + {# Add CSS class for sub when it's a sub #} + class="{% if element_container.substitution %}lesson-with-sub{% endif %}"> + <p> + {% if element_container.substitution %} + {# SUBSTITUTION #} + + {% if type == 1 and element_container.is_old %} + {# When it's the old room, let it empty #} + + {% elif element_container.substitution.table.badge %} + {# When a badge (cancellation, etc.) exists, then display it #} + <span class="badge new green darken-2">{{ element_container.substitution.table.badge }}</span> + + + {% else %} + {# Display sub #} + + {# Teacher or room > display classes #} + {% if type == 0 or type == 1 %} + {{ element_container.substitution.table.classes }} + {% endif %} + + {# Display teacher with tooltip #} + <span class="tooltipped" data-position="bottom" + data-tooltip="{{ element_container.substitution.table.teacher_full|safe }}">{{ element_container.substitution.table.teacher|safe }}</span> + + {# Display subject #} + {{ element_container.substitution.table.subject|safe }} + + {# Teacher or class > display room #} + {% if type == 0 or type == 2 %} + <span class="tooltipped" data-position="bottom" + data-tooltip="{{ element_container.substitution.table.room_full|safe }}">{{ element_container.substitution.table.room|safe }}</span> + {% endif %} + {% endif %}<br> + + {# When it isn't a room or the old plan, then display the extra text (e. g. work orders)#} + {% if not type == 1 or not element_container.is_old %} + <small> + <em>{{ element_container.substitution.table.text|default:"" }}</em> + </small> + {% endif %} + + + {% else %} + {# Normal plan #} + + {# Teacher or room > Display classes #} + {% if type == 0 or type == 1 %} + {% for class in element_container.element.classes %} + {{ class.name }} + {% endfor %} + {% endif %} + + {# Class or room > Display teacher #} + {% if type == 2 or type == 1 %} + <span data-position="bottom" class="tooltipped" + data-tooltip="{{ element_container.element.teacher }}">{{ element_container.element.teacher.shortcode }}</span> + {% endif %} + + {# Display subject #} + <strong>{{ element_container.element.subject.shortcode }}</strong> + + {# Teacher or class > Display room #} + {% if type == 0 or type == 2 %} + <span class="tooltipped" data-position="bottom" + data-tooltip="{{ element_container.room.name }}">{{ element_container.room.shortcode }}</span> + {% endif %} + {% endif %} + </p> + </div> + + {% endfor %} + </div> +</div> diff --git a/schoolapps/timetable/templates/timetable/myplan.html b/schoolapps/timetable/templates/timetable/myplan.html new file mode 100644 index 0000000000000000000000000000000000000000..4e0ef9400ef303402c89337943f9b9d695666b52 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/myplan.html @@ -0,0 +1,75 @@ +{% include 'partials/header.html' %} + + +<main> + <h4>Mein Plan</h4> + <h6>für <em>{{ el }}</em></h6> + + <script type="text/javascript"> + var dest = "/timetable/my/"; + </script> + <div class="row"> + <div class="col s12 m6"> + {% include "timetable/datepicker.html" %} + </div> + </div> + {# <h5>{{ date|date:"l, j. F Y" }}</h5>#} + <div class="row"> + <div class="timetable-plan col s12 m12 l6"> + + {# Week days #} + <div class="row"> + <div class="col s6"> + + </div> + {% for week_daye in week_days %} + {% if forloop.counter0 == week_day %} + <div class="col s6"> + <div class="card timetable-title-card"> + <div class="card-content"> + <span class="card-title"> + {{ week_daye }} + </span> + </div> + </div> + </div> + {% endif %} + {% endfor %} + </div> + {# Lessons #} + {% for row, time in plan %} + <div class="row"> + <div class="col s6"> + <div class="card timetable-title-card"> + <div class="card-content"> + + {# Lesson number #} + <span class="card-title left"> + {{ time.number_format }} + </span> + + {# Time dimension of lesson #} + <div class="right timetable-time grey-text text-darken-2"> + <span>{{ time.start|date:"H:i" }}</span><br> + <span>{{ time.end|date:"H:i" }}</span> + </div> + </div> + </div> + + </div> + {% for col in row %} + {% if forloop.counter0 == week_day %} + <div class="col s6"> + {# A lesson #} + {% include "timetable/lesson.html" %} + </div> + {% endif %} + {% endfor %} + </div> + {% endfor %} + + </div> + </div> +</main> + +{% include 'partials/footer.html' %} diff --git a/schoolapps/timetable/templates/timetable/plan.html b/schoolapps/timetable/templates/timetable/plan.html index 77af7c862f832a5706d8c00615182b9bd19c8d25..af57930dd5127c7464a9ac89dadffb526dbcc0b3 100755 --- a/schoolapps/timetable/templates/timetable/plan.html +++ b/schoolapps/timetable/templates/timetable/plan.html @@ -1,78 +1,130 @@ {% include 'partials/header.html' %} +<script type="text/javascript"> + {% if smart %} + var week = {{ selected_week }}; + function goToCalendarWeek(cw) { + window.location.href = "{% url "timetable_smart_plan" raw_type id "smart" %}/{{ selected_year }}/" + cw; + } + function onCalendarWeekChanged() { + goToCalendarWeek($("#calendar-week").val(), {{ selected_year }}); + } + function weekBefore() { + if (week > 1) { + goToCalendarWeek(week - 1) + } + } + + function weekNext() { + if (week < 52) { + goToCalendarWeek(week + 1); + } + } + + $(document).ready(function () { + $("#calendar-week").change(onCalendarWeekChanged); + $("#week-before").click(weekBefore); + $("#week-next").click(weekNext); + }); + {% endif %} +</script> <main> - <div class="row"> + <div class="row no-margin"> <div class="col s12 m6 l8 xl9"> <h3> Stundenplan <i>{{ el }}</i> </h3> + + </div> - <div class="col s12 m6 l4 xl3 right align-right"> + <div class=" col s12 m6 l4 xl3 right align-right no-print"> <a class="waves-effect waves-teal btn-flat btn-flat-medium right" id="print"> <i class="material-icons center">print</i> </a> </div> </div> - <div class="timetable-plan"> - <div class="row"> - <div class="col s2"> + <div class="row"> + {% if smart %} + {# Show if smart #} + + {# Toggle button to regular and smart plan badge #} + <div class="col s12 m6"> + <p class="left" style="margin: 0;"><span + class="badge new primary-color left-align">SMART PLAN</span> + </p> + <a class="waves-effect waves-light btn-flat no-print" href="{% url "timetable_plan" raw_type id %}"> + <i class="material-icons left">slideshow</i> + Regelplan anzeigen + </a> </div> - <div class="col s2"> - <div class="card timetable-title-card"> - <div class="card-content"> - <span class="card-title"> - Montag - </span> - </div> + + + {# Week select #} + <div class="col s12 m6 right "> + <div class="col s3 no-print"> + <a class="waves-effect waves-teal btn-flat btn-flat-medium right" id="week-before"> + <i class="material-icons center">navigate_before</i> + </a> </div> - </div> - <div class="col s2"> - <div class="card timetable-title-card"> - <div class="card-content"> - <span class="card-title"> - Dienstag - </span> - </div> + <div class="input-field col s6 no-margin"> + <select id="calendar-week"> + {% for week in weeks %} + <option value="{{ week.calendar_week }}" {% if week.calendar_week == selected_week %} + selected {% endif %}> KW {{ week.calendar_week }} ({{ week.first_day|date:"j.n.Y" }}–{{ week.last_day|date:"j.n.Y" }}) + </option> + {% endfor %} + </select> </div> - </div> - <div class="col s2"> - <div class="card timetable-title-card"> - <div class="card-content"> - <span class="card-title"> - Mittwoch - </span> - </div> + <div class="col s3 no-print"> + <a class="waves-effect waves-teal btn-flat btn-flat-medium left" id="week-next"> + <i class="material-icons center">navigate_next</i> + </a> </div> </div> + + {% else %} + {# Show if regular #} + <a class="waves-effect waves-light btn-flat no-print" + href="{% url "timetable_smart_plan" raw_type id "smart" %}"> + <i class="material-icons left">slideshow</i> + SMART PLAN ANZEIGEN + </a> + {% endif %} + </div> + <div class="timetable-plan"> + + {# Week days #} + <div class="row"> <div class="col s2"> - <div class="card timetable-title-card"> - <div class="card-content"> - <span class="card-title"> - Donnerstag - </span> - </div> - </div> + </div> - <div class="col s2"> - <div class="card timetable-title-card"> - <div class="card-content"> + {% for week_day in week_days %} + <div class="col s2"> + <div class="card timetable-title-card"> + <div class="card-content"> <span class="card-title"> - Freitag + {{ week_day }} </span> + </div> </div> </div> - </div> - + {% endfor %} </div> + + {# Lessons #} {% for row, time in plan %} <div class="row"> <div class="col s2"> <div class="card timetable-title-card"> <div class="card-content"> - <span class="card-title left"> - {{ time.number_format }} - </span> + + {# Lesson number #} + <span class="card-title left"> + {{ time.number_format }} + </span> + + {# Time dimension of lesson #} <div class="right timetable-time grey-text text-darken-2"> <span>{{ time.start|date:"H:i" }}</span><br> <span>{{ time.end|date:"H:i" }}</span> @@ -82,33 +134,8 @@ </div> {% for col in row %} - <div class="col s2"> - <div class="card lesson-card"> - <div class="card-content"> - {% for element_container in col.elements %} - <div style="background-color: {{ element_container.element.subject.hex_color }};"> - <p> - {% if type == 0 or type == 1 %} - {% for class in element_container.element.classes %} - {{ class.name }} - {% endfor %} - {% endif %} - {% if type == 2 or type == 1 %} - <span data-position="bottom" class="tooltipped" - data-tooltip="{{ element_container.element.teacher }}">{{ element_container.element.teacher.shortcode }}</span> - {% endif %} - <strong>{{ element_container.element.subject.shortcode }}</strong> - {% if type == 0 or type == 2 %} - <span class="tooltipped" data-position="bottom" - data-tooltip="{{ element_container.room.name }}">{{ element_container.room.shortcode }}</span> - {% endif %} - </p> - </div> - - {% endfor %} - </div> - </div> - </div> + {# A lesson #} + {% include "timetable/lesson.html" %} {% endfor %} </div> {% endfor %} diff --git a/schoolapps/timetable/templates/timetable/substitution.html b/schoolapps/timetable/templates/timetable/substitution.html index c33db779c4b70ae400df8c942607630b75470e76..3dca3e13839f358554228f530f1a4f50c4f12f13 100755 --- a/schoolapps/timetable/templates/timetable/substitution.html +++ b/schoolapps/timetable/templates/timetable/substitution.html @@ -1,49 +1,8 @@ {% include 'partials/header.html' %} -<script type="text/javascript"> - function updateDatepicker() { - $("#date").val(formatDate(activeDate)); - } - - function update() { - console.log("Render new."); - - updateDatepicker(); - } - - function loadNew() { - window.location.href = "/timetable/substitutions/" + formatDateForDjango(activeDate); - } - - function onDateBeforeClick() { - activeDate.setDate(activeDate.getDate() - 1); - update(); - loadNew(); - } - - function onDateNextClick() { - activeDate.setDate(activeDate.getDate() + 1); - update(); - loadNew(); - } - function onDateChanged() { - var str = $("#date").val(); - var split = str.split(".") - activeDate = new Date(split[2], split[1] - 1, split[0]); - update(); - loadNew(); - } - - var activeDate = new Date({{ date_js }}); - - $(document).ready(function () { - $("#date-before").click(onDateBeforeClick); - $("#date-next").click(onDateNextClick); - $("#date").change(onDateChanged); - - update(); - }) +<script type="text/javascript"> + var dest = "/timetable/substitutions/"; </script> <main> @@ -51,20 +10,7 @@ <div class="row no-print"> <div class="col s12 m6 l4 xl3"> - <div class="col s2"> - <a class="waves-effect waves-teal btn-flat btn-flat-medium right" id="date-before"> - <i class="material-icons center">navigate_before</i> - </a> - - </div> - <div class="col s8"> - <input type="text" class="datepicker center-align" id="date"> - </div> - <div class="col s2"> - <a class="waves-effect waves-teal btn-flat btn-flat-medium left" id="date-next"> - <i class="material-icons center">navigate_next</i> - </a> - </div> + {% include "timetable/datepicker.html" %} </div> <div class="col l4 xl6"> </div> diff --git a/schoolapps/timetable/urls.py b/schoolapps/timetable/urls.py index 9e7c7dcf253b593405fa9281af470d0bb95c68be..8c6ad77e7845bd98cb0d0e203ff23dc2b2a14c7d 100755 --- a/schoolapps/timetable/urls.py +++ b/schoolapps/timetable/urls.py @@ -3,8 +3,13 @@ from . import views urlpatterns = [ path('', views.all, name='timetable_admin_all'), + path('my', views.my_plan, name='timetable_my_plan'), + path('my/<int:year>/<int:month>/<int:day>/', views.my_plan, name='timetable_my_plan'), path('quick/', views.quicklaunch, name='timetable_quicklaunch'), - path('<str:plan_type>/<int:plan_id>/', views.plan, name='timetable_plan'), + path('<str:plan_type>/<int:plan_id>', views.plan, name='timetable_plan'), + path('<str:plan_type>/<int:plan_id>/<str:smart>', views.plan, name='timetable_smart_plan'), + path('<str:plan_type>/<int:plan_id>/<str:smart>/<int:year>/<int:calendar_week>', views.plan, + name='timetable_smart_plan_week'), path('substitutions/', views.substitutions, name='timetable_substitutions'), path('substitutions/<int:year>/<int:month>/<int:day>/', views.substitutions, name='timetable_substitutions_date'), path('class.pdf', views.sub_pdf, name="timetable_substitutions_pdf") diff --git a/schoolapps/timetable/views.py b/schoolapps/timetable/views.py index 8a9a586cf3a61830ec6d5ec8b3002d7a70628883..91b5aa970c679d9daaf5249af2acddca06e78e51 100755 --- a/schoolapps/timetable/views.py +++ b/schoolapps/timetable/views.py @@ -3,19 +3,16 @@ import os from django.contrib.auth.decorators import login_required, permission_required from django.http import Http404, FileResponse -from django.shortcuts import render +from django.shortcuts import render, redirect from django.utils import timezone +from schoolapps.settings import WEEK_DAYS from timetable.pdf import generate_class_tex, generate_pdf -from schoolapps.settings import LESSONS -from untisconnect.parse import * -from untisconnect.sub import get_substitutions_by_date, date_to_untis_date, untis_date_to_date, generate_sub_table - -try: - from schoolapps.untisconnect.api import * -except Exception: - pass +from untisconnect.plan import get_plan, TYPE_TEACHER, TYPE_CLASS, TYPE_ROOM, parse_lesson_times +from untisconnect.sub import get_substitutions_by_date, generate_sub_table +from untisconnect.api import * +from userinformation import UserInformation def get_all_context(): @@ -46,9 +43,50 @@ def quicklaunch(request): return render(request, 'timetable/quicklaunch.html', context) +def get_calendar_weeks(year=timezone.datetime.now().year): + weeks = [] + + # Get first day of year > first calendar week + first_day_of_year = timezone.datetime(year=year, month=1, day=1) + if first_day_of_year.isoweekday() != 1: + days_to_next_monday = 1 - first_day_of_year.isoweekday() + first_day_of_year += datetime.timedelta(days=days_to_next_monday) + + # Go for all weeks in year and create week dict + first_day_of_week = first_day_of_year + for i in range(52): + calendar_week = i + 1 + last_day_of_week = first_day_of_week + datetime.timedelta(days=4) + weeks.append({ + "calendar_week": calendar_week, + "first_day": first_day_of_week, + "last_day": last_day_of_week + }) + first_day_of_week += datetime.timedelta(weeks=1) + + return weeks + + +def get_calendar_week(calendar_week, year=timezone.datetime.now().year): + weeks = get_calendar_weeks(year=year) + for week in weeks: + if week["calendar_week"] == calendar_week: + return week + return None + + @login_required @permission_required("timetable.show_plan") -def plan(request, plan_type, plan_id): +def plan(request, plan_type, plan_id, smart="", year=timezone.datetime.now().year, + calendar_week=timezone.datetime.now().isocalendar()[1]): + if smart == "smart": + smart = True + else: + smart = False + + monday_of_week = get_calendar_week(calendar_week, year)["first_day"] + # print(monday_of_week) + if plan_type == 'teacher': _type = TYPE_TEACHER el = get_teacher_by_id(plan_id) @@ -59,21 +97,75 @@ def plan(request, plan_type, plan_id): _type = TYPE_ROOM el = get_room_by_id(plan_id) else: - raise Http404('Page not found.') + raise Http404('Plan not found.') - plan = get_plan(_type, plan_id) - print(parse_lesson_times()) + plan = get_plan(_type, plan_id, smart=smart, monday_of_week=monday_of_week) + # print(parse_lesson_times()) context = { + "smart": smart, "type": _type, + "raw_type": plan_type, + "id": plan_id, "plan": plan, "el": el, - "times": parse_lesson_times() + "times": parse_lesson_times(), + "weeks": get_calendar_weeks(year=year), + "selected_week": calendar_week, + "selected_year": year, + "week_days": WEEK_DAYS } return render(request, 'timetable/plan.html', context) +@login_required +@permission_required("timetable.show_plan") +def my_plan(request, year=None, day=None, month=None): + date = timezone.datetime.now() + if year is not None and day is not None and month is not None: + date = timezone.datetime(year=year, month=month, day=day) + + calendar_week = date.isocalendar()[1] + monday_of_week = get_calendar_week(calendar_week, date.year)["first_day"] + + _type = UserInformation.user_type(request.user) + + if _type == UserInformation.TEACHER: + _type = TYPE_TEACHER + shortcode = request.user.username + el = get_teacher_by_shortcode(shortcode) + plan_id = el.id + # print(el) + elif _type == UserInformation.STUDENT: + _type = TYPE_CLASS + _name = UserInformation.user_classes(request.user)[0] + # print(_name) + el = get_class_by_name(_name) + plan_id = el.id + else: + redirect("timetable_admin_all") + # print(monday_of_week) + + plan = get_plan(_type, plan_id, smart=True, monday_of_week=monday_of_week) + # print(parse_lesson_times()) + + context = { + "type": _type, + "id": plan_id, + "plan": plan, + "el": el, + "times": parse_lesson_times(), + "week_day": date.isoweekday() - 1, + "week_days": WEEK_DAYS, + "date": date, + "date_js": int(date.timestamp()) * 1000 + } + # print(context["week_day"]) + + return render(request, 'timetable/myplan.html', context) + + def get_next_weekday(date): """Get the next weekday by a datetime object""" diff --git a/schoolapps/untisconnect/api.py b/schoolapps/untisconnect/api.py index 598b00ffcad34eba66707d1acd1c193d114ea13c..96cd589717bc4f40a9e709bcda69de6f99154128 100755 --- a/schoolapps/untisconnect/api.py +++ b/schoolapps/untisconnect/api.py @@ -41,7 +41,7 @@ def row_by_row(db_ref, obj, filter_term=True): def one_by_id(db_ref, obj): # print(db_ref) - if db_ref != None: + if db_ref is not None: o = obj() o.create(db_ref) return o @@ -85,6 +85,12 @@ def get_teacher_by_id(id): return one_by_id(teacher, Teacher) +def get_teacher_by_shortcode(shortcode): + shortcode = shortcode.upper() + teacher = run_one(models.Teacher.objects).get(name__icontains=shortcode) + return one_by_id(teacher, Teacher) + + ######### # CLASS # ######### @@ -125,6 +131,12 @@ def get_class_by_id(id): return one_by_id(_class, Class) +def get_class_by_name(name): + name = name[0].upper() + name[1:] + _class = run_one(models.Class.objects).filter(name__icontains=name).all()[0] + return one_by_id(_class, Class) + + ######## # ROOM # ######## @@ -185,7 +197,7 @@ def get_all_corridors(): def get_corridor_by_id(id): - print(id) + # print(id) corridor = run_one(models.Corridor.objects, filter_term=False).get(corridor_id=id) return one_by_id(corridor, Corridor) diff --git a/schoolapps/untisconnect/parse.py b/schoolapps/untisconnect/parse.py index 1e288895e52ef2c366fb33cdb19006929ddc64bc..7fe1e9adfa8d5798a34a51d2266523267ac48301 100755 --- a/schoolapps/untisconnect/parse.py +++ b/schoolapps/untisconnect/parse.py @@ -1,9 +1,3 @@ -from django.conf import settings -from django.utils import timezone - -from schoolapps.settings import LESSONS - - class Lesson(object): def __init__(self): self.filled = False @@ -26,6 +20,7 @@ class Lesson(object): # Split data (,) lesson_id = raw_lesson.lesson_id + self.id = lesson_id raw_lesson_data = raw_lesson.lessonelement1.split(",") raw_time_data = raw_lesson.lesson_tt.split(",") @@ -80,9 +75,9 @@ class Lesson(object): # r = drive["rooms"][room_id] # rooms.append(r) rooms = [] - for room in rooms: - print(room) - print("--") + # for room in rooms: + # print(room) + # print("--") classes = [] for class_id in class_ids: @@ -146,7 +141,7 @@ def build_drive(): id = el.id drive[key][id] = el - print(drive) + # print(drive) return drive @@ -174,39 +169,6 @@ def parse(): return lessons -TYPE_TEACHER = 0 -TYPE_ROOM = 1 -TYPE_CLASS = 2 - - -class LessonContainer(object): - """ - Needed for Django template because template language does not support dictionaries - Saves the time object and the lesson elements - """ - - def __init__(self, ): - self.time = None - self.elements = [] - - def set_time(self, time): - self.time = time - - def append(self, element): - self.elements.append(element) - - -class LessonElementContainer(object): - """ - Needed for Django template because template language does not support dictionaries - Saves the lesson element object and the room (from time object) - """ - - def __init__(self, element, room): - self.element = element - self.room = room - - def get_lesson_by_id(id): global drive lesson = Lesson() @@ -216,29 +178,29 @@ def get_lesson_by_id(id): def get_lesson_element_by_id_and_teacher(lesson_id, teacher, hour=None, weekday=None): - print(lesson_id) - print(hour, "LEWE", weekday) + # print(lesson_id) + #print(hour, "LEWE", weekday) try: lesson = get_lesson_by_id(lesson_id) except Exception: return None, None el = None i = 0 - print(lesson.elements) + #print(lesson.elements) for i, element in enumerate(lesson.elements): - print(element.teacher.shortcode) + #print(element.teacher.shortcode) if element.teacher.id == teacher.id: el = element break t = None - print(lesson.times) - print(weekday) - print(hour) + # print(lesson.times) + # print(weekday) + #print(hour) for time in lesson.times: - print("DAY", time.day, time.hour) + #print("DAY", time.day, time.hour) if time.day == weekday and time.hour == hour: t = time - print(t) + #print(t) room = None if t is not None and len(t.rooms) > i: print(t.rooms) @@ -248,100 +210,3 @@ def get_lesson_element_by_id_and_teacher(lesson_id, teacher, hour=None, weekday= if el is not None: return el, room return None, None - - -def parse_lesson_times(): - times = [] - for i, t in enumerate(LESSONS): - start_split = t[0].split(":") - start_time = timezone.datetime(year=2000, day=1, month=1, hour=int(start_split[0]), minute=int(start_split[1])) - end_time = start_time + timezone.timedelta(minutes=45) - print(start_time) - print(end_time) - times.append({ - "number": i + 1, - "number_format": t[1], - "start": start_time, - "end": end_time, - }) - return times - - -def get_plan(type, id): - """ Generates a plan for type (TYPE_TEACHE, TYPE_CLASS, TYPE_ROOM) and a id of the teacher (class, room)""" - - # Get parsed lessons - lessons = parse() - times_parsed = parse_lesson_times() - - # Init plan array - plan = [] - - # Fill plan array with LessonContainers (show upside), WIDTH and HEIGHT are defined by Django settings - for hour_idx in range(settings.TIMETABLE_HEIGHT): - plan.append(([], times_parsed[hour_idx] if len(times_parsed) > hour_idx else None)) - for day_idx in range(settings.TIMETABLE_WIDTH): - plan[hour_idx][0].append(LessonContainer()) - - # Fill plan with lessons - for lesson in lessons: - for i, element in enumerate(lesson.elements): - - # Check if the lesson element is important for that plan (look by type and id) - found = False - if type == TYPE_CLASS: - for lclass in element.classes: - if lclass.id == id: - found = True - - elif type == TYPE_TEACHER: - if element.teacher: - if element.teacher.id == id: - found = True - - elif type == TYPE_ROOM: - for time in lesson.times: - for j, lroom in enumerate(time.rooms): - if lroom.id == id: - print(lroom.name) - found = True - - # If the lesson element is important then add it to plan array - if found: - for time in lesson.times: # Go for every time the lesson is thought - # print(time.hour, " ", time.day) - # print(element.subject.shortcode) - room_index = None - for j, lroom in enumerate(time.rooms): - if lroom.id == id: - room_index = j - - # Add the time object to the matching LessonContainer on the right position in the plan array - plan[time.hour - 1][0][time.day - 1].set_time(time) - - # Check if there is an room for this time and lesson - try: - room = time.rooms[i] - except IndexError: - room = None - - # print(element) - # print(room.name) - - # Create a LessonElementContainer with room and lesson element - element_container = LessonElementContainer(element, room) - - if type != TYPE_ROOM or i == room_index: - # Add this container object to the LessonContainer object in the plan array - plan[time.hour - 1][0][time.day - 1].append(element_container) - - # print(plan) - # - # for hour in plan: - # for day in hour: - # print(day.elements) - # for c in day.elements: - # # print(c.element) - # pass - - return plan diff --git a/schoolapps/untisconnect/plan.py b/schoolapps/untisconnect/plan.py new file mode 100644 index 0000000000000000000000000000000000000000..e20dd18f51a1e3f9ac75a2165190a2ba91e4361a --- /dev/null +++ b/schoolapps/untisconnect/plan.py @@ -0,0 +1,193 @@ +import datetime + +from django.utils import timezone + +from schoolapps import settings +from schoolapps.settings import LESSONS +from untisconnect.parse import parse +from untisconnect.sub import get_substitutions_by_date_as_dict, TYPE_CANCELLATION + +TYPE_TEACHER = 0 +TYPE_ROOM = 1 +TYPE_CLASS = 2 + + +class LessonContainer(object): + """ + Needed for Django template because template language does not support dictionaries + Saves the time object and the lesson elements + """ + + def __init__(self, ): + self.time = None + self.elements = [] + + def set_time(self, time): + self.time = time + + def append(self, element): + self.elements.append(element) + + +class LessonElementContainer(object): + """ + Needed for Django template because template language does not support dictionaries + Saves the lesson element object and the room (from time object) + """ + + def __init__(self, element, room, substitution=None): + self.element = element + self.room = room + self.substitution = substitution + self.is_old = False + + +def parse_lesson_times(): + times = [] + for i, t in enumerate(LESSONS): + start_split = t[0].split(":") + start_time = timezone.datetime(year=2000, day=1, month=1, hour=int(start_split[0]), minute=int(start_split[1])) + end_time = start_time + timezone.timedelta(minutes=45) + # print(start_time) + # print(end_time) + times.append({ + "number": i + 1, + "number_format": t[1], + "start": start_time, + "end": end_time, + }) + return times + + +def get_plan(type, id, smart=False, monday_of_week=None): + """ Generates a plan for type (TYPE_TEACHE, TYPE_CLASS, TYPE_ROOM) and a id of the teacher (class, room)""" + + # Get parsed lessons + lessons = parse() + times_parsed = parse_lesson_times() + + if smart: + # print("Get substitutions for smart plan") + week_days = [monday_of_week + datetime.timedelta(days=i) for i in range(5)] + # print(week_days) + subs_for_weekday = [] + for week_day in week_days: + # print(week_day) + subs = get_substitutions_by_date_as_dict(week_day) + subs_for_weekday.append(subs) + # print(subs) + # print(len(subs)) + # Init plan array + plan = [] + already_added_subs_as_ids = [] + + # Fill plan array with LessonContainers (show upside), WIDTH and HEIGHT are defined by Django settings + for hour_idx in range(settings.TIMETABLE_HEIGHT): + plan.append(([], times_parsed[hour_idx] if len(times_parsed) > hour_idx else None)) + for day_idx in range(settings.TIMETABLE_WIDTH): + plan[hour_idx][0].append(LessonContainer()) + + # Fill plan with lessons + for lesson in lessons: + for i, element in enumerate(lesson.elements): + + # Check if the lesson element is important for that plan (look by type and id) + found = False + if type == TYPE_CLASS: + for lclass in element.classes: + if lclass.id == id: + found = True + + elif type == TYPE_TEACHER: + if element.teacher: + if element.teacher.id == id: + found = True + + elif type == TYPE_ROOM: + for time in lesson.times: + for j, lroom in enumerate(time.rooms): + if lroom.id == id: + # print(lroom.name) + found = True + + # If the lesson element is important then add it to plan array + if found: + for time in lesson.times: # Go for every time the lesson is thought + # Find matching rooms + room_index = None + for j, lroom in enumerate(time.rooms): + if lroom.id == id: + room_index = j + + # Add the time object to the matching LessonContainer on the right position in the plan array + plan[time.hour - 1][0][time.day - 1].set_time(time) + + # Check if there is an room for this time and lesson + try: + room = time.rooms[i] + except IndexError: + room = None + + # Smart Plan: Check if there substitutions for this lesson + matching_sub = None + + if smart: + # If a sub with matching lesson id and day exists + if subs_for_weekday[time.day - 1].get(lesson.id, None) is not None: + for sub in subs_for_weekday[time.day - 1][lesson.id]: + # ... check whether the sub has the right old teacher and the right lesson number + if sub["sub"].teacher_old.id == element.teacher.id and sub["sub"].lesson == time.hour: + matching_sub = sub + + # If the lesson matches, add it to the list of already added subs + if matching_sub: + already_added_subs_as_ids.append(matching_sub["sub"].id) + + # Create a LessonElementContainer with room and lesson element + element_container = LessonElementContainer(element, room, substitution=matching_sub) + + # Important for rooms: Check if the current room is the old room + if smart and matching_sub is not None: + if matching_sub["sub"].room_new is not None: + if matching_sub["sub"].room_old is not None: + if matching_sub["sub"].room_old != matching_sub["sub"].room_new: + element_container.is_old = True + else: + element_container.is_old = True + + # The rooms is empty, too, if the lesson is canceled + if matching_sub["sub"].type == TYPE_CANCELLATION: + element_container.is_old = True + + if type != TYPE_ROOM or i == room_index: + # Add this container object to the LessonContainer object in the plan array + plan[time.hour - 1][0][time.day - 1].append(element_container) + + # Now check subs which were not in this plan before + if smart: + for i, week_day in enumerate(week_days): + subs_for_this_weekday = subs_for_weekday[i] + for lesson_id, subs in subs_for_this_weekday.items(): + for sub in subs: + found = False + room = sub["sub"].room_old + if type == TYPE_CLASS: + if sub["sub"].classes: + for _class in sub["sub"].classes: + if _class.id == id: + found = True + elif type == TYPE_TEACHER: + if sub["sub"].teacher_new: + if sub["sub"].teacher_new.id == id: + found = True + + elif type == TYPE_ROOM: + if sub["sub"].room_new: + if sub["sub"].room_new.id == id: + found = True + if found: + element_container = LessonElementContainer(sub["sub"].lesson_element, room, substitution=sub) + if sub["sub"].id not in already_added_subs_as_ids: + plan[sub["sub"].lesson - 1][0][i].append(element_container) + + return plan diff --git a/schoolapps/untisconnect/sub.py b/schoolapps/untisconnect/sub.py index cb195b93b04a49db57db96f0185d254ac7bb3eb9..723f79ae2171048e996ee978890071b9d432328a 100644 --- a/schoolapps/untisconnect/sub.py +++ b/schoolapps/untisconnect/sub.py @@ -1,10 +1,10 @@ from django.utils import timezone +from schoolapps.settings import DEBUG from untisconnect import models -from untisconnect.api import run_default_filter, row_by_row_helper, get_teacher_by_id, get_subject_by_id, \ - get_room_by_id, get_class_by_id, get_corridor_by_id +from untisconnect.api import run_default_filter, row_by_row_helper from untisconnect.api_helper import run_using, untis_split_first -from untisconnect.parse import get_lesson_by_id, get_lesson_element_by_id_and_teacher, build_drive +from untisconnect.parse import get_lesson_element_by_id_and_teacher, build_drive DATE_FORMAT = "%Y%m%d" @@ -85,7 +85,7 @@ class Substitution(object): self.lesson_element, self.room_old = get_lesson_element_by_id_and_teacher(self.lesson_id, self.teacher_old, self.lesson, self.date.weekday() + 1) # print(self.lesson) - print(self.room_old) + # print(self.room_old) # Subject self.subject_old = self.lesson_element.subject if self.lesson_element is not None else None if db_obj.subject_idsubst != 0: @@ -117,10 +117,10 @@ class Substitution(object): self.classes = [] class_ids = untis_split_first(db_obj.classids, conv=int) - print(class_ids) + # print(class_ids) for id in class_ids: self.classes.append(drive["classes"][id]) - print(self.classes) + # print(self.classes) def substitutions_sorter(sub): @@ -235,6 +235,13 @@ def generate_sub_table(subs): sub_row.room = generate_room_row(sub) sub_row.room_full = generate_room_row(sub, full=True) + # if DEBUG: + # # Add id only if debug mode is on + # if sub.text: + # sub_row.text = sub.text + " " + str(sub.id) + # else: + # sub_row.text = str(sub.id) + # else: sub_row.text = sub.text sub_row.badge = None @@ -264,3 +271,19 @@ def get_substitutions_by_date(date): # print(class_.name) subs.sort(key=substitutions_sorter) return subs + + +def get_substitutions_by_date_as_dict(date): + subs_raw = get_substitutions_by_date(date) + sub_table = generate_sub_table(subs_raw) + print("SUB RAW LEN", len(sub_table)) + subs = {} + for i, sub_raw in enumerate(subs_raw): + print(i) + if sub_raw.lesson_id not in subs.keys(): + subs[sub_raw.lesson_id] = [] + subs[sub_raw.lesson_id].append({"sub": sub_raw, "table": sub_table[i]}) + # print(sub_raw.teacher_old) + # print(sub_table[i].teacher) + print(len(subs)) + return subs