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 70aec478e3d08e8ec2f7c838d5636e0ca37b5aed..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 )" family="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 )" family="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 )" family="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/README.md b/README.md index 8d350785276c54343ecaaafa88be462f4d23f829..ee48aa869a7070799da09af5612c61a30fbb5ef0 100755 --- a/README.md +++ b/README.md @@ -1,28 +1,13 @@ -# school-apps + +# SchoolApps ## Apps -### In Betrieb -keine -### Im Testbetrieb -keine -### In der Entwicklung -- **Dashboard**: Verwaltet Aktivitäten und Benachrichtigungen (welche auch per E-Mail versendet werden, dient also auch zum E-Mail-Versand) -- **AUB**: Antrag auf Unterrichtsbefreiung -- **Timetable**: Anzeige des Stundenplans, Vertretungsplan fehlt noch -### Ideen (bestätigt) -- Vertretungsplan -- Klausurenplan -- Einbindung von ILIAS-Raumbuchungen in Stundenplan -- REBUS -### Ideen (unbestätigt) -- Speiseplan -- Elternsprechtag -- Bundesjungendspiele -- Chat +siehe Wiki ## Installation -**Hinweis:** Es wird aktuell nur ein aktuelles Debian, Ubuntu, Linux Mint, etc. unterstützt. +**Hinweis:** Es wird aktuell nur ein aktuelles Debian, Ubuntu, Linux Mint, etc. unterstützt. Es werden Root-Rechte benötigt. + ### Grundsystem ``` -apt install python3 python3-dev python3-pip git mariadb-server python3-venv libldap2-dev libsasl2-dev libmysqlclient-dev +sudo apt install python3 python3-dev python3-pip git mariadb-server python3-venv libldap2-dev libsasl2-dev libmysqlclient-dev ``` ### MySQL-Datenbank @@ -44,6 +29,11 @@ CREATE DATABASE Untis; ### UNTIS-Beispieldaten laden 1. PhpMyAdmin öffnen und die Datei untiskath.sql vom Forum importieren. +### SchoolApps clonen +``` +git clone git@github.com:Katharineum/school-apps.git +``` + ### Django - Zum Installationsordner wechseln ``` @@ -58,7 +48,25 @@ pip install django-material pip install django-filter ``` - `example_secure_settings.py` zu `secure_settings.py` kopieren und anpassen -### LDAP (info.katharineum.de) + +### Submodules updaten +``` +git submodule init +git submodule update +``` + +### Migrations auflösen +Leider kommt es bei einer Erstinstallation von SchoolApps immer zu Problemen mit den Migrations. Sollte es Schwierigkeiten geben, @hansegucker kontaktieren. + +Für die Migration folgende Befehle im aktivierten VirtualEnv ausführen: +``` +python3 schoolapps/manage.py makemigrations +python3 schoolapps/manage.py migrate +``` + +### Testlauf + +## LDAP (info.katharineum.de) #### Adresse vom Info aus: localhost:389 @@ -80,9 +88,3 @@ dc=skole,dc=skolelinux,dc=no 3. Verbindung in AD mit oben genannten Daten herstellen -### Submodules updaten -``` -git submodule init -git submodule update -``` - diff --git a/schoolapps/aub/filters.py b/schoolapps/aub/filters.py index 5b354ecbfa6b231f7d7915472f475f36f6936561..aa2b9f4a4074a6965e4fac65d9e6ed5ebf1be492 100644 --- a/schoolapps/aub/filters.py +++ b/schoolapps/aub/filters.py @@ -2,13 +2,15 @@ from django import forms import django_filters from .models import Aub + class AUBFilter(django_filters.FilterSet): - #timerangechoices = [('today','Heute'),('thisWeek','Diese Woche'), ('thisMonth','Dieser Monat')] - #timerange = django_filters.ChoiceFilter(label='Zeitumfang', choices=timerangechoices) + # timerangechoices = [('today','Heute'),('thisWeek','Diese Woche'), ('thisMonth','Dieser Monat')] + # timerange = django_filters.ChoiceFilter(label='Zeitumfang', choices=timerangechoices) created_by = django_filters.ChoiceFilter(label='Von') - statuschoices = [('1','In Bearbeitung 1'),('2','In Bearbeitung 2'),('3','Genehmigt'),('4','Abgelehnt')] + statuschoices = [('1', 'In Bearbeitung 1'), ('2', 'In Bearbeitung 2'), ('3', 'Genehmigt'), ('4', 'Abgelehnt')] status = django_filters.ChoiceFilter(label='Status', choices=statuschoices, initial='In Bearbeitung 1') + class Meta: model = Aub - fields = [ 'created_by', 'status'] - ordering = 'status' \ No newline at end of file + fields = ['created_by', 'status'] + ordering = 'status' diff --git a/schoolapps/aub/forms.py b/schoolapps/aub/forms.py index 440ab358f88668924ed919efed9e1899122506c5..afd7376c9508a8a4f318ee46a429bfb2d9650727 100755 --- a/schoolapps/aub/forms.py +++ b/schoolapps/aub/forms.py @@ -3,34 +3,42 @@ from django.core.exceptions import ValidationError from django.utils import timezone from datetime import datetime from material import Layout, Row, Fieldset -from schoolapps import settings +from aub.models import Aub class FilterAUBForm(forms.Form): - timerangechoices = [('today','Heute'),('thisWeek','Diese Woche'), ('thisMonth','Dieser Monat')] - timerange = forms.ChoiceField(label='Zeitumfang', choices=timerangechoices, initial='thisWeek', widget=forms.RadioSelect) - statuschoices = [('all','Alle'), ('processing','In Bearbeitung'), ('decided','Entschieden')] + timerangechoices = [('today', 'Heute'), ('thisWeek', 'Diese Woche'), ('thisMonth', 'Dieser Monat')] + timerange = forms.ChoiceField(label='Zeitumfang', choices=timerangechoices, initial='thisWeek', + widget=forms.RadioSelect) + statuschoices = [('all', 'Alle'), ('processing', 'In Bearbeitung'), ('decided', 'Entschieden')] status = forms.ChoiceField(label='Status', choices=statuschoices, initial='processing', widget=forms.RadioSelect) - sortingchoices = [('created_at_asc','Nach Datum (neue oben)'),('created_at_desc','Nach Datum (alte oben)'), ('created_by','Nach Antragsteller')] - sorting = forms.ChoiceField(label='Sortierung', choices=sortingchoices, initial='created_at_asc', widget=forms.RadioSelect) + sortingchoices = [('created_at_asc', 'Nach Datum (neue oben)'), ('created_at_desc', 'Nach Datum (alte oben)'), + ('created_by', 'Nach Antragsteller')] + sorting = forms.ChoiceField(label='Sortierung', choices=sortingchoices, initial='created_at_asc', + widget=forms.RadioSelect) layout = Layout(Fieldset('Filter', Row('timerange', 'status', 'sorting'), ) ) + def clean(self): cleaned_data = super().clean() -class ApplyForAUBForm(forms.Form): + +class ApplyForAUBForm(forms.ModelForm): + 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.')] + initial_from_time = '8:00' + initial_to_time = '15:35' from_date = forms.DateField(label='Datum', input_formats=['%d.%m.%Y']) - from_lesson = forms.ChoiceField(label='Stunde', choices=settings.LESSONS, required=False, + from_lesson = forms.ChoiceField(label='Stunde', choices=lessons, required=False, widget=forms.Select(attrs={'onchange': 'setTime(this)'})) - from_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial='8:00', ) + from_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial=initial_from_time, ) to_date = forms.DateField(label='Datum', input_formats=['%d.%m.%Y']) - to_lesson = forms.ChoiceField(label='Stunde', choices=settings.LESSONS, required=False, + to_lesson = forms.ChoiceField(label='Stunde', choices=lessons, required=False, widget=forms.Select(attrs={'onchange': 'setTime(this)'})) - to_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial='15:35') - + to_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial=initial_to_time) description = forms.CharField(label='Bitte begründen Sie Ihren Antrag.') layout = Layout(Fieldset('Von', @@ -43,16 +51,20 @@ class ApplyForAUBForm(forms.Form): 'description'), ) + class Meta: + model = Aub + fields = ('id', 'from_date', 'from_time', 'to_date', 'to_time', 'description') + def clean(self): cleaned_data = super().clean() def clean_from_to_date(self): # not related to a form field, just to clean datetime values from_date = self.cleaned_data['from_date'] - from_lesson = self.cleaned_data['from_lesson'] + # from_lesson = self.cleaned_data['from_lesson'] from_time = self.cleaned_data['from_time'] to_date = self.cleaned_data['to_date'] - to_lesson = self.cleaned_data['to_lesson'] + # to_lesson = self.cleaned_data['to_lesson'] to_time = self.cleaned_data['to_time'] from_datetime = timezone.datetime.combine(from_date, from_time) print(from_datetime) @@ -72,3 +84,83 @@ class ApplyForAUBForm(forms.Form): return data +# class ApplyForAUBForm(forms.Form): +# 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.')] +# from_date = forms.DateField(label='Datum', input_formats=['%d.%m.%Y']) +# from_lesson = forms.ChoiceField(label='Stunde', choices=lessons, required=False, +# widget=forms.Select(attrs={'onchange': 'set_time(this)'})) +# from_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial='8:00', ) +# to_date = forms.DateField(label='Datum', input_formats=['%d.%m.%Y']) +# to_lesson = forms.ChoiceField(label='Stunde', choices=lessons, required=False, +# widget=forms.Select(attrs={'onchange': 'set_time(this)'})) +# to_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial='15:35') +# +# description = forms.CharField(label='Bitte begründen Sie Ihren Antrag.') +# +# layout = Layout(Fieldset('Von', +# Row('from_date', 'from_lesson', 'from_time'), +# ), +# Fieldset('Bis', +# Row('to_date', 'to_lesson', 'to_time'), +# ), +# Fieldset('Grund / Vorhaben', +# 'description'), +# ) +# +# def clean(self): +# cleaned_data = super().clean() +# +# def clean_from_to_date(self): +# # not related to a form field, just to clean datetime values +# from_date = self.cleaned_data['from_date'] +# from_lesson = self.cleaned_data['from_lesson'] +# from_time = self.cleaned_data['from_time'] +# to_date = self.cleaned_data['to_date'] +# to_lesson = self.cleaned_data['to_lesson'] +# to_time = self.cleaned_data['to_time'] +# from_datetime = timezone.datetime.combine(from_date, from_time) +# print(from_datetime) +# to_datetime = timezone.datetime.combine(to_date, to_time) +# if (from_datetime < datetime.now()) or (to_datetime < datetime.now()): +# raise ValidationError( +# 'Die Befreiung kann nicht für bereits vergangene Tage durchgeführt werden (Datumsfehler).') +# elif from_datetime > to_datetime: +# raise ValidationError('Das Von-Datum liegt hinter dem Bis-Datum.') +# return True +# +# # def clean_from_date(self): +# # data = self.cleaned_data['from_date'] +# # # if data < timezone.datetime.date(timezone.now()): +# # # raise ValidationError('Die Befreiung kann nur zukünftig durchgeführt werden (Datumsfehler).') +# # return data +# # +# # def clean_to_date(self): +# # data = self.cleaned_data['to_date'] +# # # if data < timezone.datetime.date(timezone.now()): +# # # raise ValidationError('Die Befreiung kann nur zukünftig durchgeführt werden.') +# # return data +# # +# # def clean_from_time(self): +# # data = self.cleaned_data['from_time'] +# # # print('Data:', type(data), 'Now:', type(timezone.datetime.time(timezone.now()))) +# # +# # # if data < timezone.datetime.time(timezone.now()): +# # # raise ValidationError('Die Befreiung kann nur zukünftig durchgeführt werden (Zeitfehler).') +# # +# # return data +# # +# # def clean_to_time(self): +# # data = self.cleaned_data['to_time'] +# # +# # # if data < timezone.datetime.time(timezone.now()): +# # # raise ValidationError('Die Befreiung kann nur zukünftig durchgeführt werden.') +# # return data +# +# def clean_description(self): +# data = self.cleaned_data['description'] +# self.clean_from_to_date() +# if len(data) < 10: +# raise ValidationError('Bitte teilen Sie uns etwas mehr über Ihren Befreiungswunsch mit.') +# +# return data diff --git a/schoolapps/aub/migrations/0003_auto_20181223_0905.py b/schoolapps/aub/migrations/0003_auto_20181223_0905.py new file mode 100644 index 0000000000000000000000000000000000000000..4ed0109e2fc0134f0c4d4c9fc121cc9d566cb4a4 --- /dev/null +++ b/schoolapps/aub/migrations/0003_auto_20181223_0905.py @@ -0,0 +1,43 @@ +# Generated by Django 2.1.2 on 2018-12-23 08:05 + +import datetime +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0002_aub_status'), + ] + + operations = [ + migrations.RemoveField( + model_name='aub', + name='from_dt', + ), + migrations.RemoveField( + model_name='aub', + name='to_dt', + ), + migrations.AddField( + model_name='aub', + name='from_date', + field=models.DateField(default=datetime.date.today), + ), + migrations.AddField( + model_name='aub', + name='from_time', + field=models.TimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name='aub', + name='to_date', + field=models.DateField(default=datetime.date.today), + ), + migrations.AddField( + model_name='aub', + name='to_time', + field=models.TimeField(default=django.utils.timezone.now), + ), + ] diff --git a/schoolapps/aub/migrations/0006_merge_20190106_1714.py b/schoolapps/aub/migrations/0006_merge_20190106_1714.py new file mode 100644 index 0000000000000000000000000000000000000000..302ebcd6595efe2866096e6c79ac43417453e442 --- /dev/null +++ b/schoolapps/aub/migrations/0006_merge_20190106_1714.py @@ -0,0 +1,13 @@ +# Generated by Django 2.0.7 on 2019-01-06 16:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('aub', '0003_auto_20181223_0905'), + ('aub', '0005_auto_20181128_1709'), + ] + + operations = [ + ] diff --git a/schoolapps/aub/models.py b/schoolapps/aub/models.py index 3bb8d3a05f508ef41001e18ccc628502fa711ca9..21d0296d435826e528dc40121a13de0bf1b434ef 100755 --- a/schoolapps/aub/models.py +++ b/schoolapps/aub/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils import timezone from django.contrib.auth.models import User +from datetime import date def get_default_user(): user, created = User.objects.get_or_create(username='nouser') @@ -22,8 +23,12 @@ def get_default_status(): class Aub(models.Model): # Time - from_dt = models.DateTimeField(default=timezone.now) - to_dt = models.DateTimeField(default=timezone.now) +# from_dt = models.DateTimeField(default=timezone.now) +# to_dt = models.DateTimeField(default=timezone.now) + from_date = models.DateField(default=date.today) + from_time = models.TimeField(default=timezone.now) + to_date = models.DateField(default=date.today) + to_time = models.TimeField(default=timezone.now) # Information description = models.TextField() diff --git a/schoolapps/aub/templates/aub/apply_for.html b/schoolapps/aub/templates/aub/apply_for.html index cbe8eb4c7a0e531a44fa1dfeec092d93e90e70d3..1baa3d55e8ada3cbd50390acaf902816b794edf0 100755 --- a/schoolapps/aub/templates/aub/apply_for.html +++ b/schoolapps/aub/templates/aub/apply_for.html @@ -3,11 +3,13 @@ <main> - <h5>Antrag auf Unterrichtsbefreiung</h5> + <h4>Antrag auf Unterrichtsbefreiung</h4> + <form method = "POST" > {% csrf_token %} +{# Von#} +{# <input type="text" label="Von" input_formats=['%d.%m.%Y'] value="{{ from_dt }}" name="from_date" >#} {% form form=form %} -{# {% part form.from_lesson prefix %}<div></div>{% endpart %} #} {% endform %} <button type="submit" class="waves-effect waves-light btn green"> <i class="material-icons left">send</i> Antrag stellen diff --git a/schoolapps/aub/templates/aub/check.html b/schoolapps/aub/templates/aub/check.html index bf9c1ad0c39a68cb876bbf7c9c2c061c3fc3e3b8..7f720724573a67f695b774712fb8653f6085fa77 100755 --- a/schoolapps/aub/templates/aub/check.html +++ b/schoolapps/aub/templates/aub/check.html @@ -1,65 +1,68 @@ {% include 'partials/header.html' %} <main> - <form method="GET"> - {{ filter.form.as_p }} - <button type="submit" class="waves-effect waves-light btn blue"> - <i class="material-icons left"></i> Filter aktualisieren - </button> - </form> - <h5>Ausstehende Anträge</h5> - - - <ul class="collection"> -{# {% for aub in aubs %}#} - {% for aub in filter.qs %} -{# {% for aub in aubs %}#} - <li class="collection-item"> - <div class="row"> - <div class="col s12 m4"> - <p class="title"> + <h4>Ausstehende Anträge</h4> + + <form method="GET"> + {{ filter.form.as_p }} + <button type="submit" class="waves-effect waves-light btn"> + <i class="material-icons left">refresh</i> Filter aktualisieren + </button> + </form> + + <ul class="collection"> + {# {% for aub in aubs %}#} + {% for aub in filter.qs %} + {# {% for aub in aubs %}#} + <li class="collection-item"> + <div class="row"> + <div class="col s12 m4"> + <p class="title"> <span class="item-text"> <i class="material-icons">access_time</i> - {{ aub.from_dt }} bis {{ aub.to_dt }} +{# {{ aub.from_dt }} bis {{ aub.to_dt }}#} + {{ aub.from_date }} bis {{ aub.to_date }} </span> - </p> - <p><a href="{% url 'aub_details' aub.id %}">{{ aub.description }}</a></p> - </div> - <div class="col s12 m2"> - <p> + </p> + <p><a href="{% url 'aub_details' aub.id %}">{{ aub.description }}</a></p> + </div> + <div class="col s12 m2"> + <p> <span class="item-text"> <i class="material-icons">person</i> {{ aub.created_by }} </span> - </p> - </div> - <div class="col s12 m4"> - <p> - <form action="" method="POST" class="right"> - {% csrf_token %} - <input type="hidden" value="{{ aub.id }}" name="aub-id"> - {% if aub.status_id != 3 %} - <button type="submit" name="allow" class="waves-effect waves-light btn-flat btn-flat-large" title="Annehmen"> - <i class="material-icons center green-text">check_circle</i> - </button> - {% endif %} - {% if aub.status_id != 4 %} - <button type="submit" name="deny" class="waves-effect waves-light btn-flat btn-flat-large" title="Ablehnen"> - <i class="material-icons center red-text">not_interested</i> - </button> - {% endif %} - </form> - </p> - </div> - <div class="col s12 m2"> - <p> - <span class="badge new {{ aub.status.style_classes }}">{{ aub.status.name }}</span> - </p> - </div> + </p> + </div> + <div class="col s12 m4"> + <p> + <form action="" method="POST" class="right"> + {% csrf_token %} + <input type="hidden" value="{{ aub.id }}" name="aub-id"> + {% if aub.status_id != 3 %} + <button type="submit" name="allow" + class="waves-effect waves-light btn-flat btn-flat-large" title="Annehmen"> + <i class="material-icons center green-text">check_circle</i> + </button> + {% endif %} + {% if aub.status_id != 4 %} + <button type="submit" name="deny" + class="waves-effect waves-light btn-flat btn-flat-large" title="Ablehnen"> + <i class="material-icons center red-text">not_interested</i> + </button> + {% endif %} + </form> + </p> + </div> + <div class="col s12 m2"> + <p> + <span class="badge new {{ aub.status.style_classes }}">{{ aub.status.name }}</span> + </p> </div> - </li> - {% endfor %} - </ul> + </div> + </li> + {% endfor %} + </ul> </main> {% include 'partials/footer.html' %} diff --git a/schoolapps/aub/templates/aub/details.html b/schoolapps/aub/templates/aub/details.html index ba2a86f28b00e77d05c75fdd6583a4466ce10734..cdb152ff2cc4fa99dd6ca1e66a1c0f00122cd4f1 100755 --- a/schoolapps/aub/templates/aub/details.html +++ b/schoolapps/aub/templates/aub/details.html @@ -1,6 +1,6 @@ {% include 'partials/header.html' %} <main> - <h5>{{ aub.from_dt }} bis {{ aub.to_dt }}</h5> + <h4>{{ aub.from_date }} bis {{ aub.to_date }}</h4> <p><strong>Status: <span class="badge new {{ aub.status.style_classes }}">{{ aub.status.name }}</span></strong></p> <p>{{ aub.description }}</p> <hr> diff --git a/schoolapps/aub/templates/aub/edit.html b/schoolapps/aub/templates/aub/edit.html new file mode 100755 index 0000000000000000000000000000000000000000..3b07e767ddc99f428af5eb3268eb30574856880e --- /dev/null +++ b/schoolapps/aub/templates/aub/edit.html @@ -0,0 +1,19 @@ +{% include 'partials/header.html' %} +{% load material_form %} + + +<main> + <h4>Antrag auf Unterrichtsbefreiung</h4> + + <form method = "POST" > + {% csrf_token %} +{# <input type="hidden" value="{{ id }}" name="id" />#} + {% form form=form %} + {% endform %} + <button type="submit" class="waves-effect waves-light btn green"> + <i class="material-icons left">send</i> Antrag ändern + </button> + </form> +</main> + +{% include 'partials/footer.html' %} diff --git a/schoolapps/aub/templates/aub/index.html b/schoolapps/aub/templates/aub/index.html index aa9c10535f60c5872d7dba740360a3e7a02dc364..0a301f1ea5d0336ad8abc99aed8309adb1e75525 100755 --- a/schoolapps/aub/templates/aub/index.html +++ b/schoolapps/aub/templates/aub/index.html @@ -3,21 +3,25 @@ <main> - <a href="{% url 'aub_apply_for' %}" class="waves-effect waves-light btn green">Neuen Antrag stellen</a> - {% block content %} + {% block content %} - <h5>Übersicht der Anträge</h5> - <ul class="collection"> - {% for aub in aubs %} - <li class="collection-item"> + <h4>Übersicht der Anträge</h4> - <div class="row"> + <a href="{% url 'aub_apply_for' %}" class="waves-effect waves-light btn green">Neuen Antrag stellen</a> + + + <ul class="collection"> + {% for aub in aubs %} + <li class="collection-item"> + + <div class="row"> <div class="col s12 m4"> <p class="title"> <span class="item-text"> <i class="material-icons">access_time</i> - {{ aub.from_dt }} bis {{ aub.to_dt }} +{# {{ aub.from_dt }} bis {{ aub.to_dt }}#} + {{ aub.from_date }}, {{ aub.from_time }} bis {{ aub.to_date }}, {{ aub.to_time }} </span> </p> <p><a href="{% url 'aub_details' aub.id %}">{{ aub.description }}</a></p> @@ -32,20 +36,30 @@ </div> <div class="col s12 m4"> <p> - <form action="" method="POST" class="right"> - {% csrf_token %} - <input type="hidden" value="{{ aub.id }}" name="aub-id"> {% if aub.status_id == 1 %} - <button type="submit" name="edit" class="waves-effect waves-light btn-flat btn-flat-medium" title="Bearbeiten"> - <i class="material-icons center green-text">create</i> - </button> + <form action="{% url 'aub_edit' aub.id %}" class="right"> + {% csrf_token %} + {# <input type="hidden" value="{{ aub.id }}" name="aub-id">#} + <button type="submit" name="edit" + class="waves-effect waves-light btn-flat btn-flat-medium" + title="Bearbeiten"> + <i class="material-icons center green-text">create</i> + </button> + </form> {% endif %} - {% if aub.status_id == 1 or aub.status_id == 2 %} - <button type="submit" onclick="return confirm('Wollen Sie den Antrag wirklich löschen?')" name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium" title="Löschen"> - <i class="material-icons center red-text">cancel</i> - </button> - {% endif %} - </form> + {% if aub.status_id == 1 or aub.status_id == 2 %} + <form action="" method="POST" class="right"> + {% csrf_token %} + <input type="hidden" value="{{ aub.id }}" name="aub-id"> + + <button type="submit" + onclick="return confirm('Wollen Sie den Antrag wirklich löschen?')" + name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium" + title="Löschen"> + <i class="material-icons center red-text">cancel</i> + </button> + </form> + {% endif %} </p> </div> @@ -53,10 +67,10 @@ <p><span class="badge new {{ aub.status.style_classes }}">{{ aub.status.name }}</span></p> </div> </div> - - </li> - {% endfor %} - </ul> + + </li> + {% endfor %} + </ul> {% endblock %} </main> {% include 'partials/footer.html' %} diff --git a/schoolapps/aub/urls.py b/schoolapps/aub/urls.py index c413e8709e6603d0c648432870eb858cc9c9266c..a1584e0aeac905e70e1ac1e89308ad937700c890 100755 --- a/schoolapps/aub/urls.py +++ b/schoolapps/aub/urls.py @@ -4,8 +4,9 @@ from . import views urlpatterns = [ path('', views.index, name='aub_index'), - path('details/<int:aub_id>/', views.details, name='aub_details'), + path('details/<int:id>', views.details, name='aub_details'), path('apply_for', views.apply_for, name='aub_apply_for'), + path('edit/<int:id>', views.edit, name='aub_edit'), path('applied_for', views.applied_for, name='aub_applied_for'), path('check1', views.check1, name='aub_check1'), path('check2', views.check2, name='aub_check2'), diff --git a/schoolapps/aub/views.py b/schoolapps/aub/views.py index e2a385b26894cc9771a1d0553838f2f9f1e07557..6fb2c3330809fac148c7ac174a3225216eb10aff 100755 --- a/schoolapps/aub/views.py +++ b/schoolapps/aub/views.py @@ -3,7 +3,9 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse from django.utils import timezone from django.utils import formats +from datetime import date from datetime import datetime as dt +from django.core.exceptions import ValidationError from .apps import AubConfig from dashboard.models import Activity, register_notification @@ -17,11 +19,26 @@ SEMI_ALLOWED_STATUS = Status.objects.get_or_create(name='In Bearbeitung 2', styl ALLOWED_STATUS = Status.objects.get_or_create(name='Genehmigt', style_classes='green')[0] NOT_ALLOWED_STATUS = Status.objects.get_or_create(name='Abgelehnt', style_classes='red')[0] + @login_required @permission_required('aub.apply_for_aub') def index(request): -# order_crit = '-created_at' - order_crit = 'from_dt' + if 'aub-id' in request.POST: + id = request.POST['aub-id'] + # Edit button pressed? + if 'edit' in request.POST: + instance = Aub.objects.filter(id=id) + print('...Edit wurde gewählt') + # return render(request, 'aub/apply_for.html', {'filter': instance}) + apply_for(request, id=id) + + # Cancel button pressed? + elif 'cancel' in request.POST: + instance = Aub.objects.get(id=id) + instance.delete() + print('Eintrag gelöscht') + # order_crit = '-created_at' + order_crit = 'from_date' aubs = Aub.objects.filter(created_by=request.user).order_by(order_crit)[:100] context = { @@ -29,47 +46,174 @@ def index(request): } return render(request, 'aub/index.html', context) + @login_required @permission_required('aub.apply_for_aub') @check_own_aub(login_url='/index.html') -def details(request, aub_id): - aub = get_object_or_404(Aub, id=aub_id) +def details(request, id): + aub = get_object_or_404(Aub, id=id) context = { 'aub': aub } return render(request, 'aub/details.html', context) + @login_required @permission_required('aub.apply_for_aub') def apply_for(request): if request.method == 'POST': - form = ApplyForAUBForm(request.POST) + print('Fall 1 - ') + if 'aub-id' in request.POST: + id = request.POST['aub-id'] + instance = Aub.objects.get(id=id) + form = ApplyForAUBForm(instance=instance) + print('Fall 2 - ', 'form.is_valid:', form.is_valid(), 'form.errors:', form.errors) + else: + form = ApplyForAUBForm(request.POST or None) + print('Fall 3 - ', 'request.POST:', request.POST, 'form.is_valid:', form.is_valid(), 'form.errors:', + form.errors) + else: + form = ApplyForAUBForm() + print('Fall 4 - ', 'form.is_valid:', form.is_valid(), 'form.errors:', form.errors) + print('Fall 5 - ', 'form.is_valid:', form.is_valid(), 'form.errors:', form.errors) + if form.is_valid(): + print('form:', form) + # form.save() + from_date = form.cleaned_data['from_date'] + from_time = form.cleaned_data['from_time'] + to_date = form.cleaned_data['to_date'] + to_time = form.cleaned_data['to_time'] + description = form.cleaned_data['description'] - if form.is_valid(): - from_dt = timezone.datetime.combine(form.cleaned_data['from_date'], form.cleaned_data['from_time']) - to_dt = timezone.datetime.combine(form.cleaned_data['to_date'], form.cleaned_data['to_time']) - description = form.cleaned_data['description'] + aub = Aub(from_date=from_date, from_time=from_time, to_date=to_date, to_time=to_time, description=description, + created_by=request.user) + aub.save() - aub = Aub(from_dt=from_dt, to_dt=to_dt, description=description, created_by=request.user) - aub.save() + a = Activity(user=request.user, title="Antrag auf Unterrichtsbefreiung gestellt", + description="Sie haben einen Antrag auf Unterrichtsbefreiung " + + "für den Zeitraum von {} bis {} gestellt.".format( + aub.from_date, aub.to_date), app=AubConfig.verbose_name) + a.save() + return redirect('aub_applied_for') + return render(request, 'aub/apply_for.html', {'form': form}) - a = Activity(user=request.user, title="Antrag auf Unterrichtsbefreiung gestellt", - description="Sie haben einen Antrag auf Unterrichtsbefreiung " + - "für den Zeitraum von {} bis {} gestellt.".format( - aub.from_dt, aub.to_dt), app=AubConfig.verbose_name) - a.save() - return redirect(reverse('aub_applied_for')) - else: - form = ApplyForAUBForm() +# # Form is filled +# if request.method == 'POST': +# # get form via edit-button +# if 'aub-id' in request.POST: +# id = request.POST['aub-id'] +# # instance = get_object_or_404(Aub, id=id) +# instance = Aub.objects.get(id=id) +# print('AUB:', id, '|', instance.from_date, '|', instance.to_date, '|', instance.description) +# #instance.description = 'Mal was ganz anderes' +# form = ApplyForAUBForm(request.POST, instance=instance) +# #print('Form ist valid? IF:', instance.created_by, instance.to_date, instance.id) +# return render(request, 'aub/apply_for.html', {'form': form}) +# # get a new item +# else: +# form = ApplyForAUBForm(request.POST or None) +# print('Form ist valid? ELSE:', form.errors) +# if form.is_valid(): +# print('Form ist valid!', form.errors) +# aub = form.save() +# print('aub-id:', aub.id) +# aub.created_by = request.user +# aub.save() +# return redirect('aub_applied_for') +# form = ApplyForAUBForm() +# # return render(request, 'aub/apply_for.html', {'form': form, 'from_dt': instance.from_dt}) +# return render(request, 'aub/apply_for.html', {'form': form}) - context = { - 'form': form, - } +# if request.method == 'POST': +# +# if 'aub-id' in request.POST: +# aub_id = request.POST['aub-id'] +# aub = Aub.objects.get(id=aub_id) +# print('AUB:', aub_id, '|', aub.from_dt, '|', aub.to_dt, '|', aub.description) +# form = ApplyForAUBForm(request.POST, instance=aub) +# else: +# form = ApplyForAUBForm(request.POST) +# # form = ApplyForAUBForm(request.POST, initial=[aub.from_dt, aub.to_dt, aub.description]) +# if form.is_valid(): +# from_dt = timezone.datetime.combine(form.cleaned_data['from_date'], form.cleaned_data['from_time']) +# to_dt = timezone.datetime.combine(form.cleaned_data['to_date'], form.cleaned_data['to_time']) +# description = form.cleaned_data['description'] +# +# aub = Aub(from_dt=from_dt, to_dt=to_dt, description=description, created_by=request.user) +# aub.save() +# +# a = Activity(user=request.user, title="Antrag auf Unterrichtsbefreiung gestellt", +# description="Sie haben einen Antrag auf Unterrichtsbefreiung " + +# "für den Zeitraum von {} bis {} gestellt.".format( +# aub.from_dt, aub.to_dt), app=AubConfig.verbose_name) +# a.save() +# +# return redirect(reverse('aub_applied_for')) +# +# else: +# form = ApplyForAUBForm() +# +# context = { +# 'Aub': aub, +# 'form': form, +# } +# return render(request, 'aub/apply_for.html', context) - return render(request, 'aub/apply_for.html', context) +# @login_required +# @permission_required('aub.apply_for_aub') +# def apply_for(request): +# if request.method == 'POST': +# +# if 'aub-id' in request.POST: +# aub_id = request.POST['aub-id'] +# aub = Aub.objects.get(id=aub_id) +# print('AUB:', aub_id, '|', aub.from_dt, '|', aub.to_dt, '|', aub.description) +# form = ApplyForAUBForm(request.POST, instance=aub) +# else: +# form = ApplyForAUBForm(request.POST) +# # form = ApplyForAUBForm(request.POST, initial=[aub.from_dt, aub.to_dt, aub.description]) +# if form.is_valid(): +# from_dt = timezone.datetime.combine(form.cleaned_data['from_date'], form.cleaned_data['from_time']) +# to_dt = timezone.datetime.combine(form.cleaned_data['to_date'], form.cleaned_data['to_time']) +# description = form.cleaned_data['description'] +# +# aub = Aub(from_dt=from_dt, to_dt=to_dt, description=description, created_by=request.user) +# aub.save() +# +# a = Activity(user=request.user, title="Antrag auf Unterrichtsbefreiung gestellt", +# description="Sie haben einen Antrag auf Unterrichtsbefreiung " + +# "für den Zeitraum von {} bis {} gestellt.".format( +# aub.from_dt, aub.to_dt), app=AubConfig.verbose_name) +# a.save() +# +# return redirect(reverse('aub_applied_for')) +# +# else: +# form = ApplyForAUBForm() +# +# context = { +# 'Aub': aub, +# 'form': form, +# } +# return render(request, 'aub/apply_for.html', context) +@login_required +@permission_required('aub.apply_for_aub') +def edit(request, id): + aub = get_object_or_404(Aub, id=id) + form = ApplyForAUBForm(instance=aub) + template = 'aub/edit.html' + if request.method == 'POST': + form = ApplyForAUBForm(request.POST, instance=aub) + if form.is_valid(): + form.save() + return redirect(reverse('aub_applied_for')) + context = { + 'form': form + } + return render(request, template, context) @login_required @@ -87,21 +231,15 @@ def applied_for(request): def check1(request): if request.method == 'POST': if 'aub-id' in request.POST: - aub_id = request.POST['aub-id'] + id = request.POST['aub-id'] if 'allow' in request.POST: - Aub.objects.filter(id=aub_id).update(status=SEMI_ALLOWED_STATUS) + Aub.objects.filter(id=id).update(status=SEMI_ALLOWED_STATUS) elif 'deny' in request.POST: - Aub.objects.filter(id=aub_id).update(status=NOT_ALLOWED_STATUS) + Aub.objects.filter(id=id).update(status=NOT_ALLOWED_STATUS) aub_list = Aub.objects.all().order_by('status') aubs = AUBFilter(request.GET, queryset=aub_list) return render(request, 'aub/check.html', {'filter': aubs}) - #aubs = Aub.objects.filter(status=IN_PROCESSING_STATUS) - #context = { - # 'aubs': aubs - #} - - #return render(request, 'aub/check.html', context) @login_required @@ -109,39 +247,39 @@ def check1(request): def check2(request): if request.method == 'POST': if 'aub-id' in request.POST: - aub_id = request.POST['aub-id'] - aub = Aub.objects.get(id=aub_id) + id = request.POST['aub-id'] + aub = Aub.objects.get(id=id) if 'allow' in request.POST: # Update status - Aub.objects.filter(id=aub_id).update(status=ALLOWED_STATUS) + Aub.objects.filter(id=id).update(status=ALLOWED_STATUS) # Notify user register_notification(title="Ihr Antrag auf Unterrichtsbefreiung wurde genehmigt", description="Ihr Antrag auf Unterrichtsbefreiung vom {} bis {} wurde von der " "Schulleitung genehmigt.".format( - formats.date_format(aub.from_dt), - formats.date_format(aub.to_dt)), + # formats.date_format(aub.from_dt), + # formats.date_format(aub.to_dt)), + formats.date_format(aub.from_date), + formats.date_format(aub.to_date)), app=AubConfig.verbose_name, user=aub.created_by, link=request.build_absolute_uri(reverse('aub_details', args=[aub.id]))) elif 'deny' in request.POST: # Update status - Aub.objects.filter(id=aub_id).update(status=NOT_ALLOWED_STATUS) + Aub.objects.filter(id=id).update(status=NOT_ALLOWED_STATUS) # Notify user register_notification(title="Ihr Antrag auf Unterrichtsbefreiung wurde abgelehnt", description="Ihr Antrag auf Unterrichtsbefreiung vom {} bis {} wurde von der " "Schulleitung abgelehnt. Für weitere Informationen kontaktieren Sie " "bitte die Schulleitung.".format( - formats.date_format(aub.from_dt), - formats.date_format(aub.to_dt)), + # formats.date_format(aub.from_dt), + # formats.date_format(aub.to_dt)), + formats.date_format(aub.from_date), + formats.date_format(aub.to_date)), app=AubConfig.verbose_name, user=aub.created_by, link=request.build_absolute_uri(reverse('aub_details', args=[aub.id]))) aub_list = Aub.objects.all().order_by('status') aubs = AUBFilter(request.GET, queryset=aub_list) -# aubs = Aub.objects.filter(status=SEMI_ALLOWED_STATUS) -# context = { -# 'aubs': aubs -# } + return render(request, 'aub/check.html', {'filter': aubs}) -# return render(request, 'aub/check.html', context) 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..5ffe85bbc1fe6f8dd2fc7941b95fa2f70dfb6538 100644 --- a/schoolapps/static/common/helper.js +++ b/schoolapps/static/common/helper.js @@ -53,6 +53,9 @@ function setTime(lesson_field) { } $(document).ready(function () { + $("dmc-datetime input").addClass("datepicker"); + $("[data-form-control='time']").addClass("timepicker"); + // Initialize sidenav [MAT] $(".sidenav").sidenav(); @@ -90,6 +93,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..bc40c3299aba5c33f7f3699a25feca1d92f66bb7 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; } /**********/ @@ -82,12 +82,28 @@ ul.collection .collection-item .title { font-weight: bold; } +.section { + padding: 0; +} + +form .row { + margin-top: 0; + margin-bottom: 0; +} + /* Badges */ span.badge.new::after { content: ""; } +span.badge.new { + font-size: 1rem; + line-height: 26px; + height: 26px; +} + + /*span.badge {*/ /*width: 9em;*/ /*line-height: 4ex;*/ @@ -131,6 +147,7 @@ span.badge.new::after { } .lesson-card .card-content div { + padding: 5px; height: 100%; width: 100%; display: flex; @@ -148,6 +165,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 +205,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 5ecd73baa9bf6e46fb2406c3b86cca546d68e75d..c3ec6b79cb7f7b2f3acaa2cb10e8a05a19249194 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> @@ -88,10 +87,10 @@ </li> {% endif %} {% if perms.aub.check1_aub %} - <li><a href="{% url 'aub_check1' %}"><i class="material-icons">check</i> Anträge genehmigen 1</a></li> + <li><a href="{% url 'aub_check1' %}"><i class="material-icons">done</i> Anträge genehmigen 1</a></li> {% endif %} {% if perms.aub.check2_aub %} - <li><a href="{% url 'aub_check2' %}"><i class="material-icons">check_box</i> Anträge genehmigen 2</a> + <li><a href="{% url 'aub_check2' %}"><i class="material-icons">done_all</i> Anträge genehmigen 2</a> </li> {% endif %} {% if perms.aub.apply_for_aub or perms.aub.check1_aub or perms.aub.check2_aub %} @@ -103,9 +102,15 @@ <li> <a class="subheader grey lighten-3">Stundenplan</a> </li> + <li> + <a href="{% url 'timetable_my_plan' %}" style="padding-right: 10px;"> + <i class="material-icons">person</i> Mein Plan + <span class="badge new primary-color ">SMART PLAN</span> + </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> @@ -135,6 +140,24 @@ <div class="divider"></div> </li> {% endif %} + {% if user.is_superuser %} + <li> + <a class="subheader grey lighten-3">Administration</a> + </li> + <li> + <a href="/admin/"> + <i class="material-icons">dashboard</i> Django-Administration + </a> + </li> + <li> + <a href="/settings/"> + <i class="material-icons">settings</i> Einstellungen + </a> + </li> + <li> + <div class="divider"></div> + </li> + {% endif %} <li> <a href="{% url 'logout' %}"> <i class="material-icons">exit_to_app</i> Abmelden 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..521cf5cb8c3abdf5b14f878e602ece0e5953d7d6 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/myplan.html @@ -0,0 +1,83 @@ +{% include 'partials/header.html' %} + + +<main> + <div class="row nomargin"> + <div class="col m12 s12 l6"> + <h4> + Mein Plan + <span class="badge new primary-color ">SMART PLAN</span> + </h4> + <h6>für <em>{{ el }}</em></h6> + </div> + </div> + + + <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