diff --git a/.gitignore b/.gitignore index a7a328b769adb62645247b5ceef62be67207c732..d4282223c22241df28366753b11bca4059fb7ee5 100755 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ secure* class.pdf class.tex .idea/ -media/ \ No newline at end of file +media/ +node_modules \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index ba7b70587080ac2d496eba641ea6d25fda1ac287..924f1208bf2eca4ebe2a80639dfbebf8004c892d 100755 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (SchoolApps)" project-jdk-type="Python SDK" /> + <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (school-apps)" project-jdk-type="Python SDK" /> <component name="PyCharmProfessionalAdvertiser"> <option name="shown" value="true" /> </component> diff --git a/.idea/school-apps.iml b/.idea/school-apps.iml index 71a1486a0167aa11c087e5b6f40cdaeb7331a207..92aaf5da99a787684be08744cf91a1b60503b225 100755 --- a/.idea/school-apps.iml +++ b/.idea/school-apps.iml @@ -20,7 +20,6 @@ <orderEntry type="jdk" jdkName="Python 3.7 (SchoolApps)" 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" /> <orderEntry type="library" name="react.production" level="application" /> <orderEntry type="library" name="react-dom.production" level="application" /> <orderEntry type="library" name="jquery" level="application" /> diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 16cb31fd756461aac24fc959ef5baf83c2d55506..94a25f7f4cb416c083d265558da75d457237d671 100755 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="VcsDirectoryMappings"> - <mapping directory="" vcs="Git" /> - <mapping directory="$PROJECT_DIR$/schoolapps/static/materialize" vcs="Git" /> + <mapping directory="$PROJECT_DIR$" vcs="Git" /> </component> </project> \ No newline at end of file diff --git a/README.md b/README.md index 6d6839f71a9e9ba7a749f973209ce1241f50aa18..9b8ad555b20dbe6c2d8e055f08cc1840cebffd74 100755 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ pip install django-material pip install django-filter pip install django_react_templatetags pip install kanboard +pip install PyPDF2 ``` - `example_secure_settings.py` zu `secure_settings.py` kopieren und anpassen @@ -67,6 +68,14 @@ python3 schoolapps/manage.py makemigrations python3 schoolapps/manage.py migrate ``` +### Kanboard-Verbindung einrichten +1. Zu den [Einstellungen](localhost:8000/settings) navigieren (/settings) +2. Den Kanboard-API-Key von [Kanboard](https://kanboard.katharineum.de/?controller=ConfigController&action) eintragen +3. Die Project-IDs von ``Rebus`` (#4) und ``Feedback`` (#18) eintragen. +4. Die richtigen E-Mailadressen eintragen. + +### Testlauf + ## LDAP (info.katharineum.de) **WICHTIG: LDAP funktioniert nur bei Root-Zugriff auf dem Infoserver!** @@ -91,13 +100,6 @@ dc=skole,dc=skolelinux,dc=no 3. Verbindung in AD mit oben genannten Daten herstellen -### Submodules updaten -``` -git submodule init -git submodule update -``` -## Kanboard-Verbindung einrichten -1. Zu den [Einstellungen](localhost:800/settings) navigieren (/settings) -2. Den Kanboard-API-Key von [Kanboard](https://kanboard.katharineum.de/?controller=ConfigController&action) eintragen -3. Die Project-IDs von ``Rebus`` (#4) und ``Feedback`` (#18) eintragen. \ No newline at end of file + + diff --git a/dynselect/src/App.js b/dynselect/src/App.js index 344a20782e350cc01b67107bdda65b9b09adecf7..1845297aebffc4602388f57b7b0e9981731f159c 100755 --- a/dynselect/src/App.js +++ b/dynselect/src/App.js @@ -230,7 +230,8 @@ Select.defaultProps = { class Input extends Component { render() { return <div - className={(this.props.show ? "" : "hide ") + "input-field col s4"}> + className = {(this.props.show ? "" : "hide ") + "input-field col s12 m12 l4" + }> <i className={"material-icons prefix"}>{this.props.icon}</i> {this.props.children} <label>{this.props.label}</label> @@ -322,7 +323,8 @@ class App extends Component { return ( <div className="App"> <div className={"row"}> - <div className="input-field col s4"> + < div + className = "input-field col s12 m12 l4" > <i className={"material-icons prefix"}>list</i> <select onChange={this._onCategoryChanges} defaultValue={"noCategory"}> <option value={"noCategory"} disabled={true}>Keine Kategorie ausgewählt</option> @@ -396,7 +398,8 @@ class App extends Component { {/* Section C – Device Issue */} <div - className={(sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s4"}> + className = {(sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s12 m12 l4" +}> <i className={"material-icons prefix"}>device_unknown</i> <input type={"text"} id={"valc"} onChange={this._onSetC} required={true}/> <label htmlFor="valc">Um welches Gerät handelt es sich?</label> diff --git a/dynselect2/src/App.js b/dynselect2/src/App.js index 8cb554c9a1989d90db26579fcb100b1460d851bb..b588dcd26248b1a802fe1cc168a02ce751b4ccee 100755 --- a/dynselect2/src/App.js +++ b/dynselect2/src/App.js @@ -1,5 +1,4 @@ import React, {Component} from 'react'; -// import "materialize-css/dist/css/materialize.css"; import M from "materialize-css/dist/js/materialize"; import PropTypes from "prop-types"; @@ -251,7 +250,7 @@ class Input extends Component { return < div - className = {(this.props.show ? "" : "hide ") + "input-field col s4" + className = {(this.props.show ? "" : "hide ") + "input-field col s12 m12 l4" }> < i @@ -376,7 +375,7 @@ render() < div className = {"row"} > < div - className = "input-field col s4" > + className = "input-field col s12 m12 l4" > < i className = {"material-icons prefix"} > list < /i> < select @@ -570,7 +569,7 @@ render() } < div - className = {(sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s4" + className = {(sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s12 m12 l4" }> < i diff --git a/schoolapps/aub/README.md b/schoolapps/aub/README.md index 5a64917de328ade272d75b471b2bb63c5575dab2..e4974d7a90e5a490cfe97802cfeca11d55c47cc6 100755 --- a/schoolapps/aub/README.md +++ b/schoolapps/aub/README.md @@ -11,7 +11,6 @@ - Beschreibung (TextField) - Antragsteller (Lehrerkürzel) wird automatisch aus angemeldetem Benutzer generiert - 2. Schulleiter erhält Antrag (Link per Mail) und a. bewilligt oder b. lehnt ab a. Stellvertreter prüft Antrag und i. bewilligt oder ii. formuliert Bedenken und lehnt ab diff --git a/schoolapps/aub/filters.py b/schoolapps/aub/filters.py index 5b354ecbfa6b231f7d7915472f475f36f6936561..cc5f5f0c2f8de9861e1c39ef23046b5b41e88764 100644 --- a/schoolapps/aub/filters.py +++ b/schoolapps/aub/filters.py @@ -1,14 +1,31 @@ from django import forms +from django.contrib.auth.models import User import django_filters -from .models import Aub +from .models import Aub, Status + class AUBFilter(django_filters.FilterSet): + def getAUBUsers(): + ''' Find all users who sends an AUB''' + aub_users = Aub.objects.values_list('created_by') + users = list(User.objects.filter(id__in=aub_users)) + user_ids = [(str(user.id),user.username) for user in users] + return user_ids + + def get_status_choices(): + status_values = list(Status.objects.values_list('name')) + status_ids = [(i+1,name[0]) for i,name in enumerate(status_values)] + return status_ids + #print('status_values:', get_status_choices()) + print('users', getAUBUsers()) #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')] - status = django_filters.ChoiceFilter(label='Status', choices=statuschoices, initial='In Bearbeitung 1') + created_by = django_filters.ChoiceFilter(label='Von', choices=getAUBUsers()) + #status_choices = [('1','In Bearbeitung 1'),('2','In Bearbeitung 2'),('3','Genehmigt'),('4','Abgelehnt')] + #status = django_filters.ChoiceFilter(label='Status', choices=get_status_choices(), initial='In Bearbeitung 1') class Meta: model = Aub - fields = [ 'created_by', 'status'] - ordering = 'status' \ No newline at end of file + fields = ['created_by',] + #fields = ['created_by', 'status'] + #ordering = 'status' + diff --git a/schoolapps/aub/forms.py b/schoolapps/aub/forms.py index b57663652d545570526998d40e0abcf803fda0f2..245e2619bd5b7d2afa9cec7906fd1c61fd0ea9cd 100755 --- a/schoolapps/aub/forms.py +++ b/schoolapps/aub/forms.py @@ -6,38 +6,16 @@ from material import Layout, Row, Fieldset 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')] - 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) - - layout = Layout(Fieldset('Filter', - Row('timerange', 'status', 'sorting'), - ) - ) - - def clean(self): - cleaned_data = super().clean() - - 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=lessons, required=False, - widget=forms.Select(attrs={'onchange': 'set_time(this)'})) + 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=initial_from_time, ) 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_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=initial_to_time) description = forms.CharField(label='Bitte begründen Sie Ihren Antrag.') @@ -84,83 +62,4 @@ class ApplyForAUBForm(forms.ModelForm): 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 + \ No newline at end of file diff --git a/schoolapps/aub/migrations/0009_auto_20190111_1525.py b/schoolapps/aub/migrations/0006_auto_20181219_1107.py similarity index 50% rename from schoolapps/aub/migrations/0009_auto_20190111_1525.py rename to schoolapps/aub/migrations/0006_auto_20181219_1107.py index f1a15739d40037c7b97f6a1986c22d30f9af9fea..2d7f31b40958a64aca6d96a9f65405a1bd784675 100644 --- a/schoolapps/aub/migrations/0009_auto_20190111_1525.py +++ b/schoolapps/aub/migrations/0006_auto_20181219_1107.py @@ -1,24 +1,24 @@ -# Generated by Django 2.0.7 on 2019-01-11 14:25 +# Generated by Django 2.0.4 on 2018-12-19 10:07 from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ('aub', '0008_auto_20190102_1732'), + ('aub', '0005_auto_20181128_1709'), ] operations = [ migrations.AlterField( model_name='aub', name='created_by', - field=models.ForeignKey(default=3, on_delete=models.SET(3), related_name='aubs', - to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(default=1, on_delete=models.SET(1), related_name='aubs', to=settings.AUTH_USER_MODEL), ), migrations.AlterField( model_name='aub', name='status', - field=models.ForeignKey(default=5, on_delete=models.SET(5), related_name='aubs', to='aub.Status'), + field=models.ForeignKey(default=7, on_delete=models.SET(7), related_name='aubs', to='aub.Status'), ), ] diff --git a/schoolapps/aub/migrations/0006_merge_20181226_1733.py b/schoolapps/aub/migrations/0006_merge_20181226_1733.py new file mode 100644 index 0000000000000000000000000000000000000000..3101e6c1b3185abbe082090363fb908af8ef7de3 --- /dev/null +++ b/schoolapps/aub/migrations/0006_merge_20181226_1733.py @@ -0,0 +1,14 @@ +# Generated by Django 2.1.2 on 2018-12-26 16:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0005_auto_20181128_1709'), + ('aub', '0003_auto_20181223_0905'), + ] + + operations = [ + ] 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/migrations/0007_auto_20190102_1724.py b/schoolapps/aub/migrations/0007_auto_20190102_1724.py deleted file mode 100644 index ac1604d1805f68150ddbcf16c6c82084b4eb3ac9..0000000000000000000000000000000000000000 --- a/schoolapps/aub/migrations/0007_auto_20190102_1724.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.1.4 on 2019-01-02 16:24 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('aub', '0006_merge_20190102_1724'), - ] - - operations = [ - migrations.RemoveField( - model_name='aub', - name='created_by', - ), - migrations.RemoveField( - model_name='aub', - name='status', - ), - ] diff --git a/schoolapps/aub/migrations/0007_merge_20190118_1452.py b/schoolapps/aub/migrations/0007_merge_20190118_1452.py new file mode 100644 index 0000000000000000000000000000000000000000..a84c346b218500dc897d8f8b9bbb4773a2de1382 --- /dev/null +++ b/schoolapps/aub/migrations/0007_merge_20190118_1452.py @@ -0,0 +1,14 @@ +# Generated by Django 2.0.4 on 2019-01-18 13:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0006_auto_20181219_1107'), + ('aub', '0006_merge_20190106_1714'), + ] + + operations = [ + ] diff --git a/schoolapps/aub/migrations/0007_merge_20190211_1935.py b/schoolapps/aub/migrations/0007_merge_20190211_1935.py new file mode 100644 index 0000000000000000000000000000000000000000..c4873066238b0da2c5bf041c5d890ed2a41347d0 --- /dev/null +++ b/schoolapps/aub/migrations/0007_merge_20190211_1935.py @@ -0,0 +1,14 @@ +# Generated by Django 2.1.2 on 2019-02-11 18:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0006_merge_20181226_1733'), + ('aub', '0006_merge_20190106_1714'), + ] + + operations = [ + ] diff --git a/schoolapps/aub/migrations/0008_auto_20190102_1732.py b/schoolapps/aub/migrations/0008_auto_20190211_1935.py similarity index 71% rename from schoolapps/aub/migrations/0008_auto_20190102_1732.py rename to schoolapps/aub/migrations/0008_auto_20190211_1935.py index bace54e021a21dd0a109e68ca6c1b11a1af46b32..23e7d4ec153bb2f703e8622ebbeb6de7d5d7cc3a 100644 --- a/schoolapps/aub/migrations/0008_auto_20190102_1732.py +++ b/schoolapps/aub/migrations/0008_auto_20190211_1935.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.4 on 2019-01-02 16:32 +# Generated by Django 2.1.2 on 2019-02-11 18:35 from django.conf import settings from django.db import migrations, models @@ -7,17 +7,16 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('aub', '0007_auto_20190102_1724'), + ('aub', '0007_merge_20190211_1935'), ] operations = [ - migrations.AddField( + migrations.AlterField( model_name='aub', name='created_by', field=models.ForeignKey(default=1, on_delete=models.SET(1), related_name='aubs', to=settings.AUTH_USER_MODEL), ), - migrations.AddField( + migrations.AlterField( model_name='aub', name='status', field=models.ForeignKey(default=1, on_delete=models.SET(1), related_name='aubs', to='aub.Status'), diff --git a/schoolapps/aub/migrations/0009_merge_20190222_1226.py b/schoolapps/aub/migrations/0009_merge_20190222_1226.py new file mode 100644 index 0000000000000000000000000000000000000000..9b9f654ef37a29b5e4c4c24973fd60ed69bd7f6d --- /dev/null +++ b/schoolapps/aub/migrations/0009_merge_20190222_1226.py @@ -0,0 +1,14 @@ +# Generated by Django 2.0.4 on 2019-02-22 11:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0008_auto_20190211_1935'), + ('aub', '0007_merge_20190118_1452'), + ] + + operations = [ + ] diff --git a/schoolapps/aub/migrations/0010_auto_20190304_1404.py b/schoolapps/aub/migrations/0010_auto_20190304_1404.py new file mode 100644 index 0000000000000000000000000000000000000000..ba6a55b8dff21d572939d460e49295b1f2f4b49d --- /dev/null +++ b/schoolapps/aub/migrations/0010_auto_20190304_1404.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.2 on 2019-03-04 13:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0009_merge_20190222_1226'), + ] + + operations = [ + migrations.AlterModelOptions( + name='aub', + options={'permissions': (('apply_for_aub', 'Apply for a AUB'), ('cancel_aub', 'Cancel a AUB'), ('allow1_aub', 'First permission'), ('allow2_aub', 'Second permission'), ('check1_aub', 'Check a AUB'), ('check2_aub', 'Check a AUB'), ('view_archive', 'View AUB archive'))}, + ), + migrations.AlterField( + model_name='aub', + name='status', + field=models.ForeignKey(default=1, on_delete=models.SET(1), related_name='aubs', to='aub.Status'), + ), + ] diff --git a/schoolapps/aub/migrations/0011_merge_20190318_2004.py b/schoolapps/aub/migrations/0011_merge_20190318_2004.py new file mode 100644 index 0000000000000000000000000000000000000000..d9ff941cf3a81a1ae5b23d5eac752320c485c664 --- /dev/null +++ b/schoolapps/aub/migrations/0011_merge_20190318_2004.py @@ -0,0 +1,14 @@ +# Generated by Django 2.1.5 on 2019-03-18 19:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('aub', '0010_auto_20190304_1404'), + ('aub', '0006_merge_20190102_1724'), + ] + + operations = [ + ] diff --git a/schoolapps/aub/models.py b/schoolapps/aub/models.py index da5f537e59413467a7ee173bde7f77c665019ce3..e2d196f3287f6eb114ddd19e6e56722bb96693a7 100755 --- a/schoolapps/aub/models.py +++ b/schoolapps/aub/models.py @@ -24,8 +24,6 @@ def get_default_status(): class Aub(models.Model): # Time -# 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) @@ -50,5 +48,6 @@ class Aub(models.Model): ('allow1_aub', 'First permission'), ('allow2_aub', 'Second permission'), ('check1_aub', 'Check a AUB'), - ('check2_aub', 'Check a AUB') + ('check2_aub', 'Check a AUB'), + ('view_archive', 'View AUB archive'), ) diff --git a/schoolapps/aub/templates/aub/apply_for.html b/schoolapps/aub/templates/aub/apply_for.html index 7781584e02473c74783aa5c1b2de58b2193c1d8b..1baa3d55e8ada3cbd50390acaf902816b794edf0 100755 --- a/schoolapps/aub/templates/aub/apply_for.html +++ b/schoolapps/aub/templates/aub/apply_for.html @@ -3,7 +3,7 @@ <main> - <h5>Antrag auf Unterrichtsbefreiung</h5> + <h4>Antrag auf Unterrichtsbefreiung</h4> <form method = "POST" > {% csrf_token %} diff --git a/schoolapps/aub/templates/aub/archive.html b/schoolapps/aub/templates/aub/archive.html new file mode 100755 index 0000000000000000000000000000000000000000..6688c3af4336934d7ffaa5eaf0e52f4cd18f600e --- /dev/null +++ b/schoolapps/aub/templates/aub/archive.html @@ -0,0 +1,65 @@ +{% include 'partials/header.html' %} + +<main> + <h4>Archiv</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 filter.qs %} + <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_date }}, {{ aub.from_time }} Uhr — {{ aub.to_date }}, {{ aub.to_time }} Uhr + </span> + </p> + <p><a href="{% url 'aub_details' aub.id %}">{{ aub.description }}</a></p> + </div> + <div class="col s12 m6"> + <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> + </div> + </li> + {% endfor %} + </ul> +</main> + +{% include 'partials/footer.html' %} diff --git a/schoolapps/aub/templates/aub/check.html b/schoolapps/aub/templates/aub/check.html index 3bba4070641c598729b7ee72d12963c185b4ad35..cf0c57424e0826aac3016722d18c7c6223c8c62c 100755 --- a/schoolapps/aub/templates/aub/check.html +++ b/schoolapps/aub/templates/aub/check.html @@ -1,66 +1,56 @@ {% 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> + <h4>Anträge genehmigen</h4> - <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"> + <ul class="collection"> + {% for aub in filter.qs %} + <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_date }} bis {{ aub.to_date }} + {{ aub.from_date }}, {{ aub.from_time }} Uhr — {{ aub.to_date }}, {{ aub.to_time }} Uhr </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"> + <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> + </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 5a41e31ec8c72ea57018a299984b5e91772a080d..01fbad0975cbdc6920b721bb42afbd87ed18f6cc 100755 --- a/schoolapps/aub/templates/aub/details.html +++ b/schoolapps/aub/templates/aub/details.html @@ -1,13 +1,14 @@ {% include 'partials/header.html' %} <main> -{# <h5>{{ aub.from_dt }} bis {{ aub.to_dt }}</h5>#} - <h5>{{ aub.from_date }} bis {{ aub.to_date }}</h5> - <p><strong>Status: <span class="badge new {{ aub.status.style_classes }}">{{ aub.status.name }}</span></strong></p> - <p>{{ aub.description }}</p> - <hr> <strong> Eingereicht am {{ aub.created_at.date }} von {{ aub.created_by.first_name }} {{ aub.created_by.last_name }} </strong> + <h5>{{ aub.from_date }}, {{ aub.from_time }} Uhr bis <br /> + {{ aub.to_date }}, {{ aub.to_time }} Uhr </h5> + <p><strong>Status: <span class="badge new {{ aub.status.style_classes }}">{{ aub.status.name }}</span></strong></p> + <p><strong>Beschreibung:</strong> {{ aub.description }}</p> + <hr> + </main> {% include 'partials/footer.html' %} \ No newline at end of file diff --git a/schoolapps/aub/templates/aub/edit.html b/schoolapps/aub/templates/aub/edit.html index 1e0d8c2514f5d7c52fc30e1ee348b9a75e08f820..3b07e767ddc99f428af5eb3268eb30574856880e 100755 --- a/schoolapps/aub/templates/aub/edit.html +++ b/schoolapps/aub/templates/aub/edit.html @@ -3,7 +3,7 @@ <main> - <h5>Antrag auf Unterrichtsbefreiung</h5> + <h4>Antrag auf Unterrichtsbefreiung</h4> <form method = "POST" > {% csrf_token %} @@ -11,7 +11,7 @@ {% form form=form %} {% endform %} <button type="submit" class="waves-effect waves-light btn green"> - <i class="material-icons left">send</i> Antrag stellen + <i class="material-icons left">send</i> Antrag ändern </button> </form> </main> diff --git a/schoolapps/aub/templates/aub/index.html b/schoolapps/aub/templates/aub/index.html index ce70981402874e8855b449b37a6169489ac4634e..87199d4606e7ec955352756d2b8cf1aeba9ee2ac 100755 --- a/schoolapps/aub/templates/aub/index.html +++ b/schoolapps/aub/templates/aub/index.html @@ -7,7 +7,7 @@ {% block content %} - <h5>Ãœbersicht der Anträge</h5> + <h4>Unterrichtsbefreiungen von <span class="item-text"><i class="material-icons">person</i>{{ user }}</span></h4> <ul class="collection"> {% for aub in aubs %} <li class="collection-item"> @@ -17,8 +17,7 @@ <p class="title"> <span class="item-text"> <i class="material-icons">access_time</i> -{# {{ aub.from_dt }} bis {{ aub.to_dt }}#} - {{ aub.from_date }}, {{ aub.from_time }} bis {{ aub.to_date }}, {{ aub.to_time }} + {{ aub.from_date }}, {{ aub.from_time }} Uhr — {{ aub.to_date }}, {{ aub.to_time }} Uhr </span> </p> <p><a href="{% url 'aub_details' aub.id %}">{{ aub.description }}</a></p> @@ -36,7 +35,6 @@ {% if aub.status_id == 1 %} <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> @@ -46,7 +44,6 @@ <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> diff --git a/schoolapps/aub/urls.py b/schoolapps/aub/urls.py index a1584e0aeac905e70e1ac1e89308ad937700c890..137e11d4c0b082a86d689ab5d7066523c7f87d67 100755 --- a/schoolapps/aub/urls.py +++ b/schoolapps/aub/urls.py @@ -10,4 +10,5 @@ urlpatterns = [ path('applied_for', views.applied_for, name='aub_applied_for'), path('check1', views.check1, name='aub_check1'), path('check2', views.check2, name='aub_check2'), + path('archive', views.archive, name='aub_archive'), ] diff --git a/schoolapps/aub/views.py b/schoolapps/aub/views.py index c0fe2e7ae5b7ce9b2c56703ff605be4ab4d63aee..500fde3f3762302400a00e40dfe859e0db518ab5 100755 --- a/schoolapps/aub/views.py +++ b/schoolapps/aub/views.py @@ -1,6 +1,8 @@ from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.models import User from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse +from django.db.models import Q from django.utils import timezone from django.utils import formats from datetime import date @@ -9,7 +11,7 @@ from django.core.exceptions import ValidationError from .apps import AubConfig from dashboard.models import Activity, register_notification -from .forms import FilterAUBForm, ApplyForAUBForm +from .forms import ApplyForAUBForm from .models import Aub, Status from .filters import AUBFilter from .decorators import check_own_aub @@ -19,32 +21,32 @@ 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): + aub_user = request.user 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() + aub_id = request.POST['aub-id'] + if 'cancel' in request.POST: + aub = Aub.objects.get(id=aub_id) + aub.delete() + a = Activity(user=aub_user, title="Antrag auf Unterrichtsbefreiung gelöscht", + description="Sie haben Ihren Antrag auf Unterrichtsbefreiung " + + "für den Zeitraum von {} bis {} gelöscht.".format( + aub.from_date, aub.to_date), app=AubConfig.verbose_name) + a.save() 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] + order_crit = '-from_date' + aubs = Aub.objects.filter(created_by=aub_user).order_by(order_crit)[:100] context = { - 'aubs': aubs + 'aubs': aubs, + 'user': aub_user, } return render(request, 'aub/index.html', context) + @login_required @permission_required('aub.apply_for_aub') @check_own_aub(login_url='/index.html') @@ -55,33 +57,29 @@ def details(request, id): } return render(request, 'aub/details.html', context) + @login_required @permission_required('aub.apply_for_aub') def apply_for(request): if request.method == '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) + aub_id = request.POST['aub-id'] + aub = Aub.objects.get(id=aub_id) + form = ApplyForAUBForm(instance=aub) + print('Edit-Form erstellt ############# form.is_valid:', form.is_valid()) 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'] - 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 = 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() a = Activity(user=request.user, title="Antrag auf Unterrichtsbefreiung gestellt", @@ -92,106 +90,6 @@ def apply_for(request): return redirect('aub_applied_for') return render(request, 'aub/apply_for.html', {'form': form}) -# # 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}) - - # 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 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') @@ -203,12 +101,18 @@ def edit(request, id): form = ApplyForAUBForm(request.POST, instance=aub) if form.is_valid(): form.save() + a = Activity(user=request.user, title="Antrag auf Unterrichtsbefreiung verändert", + description="Sie haben Ihren Antrag auf Unterrichtsbefreiung " + + "für den Zeitraum von {} bis {} bearbeitet.".format( + aub.from_date, aub.to_date), app=AubConfig.verbose_name) + a.save() return redirect(reverse('aub_applied_for')) context = { 'form': form } return render(request, template, context) + @login_required @permission_required('aub.apply_for_aub') def applied_for(request): @@ -224,13 +128,26 @@ def applied_for(request): def check1(request): if request.method == 'POST': if 'aub-id' in request.POST: - id = request.POST['aub-id'] + aub_id = request.POST['aub-id'] + aub = Aub.objects.get(id=aub_id) if 'allow' in request.POST: - Aub.objects.filter(id=id).update(status=SEMI_ALLOWED_STATUS) + Aub.objects.filter(id=aub_id).update(status=SEMI_ALLOWED_STATUS) elif 'deny' in request.POST: - Aub.objects.filter(id=id).update(status=NOT_ALLOWED_STATUS) + Aub.objects.filter(id=aub_id).update(status=NOT_ALLOWED_STATUS) + # Notify user + register_notification(title="Ihr Antrag auf Unterrichtsbefreiung wurde abgelehnt", + description="Ihr Antrag auf Unterrichtsbefreiung vom {}, {} Uhr bis {}, {} Uhr wurde von der " + "Schulleitung abgelehnt. Für weitere Informationen kontaktieren Sie " + "bitte die Schulleitung." + .format(formats.date_format(aub.from_date), + formats.time_format(aub.from_time), + formats.date_format(aub.to_date), + formats.time_format(aub.to_time)), + 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') + aub_list = Aub.objects.filter(status=IN_PROCESSING_STATUS).order_by('created_at') aubs = AUBFilter(request.GET, queryset=aub_list) return render(request, 'aub/check.html', {'filter': aubs}) @@ -240,39 +157,53 @@ def check1(request): def check2(request): if request.method == 'POST': if 'aub-id' in request.POST: - id = request.POST['aub-id'] - aub = Aub.objects.get(id=id) + aub_id = request.POST['aub-id'] + aub = Aub.objects.get(id=aub_id) if 'allow' in request.POST: # Update status - Aub.objects.filter(id=id).update(status=ALLOWED_STATUS) + Aub.objects.filter(id=aub_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_date), - formats.date_format(aub.to_date)), + description="Ihr Antrag auf Unterrichtsbefreiung vom {}, {} Uhr bis {}, {} Uhr wurde von der " + "Schulleitung genehmigt." + .format(formats.date_format(aub.from_date), + formats.time_format(aub.from_time), + formats.date_format(aub.to_date), + formats.time_format(aub.to_time)), app=AubConfig.verbose_name, user=aub.created_by, - link=request.build_absolute_uri(reverse('aub_details', args=[aub.id]))) + link=request.build_absolute_uri(reverse('aub_details', args=[aub.id])) + ) elif 'deny' in request.POST: # Update status - Aub.objects.filter(id=id).update(status=NOT_ALLOWED_STATUS) + Aub.objects.filter(id=aub_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 " + description="Ihr Antrag auf Unterrichtsbefreiung vom {}, {} Uhr bis {}, {} Uhr 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_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]))) + "bitte die Schulleitung." + .format(formats.date_format(aub.from_date), + formats.time_format(aub.from_time), + formats.date_format(aub.to_date), + formats.time_format(aub.to_time)), + 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') + aub_list = Aub.objects.filter(status=SEMI_ALLOWED_STATUS).order_by('created_at') aubs = AUBFilter(request.GET, queryset=aub_list) - return render(request, 'aub/check.html', {'filter': aubs}) + + +@login_required +@permission_required('aub.view_archive') +def archive(request): + order_crit = '-from_date' + if 'created_by' in request.GET: + item = int(request.GET['created_by']) + aub_list = Aub.objects.filter((Q(status__exact=ALLOWED_STATUS) | Q(status__exact=NOT_ALLOWED_STATUS)) & Q(created_by=item)).order_by(order_crit) + else: + aub_list = Aub.objects.filter(Q(status__exact=ALLOWED_STATUS) | Q(status__exact=NOT_ALLOWED_STATUS)).order_by(order_crit) + aub_filter = AUBFilter(request.GET, queryset=aub_list) + return render(request, 'aub/archive.html', {'filter': aub_filter}) diff --git a/schoolapps/dashboard/migrations/0003_auto_20190102_1732.py b/schoolapps/dashboard/migrations/0003_auto_20190102_1732.py deleted file mode 100644 index b9a570233b37b89e033cad558614044ffa931201..0000000000000000000000000000000000000000 --- a/schoolapps/dashboard/migrations/0003_auto_20190102_1732.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 2.1.4 on 2019-01-02 16:32 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dashboard', '0002_auto_20190102_1724'), - ] - - operations = [ - migrations.AddField( - model_name='activity', - name='user', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='notification', - name='user', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/schoolapps/dashboard/models.py b/schoolapps/dashboard/models.py index 5125e2fc9990b5a5c7ab4a60459241e6a54e1345..07630ea6c702c64dbfe20a0f9f4ae2980e09e8ff 100755 --- a/schoolapps/dashboard/models.py +++ b/schoolapps/dashboard/models.py @@ -1,3 +1,4 @@ +import dbsettings from django.db import models from django.contrib.auth.models import User from django.utils import timezone diff --git a/schoolapps/dashboard/urls.py b/schoolapps/dashboard/urls.py index d578b620774c6936f54dfada3d23b8a416dc6dff..36bc9f13e3e63551e1799c3413ba3caab8b2633f 100755 --- a/schoolapps/dashboard/urls.py +++ b/schoolapps/dashboard/urls.py @@ -4,6 +4,5 @@ from . import views urlpatterns = [ path('', views.index, name='dashboard'), path('test/', views.test_notification, name='test'), - path('impress/', views.impress, name='impress') ] diff --git a/schoolapps/dashboard/views.py b/schoolapps/dashboard/views.py index ee8eca52599e309b476564d1c1ff23edc5232cee..70996fff84b8f4beb7befeaac2f22d0577f6268c 100755 --- a/schoolapps/dashboard/views.py +++ b/schoolapps/dashboard/views.py @@ -52,9 +52,5 @@ def test_notification(request): return redirect(reverse('dashboard')) -def impress(request): - return render(request, 'common/impress.html') - - def error_404(request, exception): return render(request, 'common/404.html') diff --git a/schoolapps/doc/__init__.py b/schoolapps/doc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/schoolapps/doc/admin.py b/schoolapps/doc/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/schoolapps/doc/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/schoolapps/doc/apps.py b/schoolapps/doc/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..1c79f22e3c332864a6c60822280d4e5400b1fe0b --- /dev/null +++ b/schoolapps/doc/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DocConfig(AppConfig): + name = 'doc' diff --git a/schoolapps/doc/migrations/0002_auto_20190106_0905.py b/schoolapps/doc/migrations/0002_auto_20190106_0905.py new file mode 100644 index 0000000000000000000000000000000000000000..b1cae31961ab9646d73de5ade1737cd6128088b4 --- /dev/null +++ b/schoolapps/doc/migrations/0002_auto_20190106_0905.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.2 on 2019-01-06 08:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('doc', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='subject', + name='abbreviation', + field=models.CharField(max_length=5), + ), + ] diff --git a/schoolapps/doc/migrations/0003_auto_20190108_1626.py b/schoolapps/doc/migrations/0003_auto_20190108_1626.py new file mode 100644 index 0000000000000000000000000000000000000000..89de0be627df14ca1974eec05ba73d4bb799ceb3 --- /dev/null +++ b/schoolapps/doc/migrations/0003_auto_20190108_1626.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.2 on 2019-01-08 15:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('doc', '0002_auto_20190106_0905'), + ] + + operations = [ + migrations.AlterField( + model_name='subject', + name='abbreviation', + field=models.CharField(max_length=5, unique=True), + ), + ] diff --git a/schoolapps/doc/migrations/__init__.py b/schoolapps/doc/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/schoolapps/doc/models.py b/schoolapps/doc/models.py new file mode 100644 index 0000000000000000000000000000000000000000..a7e425d408c0e0669ba9f6c336ed809b77049b52 --- /dev/null +++ b/schoolapps/doc/models.py @@ -0,0 +1,54 @@ +from django.db import models + +# Create your models here. +class Teacher(models.Model): + abbreviation = models.CharField(max_length=5, primary_key=True, unique=True) + firstname = models.CharField(max_length=20) + lastname = models.CharField(max_length=40) + + +class Certificate(models.Model): + teacher = models.ForeignKey(to=Teacher.abbreviation, on_delete=models.CASCADE) + subject = models.ForeignKey(to=Subject.abbreviation, on_delete=models.CASCADE) + + +class WorkingTime(models.Model): + teacher = models.ForeignKey(to=Teacher.abbreviation) + term = models.ForeignKey('Term', on_delete=models.CASCADE) + debit = models.DecimalField(max_digits=5, decimal_places=1) + + + +class Subject(models.Model): + abbreviation = models.CharField(max_length=5, primary_key=True, unique=True) + name = models.CharField(max_length=30) + + +class SubjectTitle(models.Model): + subject_title = models.CharField(max_length=6, primary_key=True, unique=True) + abbreviation = models.ForeignKey(to=Subject.abbreviation, on_delete=models.CASCADE) + + +class Course(models.Model): + grade = models.CharField(max_length=6) + group = models.CharField(max_length=6) + subject_title = models.ForeignKey(to=SubjectTitle.subject_title, on_delete=models.CASCADE) + teacher = models.ForeignKey(to=Teacher.abbreviation, on_delete=models.CASCADE) + + +class NoLessons(models.Model): + day = models.DateField() + reason = models.CharField(max_length=50) + + +class Schoolyear(models.Model): + begin = models.DateField() + end = models.DateField() + + +class Term(models.Model): + begin = models.DateField() + end = models.DateField() + + + diff --git a/schoolapps/timetable/tests.py b/schoolapps/doc/tests.py old mode 100755 new mode 100644 similarity index 100% rename from schoolapps/timetable/tests.py rename to schoolapps/doc/tests.py diff --git a/schoolapps/doc/views.py b/schoolapps/doc/views.py new file mode 100644 index 0000000000000000000000000000000000000000..91ea44a218fbd2f408430959283f0419c921093e --- /dev/null +++ b/schoolapps/doc/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/schoolapps/menu/views.py b/schoolapps/menu/views.py index b56d6ff054fc31fe8deeeaeed2784061a2121fad..fc2bfe2452b2ce86bd5da0b82d18aecfb82c298b 100644 --- a/schoolapps/menu/views.py +++ b/schoolapps/menu/views.py @@ -7,6 +7,7 @@ from django.shortcuts import render, redirect from django.utils import timezone from menu.models import Menu +from schoolapps.settings import BASE_DIR from .forms import MenuUploadForm @@ -29,7 +30,7 @@ def upload(request): @login_required @permission_required("menu.add_menu") def delete(request, id): - print(id) + # print(id) Menu.objects.get(id=id).delete() return redirect("menu_index_msg", msg="delete_success") @@ -52,7 +53,7 @@ def return_pdf(filename): def return_default_pdf(): """Response the default PDF""" - return return_pdf(os.path.join("menu", "default.pdf")) + return return_pdf(os.path.join(BASE_DIR, "menu", "default.pdf")) def show_current(request): @@ -76,7 +77,7 @@ def show_current(request): # Look for matching PDF in DB try: obj = Menu.objects.get(year=year, calendar_week=calendar_week) - return return_pdf(os.path.join("media", str(obj.pdf))) + return return_pdf(os.path.join(BASE_DIR, "media", str(obj.pdf))) # Or show the default PDF except Menu.DoesNotExist: diff --git a/schoolapps/schoolapps/settings.py b/schoolapps/schoolapps/settings.py index 3b8464fcc2de066ff365491f0f88b7aa88842924..f96ac682aac6542372deff901714a0a5ff69fcc6 100755 --- a/schoolapps/schoolapps/settings.py +++ b/schoolapps/schoolapps/settings.py @@ -166,6 +166,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/schoolapps/urls.py b/schoolapps/schoolapps/urls.py index cfc33cd05d6ecc65216e73a2b5a1ce76c1e52a81..2ca9b41a73e8087898a6012a388d4f7dabeb7f55 100755 --- a/schoolapps/schoolapps/urls.py +++ b/schoolapps/schoolapps/urls.py @@ -13,12 +13,26 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +import os + from django.conf.urls import include from django.contrib import admin +from django.contrib.staticfiles.views import serve from django.urls import path from django.conf.urls.static import static from django.conf import settings +from schoolapps.settings import BASE_DIR + + +def manifest(request): + return serve(request, "manifest.json") + + +def serviceworker(request): + return serve(request, "common/pwabuilder-sw.js") + + urlpatterns = [ ############# # Dashboard # @@ -55,6 +69,7 @@ urlpatterns = [ # SUPPORT # ########### path('support/', include('support.urls')), + path("pwabuilder-sw.js", serviceworker) ] urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/schoolapps/static/common/helper.js b/schoolapps/static/common/helper.js index c803c0a883c717b833060628326111e5bef26cb2..b730f98181d6b33c51173f72d6404fc613c0aa15 100644 --- a/schoolapps/static/common/helper.js +++ b/schoolapps/static/common/helper.js @@ -53,6 +53,10 @@ function setTime(lesson_field) { } $(document).ready(function () { + $("dmc-datetime input").addClass("datepicker"); + $("[data-form-control='date']").addClass("datepicker"); + $("[data-form-control='time']").addClass("timepicker"); + // Initialize sidenav [MAT] $(".sidenav").sidenav(); @@ -90,6 +94,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/manifest.json b/schoolapps/static/common/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..7c02932d13f0e6db46ca45355312adb23f72b5f2 --- /dev/null +++ b/schoolapps/static/common/manifest.json @@ -0,0 +1,111 @@ +{ + "dir": "ltr", + "lang": "de", + "name": "SchoolApps", + "scope": "", + "display": "standalone", + "start_url": "https://info.katharineum.de/", + "short_name": "SchoolApps", + "theme_color": "#da1f3d", + "description": "", + "background_color": "#ffffff", + "related_applications": [], + "prefer_related_applications": false, + "icons": [ + { + "src": "/static/images/86844799-71f8-10c1-e7d7-796910f44e77.webPlatform.png", + "sizes": "44x44", + "type": "image/png" + }, + { + "src": "/static/images/532d1325-f176-0008-3964-343a1bcd5935.webPlatform.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "/static/images/04f528db-0b01-8c14-7762-7a69810d1265.webPlatform.png", + "sizes": "1240x600", + "type": "image/png" + }, + { + "src": "/static/images/e677beba-e9e4-3000-bca6-c8539dbf74e4.webPlatform.png", + "sizes": "300x300", + "type": "image/png" + }, + { + "src": "/static/images/aa11bd3d-260b-c276-1c09-b767ac224f2e.webPlatform.png", + "sizes": "150x150", + "type": "image/png" + }, + { + "src": "/static/images/8dde8463-278d-5c78-b2f6-f342b026e965.webPlatform.png", + "sizes": "88x88", + "type": "image/png" + }, + { + "src": "/static/images/5d6e255c-1240-2653-ec44-0ea01bff39e6.webPlatform.png", + "sizes": "24x24", + "type": "image/png" + }, + { + "src": "/static/images/9bba1bae-17fb-2975-9edf-a0a90f3a54eb.webPlatform.png", + "sizes": "50x50", + "type": "image/png" + }, + { + "src": "/static/images/d61086e1-4929-90e8-088a-129e4d16b0fa.webPlatform.png", + "sizes": "620x300", + "type": "image/png" + }, + { + "src": "/static/images/6f985a9c-e48a-0035-52b5-4c4b28b8c16d.webPlatform.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/static/images/eaf673aa-e19a-74b4-33c0-d38154cd1b18.webPlatform.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/static/images/ffdd263d-816e-c2ef-5978-2377f85c42b9.webPlatform.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/static/images/5e034d01-f259-28a7-7cad-cda2b2758636.webPlatform.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/static/images/33f6c523-376b-7ee2-4847-cb0e750c3642.webPlatform.png", + "sizes": "36x36", + "type": "image/png" + }, + { + "src": "/static/images/09ec9d1a-7f1a-25e5-97b3-96b90712cc60.webPlatform.png", + "sizes": "1024x1024", + "type": "image/png" + }, + { + "src": "/static/images/061e9652-06e6-8446-46e3-819674db9979.webPlatform.png", + "sizes": "180x180", + "type": "image/png" + }, + { + "src": "/static/images/982059a6-7527-5086-5334-b0252e562f97.webPlatform.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "/static/images/2d819cff-1e82-fcfb-e85a-6090d6b472eb.webPlatform.png", + "sizes": "120x120", + "type": "image/png" + }, + { + "src": "/static/images/8fc2ae6f-6af5-d172-da72-af62f93bc5c0.webPlatform.png", + "sizes": "76x76", + "type": "image/png" + } + ] +} diff --git a/schoolapps/static/common/manup.min.js b/schoolapps/static/common/manup.min.js new file mode 100644 index 0000000000000000000000000000000000000000..1805322c273d0f6055aaad75e6e1f5ebed33b115 --- /dev/null +++ b/schoolapps/static/common/manup.min.js @@ -0,0 +1 @@ +var manUpObject,tagArray=[],linkArray=[],validMetaValues=[{name:"mobile-web-app-capable",manifestName:"display"},{name:"apple-mobile-web-app-capable",manifestName:"display"},{name:"apple-mobile-web-app-title",manifestName:"short_name"},{name:"application-name",manifestName:"short_name"},{name:"msapplication-TileColor",manifestName:"ms_TileColor"},{name:"msapplication-square70x70logo",manifestName:"icons",imageSize:"70x70"},{name:"msapplication-square150x150logo",manifestName:"icons",imageSize:"150x150"},{name:"msapplication-wide310x150logo",manifestName:"icons",imageSize:"310x150"},{name:"msapplication-square310x310logo",manifestName:"icons",imageSize:"310x310"}],validLinkValues=[{name:"apple-touch-icon",manifestName:"icons",imageSize:"152x152"},{name:"apple-touch-icon",manifestName:"icons",imageSize:"120x120"},{name:"apple-touch-icon",manifestName:"icons",imageSize:"76x76"},{name:"apple-touch-icon",manifestName:"icons",imageSize:"60x60"},{name:"apple-touch-icon",manifestName:"icons",imageSize:"57x57"},{name:"apple-touch-icon",manifestName:"icons",imageSize:"72x72"},{name:"apple-touch-icon",manifestName:"icons",imageSize:"114x114"},{name:"icon",manifestName:"icons",imageSize:"128x128"},{name:"icon",manifestName:"icons",imageSize:"192x192"}],generateFullMetaData=function(){for(var e=0;e<validMetaValues.length;e++)if(manUpObject[validMetaValues[e].manifestName])if("icons"==validMetaValues[e].manifestName)for(var a=manUpObject.icons,n=0;n<a.length;n++)a[n].sizes==validMetaValues[e].imageSize&&(validMetaValues[e].content=a[n].src);else validMetaValues[e].content=manUpObject[validMetaValues[e].manifestName],"display"==validMetaValues[e].manifestName&&"standalone"==manUpObject.display&&(validMetaValues[e].content="yes");return validMetaValues},generateFullLinkData=function(){for(var e=0;e<validLinkValues.length;e++)if(manUpObject[validLinkValues[e].manifestName])if("icons"==validLinkValues[e].manifestName)for(var a=manUpObject.icons,n=0;n<a.length;n++)a[n].sizes==validLinkValues[e].imageSize&&(validLinkValues[e].content=a[n].src);else validLinkValues[e].content=manUpObject[validLinkValues[e].manifestName];return validLinkValues},generateMetaArray=function(){for(var e=generateFullMetaData(),a=document.getElementsByTagName("head")[0],n=0;n<e.length;n++){var i=document.createElement("meta");i.name=e[n].name,i.content=e[n].content,a.appendChild(i)}},generateLinkArray=function(){for(var e=generateFullLinkData(),a=document.getElementsByTagName("head")[0],n=0;n<e.length;n++){var i=document.createElement("link");i.setAttribute("rel",e[n].name),i.setAttribute("sizes",e[n].imageSize),i.setAttribute("href",e[n].content),a.appendChild(i)}},generateObj=function(e){manUpObject=JSON.parse(e),generateLinkArray(),generateMetaArray()},makeAjax=function(e){if(window.XMLHttpRequest){var a,n=/^https?:\/\//i;n.test(e)?fulURL=e:a=window.location.hostname+e;var i=new XMLHttpRequest;i.onreadystatechange=function(){4==i.readyState&&200==i.status&&generateObj(i.responseText)},i.open("GET",e,!0),i.send()}},collectManifestObj=function(){for(var e=document.getElementsByTagName("link"),a=0;a<e.length;a++)e[a].rel&&"manifest"==e[a].rel&&makeAjax(e[a].href)},testForManifest=function(){collectManifestObj()}(); \ No newline at end of file diff --git a/schoolapps/static/common/pwabuilder-sw-register.js b/schoolapps/static/common/pwabuilder-sw-register.js new file mode 100644 index 0000000000000000000000000000000000000000..3fba63945c18c6c4afc7808dab663b63d83cbda0 --- /dev/null +++ b/schoolapps/static/common/pwabuilder-sw-register.js @@ -0,0 +1,14 @@ +//This is the "Offline copy of pages" service worker + +//Add this below content to your HTML page, or add the js file to your page at the very top to register service worker +if (navigator.serviceWorker.controller) { + console.log('[PWA Builder] active service worker found, no need to register') +} else { + //Register the ServiceWorker + navigator.serviceWorker.register('pwabuilder-sw.js', { + scope: './' + }).then(function (reg) { + console.log('Service worker has been registered for scope:' + reg.scope); + }); +} + diff --git a/schoolapps/static/common/pwabuilder-sw.js b/schoolapps/static/common/pwabuilder-sw.js new file mode 100644 index 0000000000000000000000000000000000000000..18ddd2d0aaa8f9e22a3010136987c53c66a1f642 --- /dev/null +++ b/schoolapps/static/common/pwabuilder-sw.js @@ -0,0 +1,43 @@ +//This is the "Offline copy of pages" service worker + +//Install stage sets up the index page (home page) in the cache and opens a new cache +self.addEventListener('install', function (event) { + var indexPage = new Request('/'); + event.waitUntil( + fetch(indexPage).then(function (response) { + return caches.open('pwabuilder-offline').then(function (cache) { + console.log('[PWA Builder] Cached index page during Install' + response.url); + return cache.put(indexPage, response); + }); + })); +}); + +//If any fetch fails, it will look for the request in the cache and serve it from there first +self.addEventListener('fetch', function (event) { + var updateCache = function (request) { + return caches.open('pwabuilder-offline').then(function (cache) { + return fetch(request).then(function (response) { + console.log('[PWA Builder] add page to offline' + response.url) + return cache.put(request, response); + }); + }); + }; + + event.waitUntil(updateCache(event.request)); + + event.respondWith( + fetch(event.request).catch(function (error) { + console.log('[PWA Builder] Network request Failed. Serving content from cache: ' + error); + + //Check to see if you have it in the cache + //Return response + //If not in the cache, then return error page + return caches.open('pwabuilder-offline').then(function (cache) { + return cache.match(event.request).then(function (matching) { + var report = !matching || matching.status === 404 ? Promise.reject('no-match') : matching; + return report + }); + }); + }) + ); +}) diff --git a/schoolapps/static/common/style.css b/schoolapps/static/common/style.css index c3ff52480690bc3af2373f3d42a0b03780bf8af9..8d0691cf52315f3165236910d2faabdeafdfa2b3 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; } /**********/ @@ -29,7 +29,7 @@ body { /********/ main { - padding: 20px; + padding: 10px 20px; flex: 1 0 auto; } @@ -82,12 +82,27 @@ 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,7 +146,8 @@ span.badge.new::after { } .lesson-card .card-content div { - height: 100%; + padding: 3px; + flex: auto; width: 100%; display: flex; align-items: center; @@ -148,6 +164,19 @@ 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; +} + +.lesson-card a { + color: black; +} + /*.timetable-time {*/ /*margin-right: 20px;*/ /*}*/ @@ -179,6 +208,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); } @@ -245,6 +280,27 @@ table.striped > tbody > tr:nth-child(odd) { } } +.alert p { + margin: 10px; + padding: 10px; + border-left: 5px solid; +} + +.alert.success p { + background-color: #c5e1a5; + border-color: #4caf50; +} + +.alert.error p { + background-color: #ef9a9a; + border-color: #b71c1c; +} + +main .alert p { + margin-left: -10px; + margin-right: -10px; +} + /*++++++++++++++++ FEEDBACK ++++++++++++++++*/ @@ -300,3 +356,4 @@ FEEDBACK width: 24px; vertical-align: middle; } + diff --git a/schoolapps/static/images/04f528db-0b01-8c14-7762-7a69810d1265.webPlatform.png b/schoolapps/static/images/04f528db-0b01-8c14-7762-7a69810d1265.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..5fdc2bedb0feebc69f2b98992fc4ca1ae6bdcc62 Binary files /dev/null and b/schoolapps/static/images/04f528db-0b01-8c14-7762-7a69810d1265.webPlatform.png differ diff --git a/schoolapps/static/images/061e9652-06e6-8446-46e3-819674db9979.webPlatform.png b/schoolapps/static/images/061e9652-06e6-8446-46e3-819674db9979.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..6ec6a6ffa2055d6f9de91973e896acfcfc442158 Binary files /dev/null and b/schoolapps/static/images/061e9652-06e6-8446-46e3-819674db9979.webPlatform.png differ diff --git a/schoolapps/static/images/09ec9d1a-7f1a-25e5-97b3-96b90712cc60.webPlatform.png b/schoolapps/static/images/09ec9d1a-7f1a-25e5-97b3-96b90712cc60.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..ae0aae6bbbe7bb075a3d484618876a2d4045a079 Binary files /dev/null and b/schoolapps/static/images/09ec9d1a-7f1a-25e5-97b3-96b90712cc60.webPlatform.png differ diff --git a/schoolapps/static/images/2d819cff-1e82-fcfb-e85a-6090d6b472eb.webPlatform.png b/schoolapps/static/images/2d819cff-1e82-fcfb-e85a-6090d6b472eb.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..1504d5df5b5e5bbb2ed4b956ccc014dbddb0021b Binary files /dev/null and b/schoolapps/static/images/2d819cff-1e82-fcfb-e85a-6090d6b472eb.webPlatform.png differ diff --git a/schoolapps/static/images/33f6c523-376b-7ee2-4847-cb0e750c3642.webPlatform.png b/schoolapps/static/images/33f6c523-376b-7ee2-4847-cb0e750c3642.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..9438927139720f8cb62c5b554bcfc82196cf5b56 Binary files /dev/null and b/schoolapps/static/images/33f6c523-376b-7ee2-4847-cb0e750c3642.webPlatform.png differ diff --git a/schoolapps/static/images/532d1325-f176-0008-3964-343a1bcd5935.webPlatform.png b/schoolapps/static/images/532d1325-f176-0008-3964-343a1bcd5935.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..45f297cdbaff95d08f776aa809a16b19d8a67728 Binary files /dev/null and b/schoolapps/static/images/532d1325-f176-0008-3964-343a1bcd5935.webPlatform.png differ diff --git a/schoolapps/static/images/5d6e255c-1240-2653-ec44-0ea01bff39e6.webPlatform.png b/schoolapps/static/images/5d6e255c-1240-2653-ec44-0ea01bff39e6.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..4f941ec1a92616c33445d5dab6937410a4455cad Binary files /dev/null and b/schoolapps/static/images/5d6e255c-1240-2653-ec44-0ea01bff39e6.webPlatform.png differ diff --git a/schoolapps/static/images/5e034d01-f259-28a7-7cad-cda2b2758636.webPlatform.png b/schoolapps/static/images/5e034d01-f259-28a7-7cad-cda2b2758636.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..dddf7371ba8f03464e98acbfd521549d4f3027e9 Binary files /dev/null and b/schoolapps/static/images/5e034d01-f259-28a7-7cad-cda2b2758636.webPlatform.png differ diff --git a/schoolapps/static/images/6f985a9c-e48a-0035-52b5-4c4b28b8c16d.webPlatform.png b/schoolapps/static/images/6f985a9c-e48a-0035-52b5-4c4b28b8c16d.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..66f858f3ae486fa52fad800005956830c2948002 Binary files /dev/null and b/schoolapps/static/images/6f985a9c-e48a-0035-52b5-4c4b28b8c16d.webPlatform.png differ diff --git a/schoolapps/static/images/86844799-71f8-10c1-e7d7-796910f44e77.webPlatform.png b/schoolapps/static/images/86844799-71f8-10c1-e7d7-796910f44e77.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..ed3bf0ecd7bf159a55b5b5c542f69e79bf658555 Binary files /dev/null and b/schoolapps/static/images/86844799-71f8-10c1-e7d7-796910f44e77.webPlatform.png differ diff --git a/schoolapps/static/images/8dde8463-278d-5c78-b2f6-f342b026e965.webPlatform.png b/schoolapps/static/images/8dde8463-278d-5c78-b2f6-f342b026e965.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..8c1a9257b31d06f141965bb657292cf706d27056 Binary files /dev/null and b/schoolapps/static/images/8dde8463-278d-5c78-b2f6-f342b026e965.webPlatform.png differ diff --git a/schoolapps/static/images/8fc2ae6f-6af5-d172-da72-af62f93bc5c0.webPlatform.png b/schoolapps/static/images/8fc2ae6f-6af5-d172-da72-af62f93bc5c0.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..8083bfab54c50d4c4c67e296d37d46859996f1dd Binary files /dev/null and b/schoolapps/static/images/8fc2ae6f-6af5-d172-da72-af62f93bc5c0.webPlatform.png differ diff --git a/schoolapps/static/images/982059a6-7527-5086-5334-b0252e562f97.webPlatform.png b/schoolapps/static/images/982059a6-7527-5086-5334-b0252e562f97.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..6ca6247bb9edac56d3cfc9ebb25257cef309a477 Binary files /dev/null and b/schoolapps/static/images/982059a6-7527-5086-5334-b0252e562f97.webPlatform.png differ diff --git a/schoolapps/static/images/9bba1bae-17fb-2975-9edf-a0a90f3a54eb.webPlatform.png b/schoolapps/static/images/9bba1bae-17fb-2975-9edf-a0a90f3a54eb.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5ff63b7cbb327363e0243e8a06812942bd0a1c Binary files /dev/null and b/schoolapps/static/images/9bba1bae-17fb-2975-9edf-a0a90f3a54eb.webPlatform.png differ diff --git a/schoolapps/static/images/aa11bd3d-260b-c276-1c09-b767ac224f2e.webPlatform.png b/schoolapps/static/images/aa11bd3d-260b-c276-1c09-b767ac224f2e.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..db96282a1a185ab77226279f8958392ef626d901 Binary files /dev/null and b/schoolapps/static/images/aa11bd3d-260b-c276-1c09-b767ac224f2e.webPlatform.png differ diff --git a/schoolapps/static/images/d61086e1-4929-90e8-088a-129e4d16b0fa.webPlatform.png b/schoolapps/static/images/d61086e1-4929-90e8-088a-129e4d16b0fa.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..003a2206d8553c41166903445c9c5d4d6a4a23fd Binary files /dev/null and b/schoolapps/static/images/d61086e1-4929-90e8-088a-129e4d16b0fa.webPlatform.png differ diff --git a/schoolapps/static/images/e677beba-e9e4-3000-bca6-c8539dbf74e4.webPlatform.png b/schoolapps/static/images/e677beba-e9e4-3000-bca6-c8539dbf74e4.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..59235792e1c86ef6b847c7c79c579e6902f72bc7 Binary files /dev/null and b/schoolapps/static/images/e677beba-e9e4-3000-bca6-c8539dbf74e4.webPlatform.png differ diff --git a/schoolapps/static/images/eaf673aa-e19a-74b4-33c0-d38154cd1b18.webPlatform.png b/schoolapps/static/images/eaf673aa-e19a-74b4-33c0-d38154cd1b18.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..002ef0b644f0b9be0042578b05db166359ddff65 Binary files /dev/null and b/schoolapps/static/images/eaf673aa-e19a-74b4-33c0-d38154cd1b18.webPlatform.png differ diff --git a/schoolapps/static/images/ffdd263d-816e-c2ef-5978-2377f85c42b9.webPlatform.png b/schoolapps/static/images/ffdd263d-816e-c2ef-5978-2377f85c42b9.webPlatform.png new file mode 100644 index 0000000000000000000000000000000000000000..e56b4494e3843a37e6d052f8918c4762ac15a817 Binary files /dev/null and b/schoolapps/static/images/ffdd263d-816e-c2ef-5978-2377f85c42b9.webPlatform.png differ diff --git a/schoolapps/static/support/bundle.js b/schoolapps/static/support/bundle.js index 83412d8f2b9f5c4de715ca23bc51609e3130a65f..ab65892c663bdff7d71de6ca2199a3741ff75cfb 100644 --- a/schoolapps/static/support/bundle.js +++ b/schoolapps/static/support/bundle.js @@ -16451,7 +16451,7 @@ object-assign key: "render", value: function render() { return _react.default.createElement("div", { - className: (this.props.show ? "" : "hide ") + "input-field col s4" + className: (this.props.show ? "" : "hide ") + "input-field col s12 m12 l4" }, _react.default.createElement("i", { className: "material-icons prefix" }, this.props.icon), this.props.children, _react.default.createElement("label", null, this.props.label)); @@ -16561,7 +16561,7 @@ object-assign }, _react.default.createElement("div", { className: "row" }, _react.default.createElement("div", { - className: "input-field col s4" + className: "input-field col s12 m12 l4" }, _react.default.createElement("i", { className: "material-icons prefix" }, "list"), _react.default.createElement("select", { @@ -16644,7 +16644,7 @@ object-assign values: ["kath-schueler", "kath-lehrer", "kath-edu", "kath-gaeste"], defaultValue: "-" })), _react.default.createElement("div", { - className: (sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s4" + className: (sC === "deviceIssues" && step === 2 ? "" : "hide ") + "input-field col s12 m12 l4" }, _react.default.createElement("i", { className: "material-icons prefix" }, "device_unknown"), _react.default.createElement("input", { diff --git a/schoolapps/support/forms.py b/schoolapps/support/forms.py index efb89082e885e6e23fdd7e776378a807060c4c8b..cc9ef7a83ddc6ee8ef3962c7bd49e9f94866f2e7 100644 --- a/schoolapps/support/forms.py +++ b/schoolapps/support/forms.py @@ -18,7 +18,7 @@ class FeedbackForm(forms.Form): choices=ratings, widget=forms.RadioSelect(attrs={"checked": "checked"}), required=True, -) + ) performance_rating = forms.ChoiceField(label='Geschwindigkeit', choices=ratings, @@ -35,9 +35,10 @@ class FeedbackForm(forms.Form): widget=forms.RadioSelect(attrs={"checked": "checked"}), required=True) - apps = forms.CharField(label="Bitte gebe uns Feedback zu den einzelnen Funktionen von SchoolApps", - required=False, - widget=forms.Textarea) + apps = forms.CharField( + label="Bitte sage uns, was dir an SchoolApps gefällt und was dich stört bzw. was du ändern würdest.", + required=False, + widget=forms.Textarea) more = forms.CharField(label="Möchtest du uns sonst noch etwas mitteilen?", required=False, @@ -47,4 +48,4 @@ class FeedbackForm(forms.Form): ideas = forms.CharField( label='Hast du Ideen, was wir noch in SchoolApps einbauen könnten/sollten?', required=False, - widget=forms.Textarea) \ No newline at end of file + widget=forms.Textarea) diff --git a/schoolapps/support/models.py b/schoolapps/support/models.py index cb5b43a732a25d566bd5d385534e0303b874ad20..bedca1bcbb06a14563f3e57c6ff51625fb92185d 100644 --- a/schoolapps/support/models.py +++ b/schoolapps/support/models.py @@ -1,5 +1,4 @@ import dbsettings -from django import forms from django.db import models @@ -10,6 +9,10 @@ class KanboardSettings(dbsettings.Group): kb_project_id_feedback = dbsettings.PositiveIntegerValue("Project ID for feedback tasks") +class MailSettings(dbsettings.Group): + mail_rebus = dbsettings.EmailValue("Email address for REBUS") + mail_feedback = dbsettings.EmailValue("Email address for Feedback") + class Support(models.Model): class Meta: permissions = ( @@ -19,3 +22,4 @@ class Support(models.Model): kanboard_settings = KanboardSettings("Kanboard") +mail_settings = MailSettings("Mail adresses") diff --git a/schoolapps/support/templates/support/feedback.html b/schoolapps/support/templates/support/feedback.html index faee1a29e82eab4fa3052a2d098f74954dc33e18..d5a7f7e3a2e5a7c20502fe8d4fbb5ee09e7e84ae 100644 --- a/schoolapps/support/templates/support/feedback.html +++ b/schoolapps/support/templates/support/feedback.html @@ -4,73 +4,88 @@ <main> -<p class="flow-text"> - Bitte gebe uns ein ausführliches und ehrliches Feedback, damit wir SchoolApps noch besser machen können! -</p> + <p class="flow-text"> + Bitte gebe uns ein ausführliches und ehrliches Feedback, damit wir SchoolApps noch besser machen können! + </p> -<form method="post"> - {% csrf_token %} + <form method="post"> + {% csrf_token %} <ul class="collection"> - <li class="collection-item"> - <div>{{ form.design_rating.label }} <span class="red-text">*</span> - <div class="rating float_right"> - {% for radio in form.design_rating %} - {{ radio.tag }} - <label for="{{ radio.id_for_label }}"></label> - {% endfor %} - </div> - </div> - </li> - <li class="collection-item"> - <div>{{ form.performance_rating.label }} <span class="red-text">*</span> - <div class="rating float_right"> - {% for radio in form.performance_rating %} - {{ radio.tag }} - <label for="{{ radio.id_for_label }}"></label> - {% endfor %} - </div> - </div> - </li> - <li class="collection-item"> - <div>{{ form.usability_rating.label }} <span class="red-text">*</span> - <div class="rating float_right"> - {% for radio in form.usability_rating %} - {{ radio.tag }} - <label for="{{ radio.id_for_label }}"></label> - {% endfor %} - </div> - </div> - </li> - <li class="collection-item"> - <div>{{ form.overall_rating.label }} <span class="red-text">*</span> - <div class="rating float_right"> - {% for radio in form.overall_rating %} - {{ radio.tag }} - <label for="{{ radio.id_for_label }}"></label> - {% endfor %} - </div> - </div> - </li> + <li class="collection-item"> + <div>{{ form.design_rating.label }} <span class="red-text">*</span> + <div class="rating float_right"> + {% for radio in form.design_rating %} + {{ radio.tag }} + <label for="{{ radio.id_for_label }}"></label> + {% endfor %} + </div> + </div> + </li> + <li class="collection-item"> + <div>{{ form.performance_rating.label }} <span class="red-text">*</span> + <div class="rating float_right"> + {% for radio in form.performance_rating %} + {{ radio.tag }} + <label for="{{ radio.id_for_label }}"></label> + {% endfor %} + </div> + </div> + </li> + <li class="collection-item"> + <div>{{ form.usability_rating.label }} <span class="red-text">*</span> + <div class="rating float_right"> + {% for radio in form.usability_rating %} + {{ radio.tag }} + <label for="{{ radio.id_for_label }}"></label> + {% endfor %} + </div> + </div> + </li> + <li class="collection-item"> + <div>{{ form.overall_rating.label }} <span class="red-text">*</span> + <div class="rating float_right"> + {% for radio in form.overall_rating %} + {{ radio.tag }} + <label for="{{ radio.id_for_label }}"></label> + {% endfor %} + </div> + </div> + </li> - <li class="collection-item"> - <div>{{ form.ideas.label }} - <div class="input-field inline float_right feedback-input"> - <input name="{{ form.ideas.html_name }}" type="text" {{ form.ideas.required}}> - </div> - </div> - - </li> + <li class="collection-item"> + <div class="row"> + <div class="col s6"> + {{ form.ideas.label }} + </div> + <div class="col s6"> + <div class="input-field inline float_right feedback-input"> + <input name="{{ form.ideas.html_name }}" type="text" {{ form.ideas.required }}> + </div> + </div> + </div> + </li> - <li class="collection-item"> - <div> - <div class="input-field col s12"> - <textarea id="{{ form.more.id_for_label }}" name="{{ form.more.html_name }}" class="materialize-textarea"></textarea> - <label for="{{ form.more.id_for_label }}">{{ form.more.label }}</label> - </div> - </div> - </li> + <li class="collection-item"> + <div> + <div class="input-field"> + <textarea id="{{ form.apps.id_for_label }}" name="{{ form.apps.html_name }}" + class="materialize-textarea"></textarea> + <label for="{{ form.apps.id_for_label }}">{{ form.apps.label }}</label> + </div> + </div> + </li> + + <li class="collection-item"> + <div> + <div class="input-field"> + <textarea id="{{ form.more.id_for_label }}" name="{{ form.more.html_name }}" + class="materialize-textarea"></textarea> + <label for="{{ form.more.id_for_label }}">{{ form.more.label }}</label> + </div> + </div> + </li> </ul> @@ -80,9 +95,9 @@ <button type="submit" name="go" class="btn waves-effect waves-light green"> Feedback senden - <i class="material-icons right">send</i> - </button> -</form> + <i class="material-icons right">send</i> + </button> + </form> </main> diff --git a/schoolapps/support/templates/support/mail/feedback.html b/schoolapps/support/templates/support/mail/feedback.html index fba4e0416a21d59f322317812afca51da68f71fe..9decc58b4fbea6c9319a10dbc177a06794846e0d 100755 --- a/schoolapps/support/templates/support/mail/feedback.html +++ b/schoolapps/support/templates/support/mail/feedback.html @@ -1,7 +1,7 @@ {% include "mail/header.html" %} <main> - <p>Hallo,</p> + <p>Hallo SchoolApps-Team,</p> <p>es gibt neues Feedback:</p> <blockquote> Bewertungen: {{ design }}/5 (Design), {{ performance }}/5 (Geschwindigkeit), {{ usability }}/5 @@ -9,13 +9,13 @@ Bewertung (insgesamt): {{ overall }}/10 - Feedback zu einzelnen Apps: {{ apps }} + Pro/Contra: {{ apps }} Ideen/Wünsche: {{ ideas }} Sonstiges: {{ more }} + <p>Ãœbermittelt von {{ user }}</p> </blockquote> - <i>Dein SchoolApps-Team</i> </main> \ No newline at end of file diff --git a/schoolapps/support/templates/support/mail/feedback.txt b/schoolapps/support/templates/support/mail/feedback.txt index b547bfb48f85da3cefd3c12741d60cdf8ceaf2b9..c6de9bc45dfa2132cc99dee760450fb80f5d9e7c 100755 --- a/schoolapps/support/templates/support/mail/feedback.txt +++ b/schoolapps/support/templates/support/mail/feedback.txt @@ -1,4 +1,4 @@ -Hallo, +Hallo SchoolApps-Team, es gibt neues Feedback: @@ -7,7 +7,7 @@ Bewertungen: {{ design }}/5 (Design), {{ performance }}/5 (Geschwindigkeit), {{ Bewertung (insgesamt): {{ overall }}/5 -Feedback zu einzelnen Apps: {{ apps }} +Pro/Contra: {{ apps }} Ideen/Wünsche: {{ ideas }} @@ -15,5 +15,3 @@ Sonstiges: {{ more }} Ãœbermittelt von {{ user }} - -Dein SchoolApps-Team diff --git a/schoolapps/support/templates/support/rebus.html b/schoolapps/support/templates/support/rebus.html index 79afc1c3e45da567ea1f4a4845ca4c21e2440b94..b7ce348aa17b1b99e921d8ff0458287302e9fb83 100644 --- a/schoolapps/support/templates/support/rebus.html +++ b/schoolapps/support/templates/support/rebus.html @@ -34,7 +34,7 @@ class="materialize-textarea"></textarea> <label for="{{ form.long_description.id_for_label }}">Bitte beschreibe dein Problem - <strong>genauer</strong></label> + <strong>genauer</strong> (optional)</label> </div> </div> diff --git a/schoolapps/support/views.py b/schoolapps/support/views.py index c52ca846c8a4fe6574f5236c592eba0ed119482e..4ef0e37a084909ef487ac5365552db409b387a97 100644 --- a/schoolapps/support/views.py +++ b/schoolapps/support/views.py @@ -1,14 +1,13 @@ from django.shortcuts import render from mailer import send_mail_with_template -from support.models import kanboard_settings +from support.models import kanboard_settings, mail_settings from untisconnect.api import get_all_rooms from .forms import REBUSForm from .forms import FeedbackForm from kanboard import Kanboard from dashboard.models import Activity -print("HI") api_token = kanboard_settings.api_token p_id_rebus = kanboard_settings.kb_project_id_rebus p_id_feedback = kanboard_settings.kb_project_id_feedback @@ -30,7 +29,7 @@ def rebus(request): long_description = form.cleaned_data['long_description'] # Build description for kanboard - description = "**Kategorie:** {} > {} > {} \n\n **Ãœbermittelt von:** {} \n\n **Nachricht:** {}".format(a, b, + description = "**Kategorie:** {} → {} → {} \n\n **Ãœbermittelt von:** {} \n\n **Nachricht:** {}".format(a, b, c, contraction, long_description) @@ -38,7 +37,7 @@ def rebus(request): kb.create_task(project_id=p_id_rebus, title=short_description, description=description) # Register activity - desc_act = "{} > {} > {} | {} | {}".format(a, b, c, short_description, long_description) + desc_act = "{} → {} → {} | {}".format(a, b, c, short_description) act = Activity(title="Du hast uns ein Problem gemeldet.", description=desc_act, app="REBUS", user=request.user) act.save() @@ -52,7 +51,7 @@ def rebus(request): "long_desc": long_description, "user": request.user.username } - send_mail_with_template("Neue REBUS-Meldung", ["support@katharineum.de"], "support/mail/rebus.txt", + send_mail_with_template("Neue REBUS-Meldung", [mail_settings.mail_rebus], "support/mail/rebus.txt", "support/mail/rebus.html", context) return render(request, 'support/rebus_submitted.html') @@ -65,70 +64,71 @@ def rebus(request): def feedback(request): - if request.method == 'POST': - form = FeedbackForm(request.POST) - if form.is_valid(): - # Read out form data - design_rating = form.cleaned_data['design_rating'] - performance_rating = form.cleaned_data['performance_rating'] - usability_rating = form.cleaned_data['usability_rating'] - overall_rating = form.cleaned_data['overall_rating'] - more = form.cleaned_data['more'] - ideas = form.cleaned_data['ideas'] - apps = form.cleaned_data["apps"] - - # Build description for kanboard - description = """ + if request.method == 'POST': + form = FeedbackForm(request.POST) + if form.is_valid(): + # Read out form data + design_rating = form.cleaned_data['design_rating'] + performance_rating = form.cleaned_data['performance_rating'] + usability_rating = form.cleaned_data['usability_rating'] + overall_rating = form.cleaned_data['overall_rating'] + more = form.cleaned_data['more'] + ideas = form.cleaned_data['ideas'] + apps = form.cleaned_data["apps"] + + # Build description for kanboard + description = """ **Bewertungen:** {}/5 (Design), {}/5 (Geschwindigkeit), {}/5 (Benutzerfreundlichkeit) **Bewertung (insgesamt):** {}/5 - **Feedback zu einzelnen Apps:** {} + **Pro/Contra:** {} **Ideen/Wünsche:** {} **Sonstiges:** {} """.format(design_rating, performance_rating, usability_rating, overall_rating, apps, ideas, more) - # Get color for kanboard by rating - if int(overall_rating) <= 3: - color = "red" - elif 3 < int(overall_rating) <= 7: - color = "yellow" - else: - color = "green" - - # Add kanboard task - kb.create_task(project_id=p_id_feedback, - title="Feedback von {}".format(request.user.username), - description=description, - color_id=color) - - # Register activity - act = Activity(title="Du hast uns Feedback gegeben.", - description="Du hast uns auf einer Skala von 1 bis 5 mit {} Sternen bewertet.".format( - overall_rating), app="Feedback", - user=request.user) - act.save() - - # Send mail - context = { - "design": design_rating, - "performance": performance_rating, - "usability": usability_rating, - "overall": overall_rating, - "more": more, - "apps": apps, - "ideas": ideas, - "user": request.user.username - } - send_mail_with_template("Neues Feedback von {}".format(request.user.username), ["support@katharineum.de"], "support/mail/feedback.txt", - "support/mail/feedback.html", context) - print(context) - - return render(request, 'support/feedback_submitted.html') - else: - form = FeedbackForm() - - - return render(request, 'support/feedback.html', {'form': form}) + # Get color for kanboard by rating + if int(overall_rating) < 2: + color = "red" + elif 2 < int(overall_rating) <= 3: + color = "yellow" + else: + color = "green" + + # Add kanboard task + kb.create_task(project_id=p_id_feedback, + title="Feedback von {}".format(request.user.username), + description=description, + color_id=color) + + # Register activity + act = Activity(title="Du hast uns Feedback gegeben.", + description="Du hast SchoolApps mit {} von 5 Sternen bewertet.".format( + overall_rating), app="Feedback", + user=request.user) + act.save() + + # Send mail + context = { + "design": design_rating, + "performance": performance_rating, + "usability": usability_rating, + "overall": overall_rating, + "more": more, + "apps": apps, + "ideas": ideas, + "user": request.user.username + } + send_mail_with_template("Neues Feedback von {}".format(request.user.username), + [mail_settings.mail_feedback], + "support/mail/feedback.txt", + "support/mail/feedback.html", context) + print(context) + + return render(request, 'support/feedback_submitted.html') + else: + form = FeedbackForm() + + return render(request, 'support/feedback.html', {'form': form}) diff --git a/schoolapps/templates/common/impress.html b/schoolapps/templates/common/impress.html deleted file mode 100755 index b19c378808fc3583da608675708410a7034319fd..0000000000000000000000000000000000000000 --- a/schoolapps/templates/common/impress.html +++ /dev/null @@ -1,34 +0,0 @@ -{% include 'partials/header.html' %} - -<main> - <h3>Impressum</h3> - - <p> - <strong>Katharineum zu Lübeck</strong> <br> - Königsstraße 27-31 <br> - 23552 Lübeck - </p> - - <p> - <strong>Kommissarischer Schulleiter:</strong> Herr StD Poetzsch-Heffter <br> - <strong>E-Mail:</strong> schulleiter@katharineum.de - </p> - - <p> - <strong>Schulträger:</strong> Hansestadt Lübeck <br> - vertreten durch den Bürgermeister Bernd Saxe - </p> - - <p> - <strong>Entwicklung und Betreuung von <i>SchoolApps</i>:</strong> Herr Poetzsch-Heffter und die Computer-AG - </p> - - <h4>Disclaimer – rechtliche Hinweise</h4> - <strong><i><u>TODO</u></i></strong><br> - Haftungsausschluss (Aktualität, Richtigkeit, etc.)<br> - Datenschutz, Datenverarbeitung (Weitergabe an Dritte?, etc.)<br> - Sicherheit (Verschlüsselung, generell Sicherheitsprobleme möglich) - -</main> - -{% include 'partials/footer.html' %} diff --git a/schoolapps/templates/partials/footer.html b/schoolapps/templates/partials/footer.html index 01e26e4a9f9d521c220075762a2d1903b7a0f0c5..b7c73e8e229a5cca5ee181c6d52732d078fff9e8 100755 --- a/schoolapps/templates/partials/footer.html +++ b/schoolapps/templates/partials/footer.html @@ -1,8 +1,17 @@ <footer class="page-footer primary-color"> <div class="footer-copyright"> <div class="container"> - © 2018 Computer-AG, Katharineum zu Lübeck - <a class="grey-text text-lighten-4 right" href="{% url 'impress' %}">Impressum</a> + <span class="left"> + SchoolApps · Version 1.0-beta "Aebli" <br> + © 2018–{% now "Y" %} Computer-AG, Katharineum zu Lübeck · + Licensed under <a href="http://www.gnu.org/licenses/gpl-3.0-standalone.html">GNU GPL 3.0</a> + </span> + <span class="right"> + + <a class="grey-text text-lighten-4" href="https://katharineum-zu-luebeck.de/impressum/">Impressum</a> + · + <a class="grey-text text-lighten-4" href="https://katharineum-zu-luebeck.de/datenschutzerklaerung/">Datenschutzerklärung</a> + </span> </div> </div> </footer> diff --git a/schoolapps/templates/partials/header.html b/schoolapps/templates/partials/header.html index 25bc5e7ba7e69992849a5093bd83e55e31a897bf..c1ba3a1d67e7449fb9f4518d16d7f3cbe6ac50be 100755 --- a/schoolapps/templates/partials/header.html +++ b/schoolapps/templates/partials/header.html @@ -8,7 +8,8 @@ <title>SchoolApps – Katharineum zu Lübeck</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Favicon --> - <link rel="shortcut icon" type="image/png" href="{% static 'common/favicon.ico' %}"/> + <link rel="shortcut icon" type="image/png" href="{% static 'common/favicon.ico' %}"> + <link rel="manifest" href="{% static "common/manifest.json" %}"> <!---------> <!-- CSS --> @@ -16,19 +17,16 @@ <link href="{% static 'materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet"> <link rel="stylesheet" type="text/css" media="screen" href="{% static 'materialize/dist/css/materialize.min.css' %}"> - - <!-- django-material --> - {% include 'material/includes/material_css.html' %} - <link rel="stylesheet" type="text/css" href="{% static 'common/style.css' %}"> + <!----------------> <!-- JavaScript (jquery v. 3.3.1.slim)--> <!----------------> + <script src="{% static 'common/manup.min.js' %}"></script> + <script src="{% static "common/pwabuilder-sw-register.js" %}"></script> <script src="{% static 'jquery/jquery-3.3.1.slim.min.js' %}"></script> <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' %} </head> <body> @@ -94,7 +92,11 @@ <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 %} + {% if perms.aub.view_archive %} + <li><a href="{% url 'aub_archive' %}"><i class="material-icons">archive</i> Archiv</a> + </li> + {% endif %} + {% if perms.aub.apply_for_aub or perms.aub.check1_aub or perms.aub.check2_aub or perms.aub.archiv %} <li> <div class="divider"></div> </li> @@ -104,53 +106,86 @@ <a class="subheader grey lighten-3">Stundenplan</a> </li> <li> - <a href="{% url 'timetable_admin_all' %}"> - <i class="material-icons">grid_on</i> Ãœbersicht + <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> Alle Pläne#} + {# </a>#} + {# </li>#} <li> <a href="{% url 'timetable_quicklaunch' %}"> - <i class="material-icons">directions</i> Schnellzugriff + <i class="material-icons">grid_on</i> Alle Pläne </a> </li> <li> <a href="{% url 'timetable_substitutions' %}"> - <i class="material-icons">person</i> Vertretungsplan + <i class="material-icons">update</i> Vertretungsplan </a> </li> <li> <div class="divider"></div> </li> {% endif %} + + <li> + <a class="subheader grey lighten-3">Speiseplan</a> + </li> + <li> + <a href="{% url 'menu_show_current' %}" target="_blank"> + <i class="material-icons">picture_as_pdf</i> Aktueller Speiseplan + </a> + </li> {% if perms.menu.add_menu %} - <li> - <a class="subheader grey lighten-3">Speiseplan</a> - </li> <li> <a href="{% url 'menu_index' %}"> <i class="material-icons">restaurant_menu</i> Speiseplan hochladen </a> </li> - <li> - <div class="divider"></div> - </li> {% endif %} + <li> + <div class="divider"></div> + </li> + + <li> + <a class="subheader grey lighten-3">Support</a> + </li> + <li> + <a href="{% url 'rebus' %}"> + <i class="material-icons">bug_report</i> Fehler melden + </a> + </li> + <li> + <a href="{% url 'feedback' %}"> + <i class="material-icons">feedback</i> Feedback + </a> + </li> + <li> + <div class="divider"></div> + </li> + + {% if user.is_superuser %} <li> - <a class="subheader grey lighten-3">Support</a> + <a class="subheader grey lighten-3">Administration</a> </li> <li> - <a href="{% url 'rebus' %}"> - <i class="material-icons">bug_report</i> Fehler melden + <a href="/admin/"> + <i class="material-icons">dashboard</i> Django-Administration </a> </li> <li> - <a href="{% url 'feedback' %}"> - <i class="material-icons">feedback</i> Feedback + <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 @@ -159,3 +194,11 @@ {% endif %} </ul> </header> +<header class="alert success"> + <p> + <i class="material-icons left">info</i> + Du befindest dich in der Testversion von SchoolApps. Daher kann es immer mal wieder zu unvorhergesehenen + Problemen kommen. Es würde uns sehr helfen, wenn du uns dann über + <a href="mailto:support@katharineum.de">support@katharineum.de</a> schreibst. + </p> +</header> diff --git a/schoolapps/templates/registration/logged_out.html b/schoolapps/templates/registration/logged_out.html index f24713ec565aa858a3d0e7b26b38159f183691d2..2a7251575943c8d7b2083babcfa8dcafde300110 100755 --- a/schoolapps/templates/registration/logged_out.html +++ b/schoolapps/templates/registration/logged_out.html @@ -1,7 +1,7 @@ {% include 'partials/header.html' %} <main> - <p class="flow-text">Sie sind nun abgemeldet.</p> + <p class="flow-text">Du bist nun abgemeldet.</p> <a href="{% url 'login' %}">Wieder anmelden?</a> </main> diff --git a/schoolapps/templates/registration/login.html b/schoolapps/templates/registration/login.html index a4890ad9a7217951943cbe0afd13bd23c2c246b4..9f9761e5d667ff150695289d01493853bf3f0d23 100755 --- a/schoolapps/templates/registration/login.html +++ b/schoolapps/templates/registration/login.html @@ -1,19 +1,33 @@ {% include 'partials/header.html' %} <main> + {% if form.errors %} - <p class="flow-text red-text">Der Benutzername und/oder das Passwort ist falsch. Bitte probieren Sie es - nochmal.</p> + <div class="alert error"> + <p> + <i class="material-icons left">warning</i> + Der Benutzername und/oder das Passwort ist falsch. Bitte probiere es + nochmal.</p> + </div> {% endif %} {% if next %} {% if user.is_authenticated %} - <p class="flow-text red-text">Sie haben keine Erlaubnis, diese Seite zu sehen. - Bitte probieren Sie es mit einem anderen Benutzer.</p> + <div class="alert error"> + <p> + <i class="material-icons left">warning</i> + Du darfst diese Seite leider nicht sehen. + Bitte probiere es mit einem anderen Benutzer.</p> + </div> {% else %} - <p class="flow-text red-text">Bitte melden Sie sich an, um diese Seite zu sehen.</p> + <div class="alert error"> + <p> + <i class="material-icons left">warning</i> + Bitte melde dich an, um diese Seite zu sehen. </p> + </div> {% endif %} {% endif %} + <br> <form method="post" action="{% url 'login' %}"> {% csrf_token %} diff --git a/schoolapps/timetable/pdf.py b/schoolapps/timetable/pdf.py index 281649eaf6f0a1e9d248d95ef63aced117b43203..52f8cb8792fccbbd01d4896a7fff410bcc966d1c 100644 --- a/schoolapps/timetable/pdf.py +++ b/schoolapps/timetable/pdf.py @@ -4,21 +4,24 @@ import subprocess from django.utils import timezone from django.utils import formats +from schoolapps.settings import BASE_DIR + # LaTeX constants +from untisconnect.sub import get_header_information -TEX_HEADER = """\\documentclass[11pt]{article} +DIR = os.path.join(BASE_DIR, "static", "common", "logo.png") +TEX_HEADER_1 = """\\documentclass[11pt]{article} \\usepackage[ngerman]{babel} \\usepackage[sfdefault]{cabin} \\usepackage[utf8]{inputenc} \\usepackage[a4paper,left=1cm,right=1cm,top=2cm,bottom=2cm,bindingoffset=0mm]{geometry} +% Packages \\usepackage{fancyhdr} \\usepackage{graphicx} - \\usepackage{longtable} \\usepackage{multirow} \\usepackage{color, colortbl} - \\usepackage{geometry} \\usepackage{ulem, xpatch} @@ -26,18 +29,8 @@ TEX_HEADER = """\\documentclass[11pt]{article} {\\bgroup} {\\bgroup\def\\ULthickness{1.5pt}} {}{} - -\\usepackage[framemethod=tikz]{mdframed} -\\newmdenv[ - roundcorner=5pt, - backgroundcolor=green, - linecolor=green, - skipabove=0pt, - skipbelow=0pt, - leftmargin=0pt, - rightmargin=0pt -]{badges} +% Badge box \\usepackage{tcolorbox} \\newtcbox{\\badge}{nobeforeafter,colframe=green,colback=green,boxrule=0.5pt,arc=4pt, boxsep=0pt,left=5pt,right=5pt,top=5pt,bottom=5pt,tcbox raise base, @@ -46,40 +39,40 @@ TEX_HEADER = """\\documentclass[11pt]{article} enlarge top by=3pt, enlarge bottom by=3pt,coltext=white} - -%\\usepackage{helvet} %Helvetica als Standardschriftart -%\\renewcommand{\\familydefault}{\\sfdefault} %Helvetica als Standardschriftart - +% Define colors \\definecolor{grey}{RGB}{208, 208, 208} \\definecolor{darkgrey}{rgb}{0.6,0.6,0.6} \\definecolor{white}{rgb}{1,1,1} \\definecolor{green}{RGB}{76,175,80} +% Define header \\pagestyle{fancy} -%\\renewcommand{\\sectionmark}[1]{#1} -%\\lhead{\\rightmark} -\\lhead{\\includegraphics[width=5cm]{static/common/logo.png}} +% Left header: logo +\\lhead{\\includegraphics[width=5cm]{""" + +TEX_HEADER_2 = """}} +% Define footer \\lfoot{Katharineum zu Lübeck} \\cfoot{\\thepage} -\\rfoot{\\small Umsetzung: © 2018 by Computer-AG} +\\rfoot{\\small Umsetzung: © 2018--2019 by Computer-AG} \\begin{document}""" +TEX_HEADER = TEX_HEADER_1 + DIR + TEX_HEADER_2 TEX_FOOTER = '\end{document}' TEX_TABLE_HEADER_CLASS = """ +% Init table \def\\arraystretch{1.5} -\\begin{longtable}{p{20mm}p{10mm}p{32mm}p{25mm}p{30mm}p{35mm}} -%\\arrayrulecolor{black} - -%\\hline\n -%\\rowcolor{darkgrey} +\\begin{longtable}{p{20mm}p{8mm}p{32mm}p{25mm}p{30mm}p{45mm}} \\textbf{Klassen} & \\textbf{Std.} & \\textbf{Lehrer} & \\textbf{Fach} & \\textbf{Raum} & -\\textbf{Hinweis}\\\\\\hline +\\textbf{Hinweis}\\\\ +\\hline +\\endhead """ TEX_HEADER_CLASS = """ @@ -89,19 +82,43 @@ TEX_HEADER_CLASS = """ \\section*{\\Huge Vertretungen %s} \n""" +TEX_HEADER_BOX_START = """ +\\fbox{\\parbox{0.27\\linewidth}{ +""" + +TEX_HEADER_BOX_MIDDLE = """ +}\\parbox{0.73\\linewidth}{ +""" + +TEX_HEADER_BOX_END = """ +}} \n\n +""" + +TEX_HEADER_BOX_ROW_A = """ +\\textbf{%s} +""" + +TEX_HEADER_BOX_ROW_B = """ +%s +""" + def generate_pdf(tex, filename): """Generate a PDF by LaTeX code""" # Read LaTeX file - tex_file = open(os.path.join("latex", filename + ".tex"), "w") + tex_file = open(os.path.join(BASE_DIR, "latex", filename + ".tex"), "w", encoding="utf8") + tex_file.write(tex) tex_file.close() # Execute pdflatex to generate the PDF - bash_command = "pdflatex -output-directory latex {}.tex".format(filename) + bash_command = "pdflatex -output-directory {} {}.tex".format(os.path.join(BASE_DIR, "latex"), + os.path.join(BASE_DIR, "latex", filename)) + print(bash_command) process = subprocess.Popen(bash_command.split(), stdout=subprocess.PIPE) output = process.communicate()[0] + print(output) def tex_replacer(s): @@ -121,8 +138,9 @@ def tex_replacer(s): return s -def generate_class_tex(subs, date): +def generate_class_tex(subs, date, header_info): """Generate LaTeX for a PDF by a substitution table""" + tex_body = "" # Format dates @@ -133,12 +151,26 @@ def generate_class_tex(subs, date): # Generate header with dates tex_body += TEX_HEADER_CLASS % (status_date, current_date, head_date) + if header_info.is_box_needed(): + tex_body += TEX_HEADER_BOX_START + for row in header_info.rows: + tex_body += TEX_HEADER_BOX_ROW_A % row[0] + tex_body += TEX_HEADER_BOX_MIDDLE + for row in header_info.rows: + tex_body += TEX_HEADER_BOX_ROW_B % row[1] + tex_body += TEX_HEADER_BOX_END # Begin table tex_body += TEX_TABLE_HEADER_CLASS color_background = True + last_classes = "" for sub in subs: - # Color every second row in grey + # Color groups of classes in grey/white + if last_classes != sub.classes: + color_background = not color_background + + last_classes = sub.classes + if color_background: tex_body += '\\rowcolor{grey}' @@ -146,6 +178,7 @@ def generate_class_tex(subs, date): color = "\color{%s}" % sub.color # Print classes + # print(sub.classes) tex_body += color tex_body += '\\textbf{' + sub.classes + '} & ' @@ -162,8 +195,6 @@ def generate_class_tex(subs, date): tex_body += color tex_body += "\\Large\\textit{%s}\\\\\n" % (sub.text or "") - # Change background - color_background = not color_background # End table tex_body += '\\end{longtable}' diff --git a/schoolapps/timetable/templates/timetable/all.html b/schoolapps/timetable/templates/timetable/all.html index dd56f6d3729573c26d31a588911d2cc10142c059..ce5cc999c6fa461bbb9456c90a97b5f9d7294423 100755 --- a/schoolapps/timetable/templates/timetable/all.html +++ b/schoolapps/timetable/templates/timetable/all.html @@ -1,7 +1,7 @@ {% include 'partials/header.html' %} <main> - <h3>Ãœbersicht</h3> + <h3>Alle Pläne</h3> <div class="row"> <div class="col s12 m4"> <h4>Lehrkräfte</h4> @@ -9,7 +9,7 @@ {% for teacher in teachers %} <li class="collection-item avatar"> <i class="circle">{{ teacher.shortcode }}</i><!-- Shortcode --> - <a href="{% url 'timetable_plan' 'teacher' teacher.id %}">{{ teacher.first_name }} + <a href="{% url 'timetable_smart_plan' 'teacher' teacher.id %}">{{ teacher.first_name }} <strong>{{ teacher.name }}</strong></a> </li> {% endfor %} @@ -22,7 +22,7 @@ {% for class in classes %} <li class="collection-item avatar"> <i class="circle">{{ class.name }}</i> - <a href="{% url 'timetable_plan' 'class' class.id %}"><strong>{{ class.name }}</strong></a> + <a href="{% url 'timetable_smart_plan' 'class' class.id %}"><strong>{{ class.name }}</strong></a> <p> {{ class.text1|default:"" }} – {{ class.text2|default:"" }} <br> Raum: {{ class.room.name|default:"---" }} @@ -38,7 +38,7 @@ {% for room in rooms %} <li class="collection-item avatar"> <i class="circle">{{ room.shortcode }}</i> - <a href="{% url 'timetable_plan' 'room' room.id %}"><strong>{{ room.name }}</strong></a> + <a href="{% url 'timetable_smart_plan' 'room' room.id %}"><strong>{{ room.name }}</strong></a> </li> {% endfor %} </ul> diff --git a/schoolapps/timetable/templates/timetable/datepicker.html b/schoolapps/timetable/templates/timetable/datepicker.html new file mode 100644 index 0000000000000000000000000000000000000000..b30a0aa957a3117311f028523101304a47b628a5 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/datepicker.html @@ -0,0 +1,82 @@ +<script type="text/javascript"> + function updateDatepicker() { + {% if not display_date_only %} + $("#date").val(formatDate(activeDate)); + {% endif %} + } + + function update() { + console.log("Render new."); + + updateDatepicker(); + } + + function loadNew() { + window.location.href = dest + formatDateForDjango(activeDate); + } + + function onDateBeforeClick() { + if (activeDate.getDay() === 1) { + var minus = 3; + } else { + var minus = 1; + } + activeDate.setDate(activeDate.getDate() - minus); + update(); + loadNew(); + } + + function onDateNextClick() { + if (activeDate.getDay() === 5) { + var plus = 3; + } else { + var plus = 1; + } + activeDate.setDate(activeDate.getDate() + plus); + 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 left" id="date-before"> + <i class="material-icons center">navigate_before</i> + </a> + +</div> +{% if display_date_only %} + <div class="col s8"> + <span class="card-title center-block" id="date"> + {{ date|date:"l, j.n.Y" }} + </span> + </div> +{% else %} + <div class="col s8"> + <input type="text" class="datepicker center-align" id="date"> + </div> +{% endif %} +<div class="col s2" style="display: initial;"> + <a class="waves-effect waves-teal btn-flat btn-flat-medium right" 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..aa4f59a8f1ecb4a69777e486d80a1b40caeba5b1 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/lesson.html @@ -0,0 +1,112 @@ +<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 with the teacher#} + + {# Class or room > Display teacher #} + {% if type == 2 or type == 1 %} + <span data-position="bottom" class="tooltipped" + data-tooltip="{{ element_container.element.teacher }}"><a + href="{% url "timetable_smart_plan" "teacher" element_container.element.teacher.id %}">{{ element_container.element.teacher.shortcode }}</a></span> + <br> + {% endif %} + + {# Badge #} + <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 %} + {# {{ element_container.element.classes }}#} + {% if element_container.element.classes %} + <a href="{% url "timetable_smart_plan" "class" element_container.element.classes.0.id %}"> + {{ element_container.classes_formatted }} + </a> + {% endif %} + {% endif %} + + {# Class or room > Display teacher #} + {% if type == 2 or type == 1 %} + <span data-position="bottom" class="tooltipped" + data-tooltip="{{ element_container.element.teacher }}"> + <a href="{% url "timetable_smart_plan" "teacher" element_container.element.teacher.id %}"> + {{ element_container.element.teacher.shortcode }} + </a> + </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 }}"> + {% if element_container.room %} + <a href="{% url "timetable_smart_plan" "room" element_container.room.id %}"> + {{ element_container.room.shortcode }} + </a> + {% endif %} + </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..75dc2a2a0f8dd5fb2c91345bcf7b99a4903419d3 --- /dev/null +++ b/schoolapps/timetable/templates/timetable/myplan.html @@ -0,0 +1,73 @@ +{% 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>{{ el }}</h6> + </div> + </div> + + + <script type="text/javascript"> + var dest = "/timetable/my/"; + </script> + + <div class="row"> + <div class="timetable-plan col s12 m12 l6"> + + {# Date #} + + <div class="row"> + <div class="col s12"> + <div class="card timetable-title-card"> + <div class="card-content"> + <span class="card-title"> + {% include "timetable/datepicker.html" %} + </span> + </div> + </div> + + </div> + </div> + {# Lessons #} + {% for row, time in plan %} + <div class="row"> + <div class="col s4"> + <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 s8"> + {# 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..5d0297d439a39766df244acd889a8bda12c31124 100755 --- a/schoolapps/timetable/templates/timetable/plan.html +++ b/schoolapps/timetable/templates/timetable/plan.html @@ -1,78 +1,131 @@ {% 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 %}/{{ 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_regular_plan" raw_type id "regular" %}"> + <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 %}"> + <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,32 +135,9 @@ </div> {% for col in row %} + {# A lesson #} <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> + {% include "timetable/lesson.html" %} </div> {% endfor %} </div> diff --git a/schoolapps/timetable/templates/timetable/quicklaunch.html b/schoolapps/timetable/templates/timetable/quicklaunch.html index ca213ff63d1384b96a1e06f8080b7e80e73b7f01..67edf305baf6d6aa2a0136d5a5c3cc6bb2959b76 100755 --- a/schoolapps/timetable/templates/timetable/quicklaunch.html +++ b/schoolapps/timetable/templates/timetable/quicklaunch.html @@ -1,13 +1,13 @@ {% include 'partials/header.html' %} <main> - <h3>Ãœbersicht</h3> + <h3>Alle Pläne</h3> <div class="row"> <div class="col s12 m4"> <h4>Lehrkräfte</h4> {% for teacher in teachers %} <a class="waves-effect waves-light btn btn-timetable-quicklaunch" - href="{% url 'timetable_plan' 'teacher' teacher.id %}"> + href="{% url 'timetable_smart_plan' 'teacher' teacher.id %}"> {{ teacher.shortcode }} </a><!-- Shortcode --> {% endfor %} @@ -18,7 +18,7 @@ {% for class in classes %} <a class="waves-effect waves-light btn btn-timetable-quicklaunch" - href="{% url 'timetable_plan' 'class' class.id %}"> + href="{% url 'timetable_smart_plan' 'class' class.id %}"> {{ class.name }} </a> {% endfor %} @@ -29,7 +29,7 @@ <h4>Räume</h4> {% for room in rooms %} <a class="waves-effect waves-light btn btn-timetable-quicklaunch" - href="{% url 'timetable_plan' 'room' room.id %}"> + href="{% url 'timetable_smart_plan' 'room' room.id %}"> {{ room.shortcode }} </a> {% endfor %} diff --git a/schoolapps/timetable/templates/timetable/substitution.html b/schoolapps/timetable/templates/timetable/substitution.html index c33db779c4b70ae400df8c942607630b75470e76..c89bb6ecfc66748ce69eb38c915b0db607f83290 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> @@ -92,14 +38,14 @@ <tbody> {% for sub in sub_table %} <tr class="{{ sub.css_class }}"> + <td> + {{ sub.classes }} + </td> <td> <strong> {{ sub.lesson }} </strong> </td> - <td> - {{ sub.classes }} - </td> <td> <span class="tooltipped" data-position="bottom" data-tooltip="{{ sub.teacher_full|safe }}">{{ sub.teacher|safe }}</span> diff --git a/schoolapps/timetable/urls.py b/schoolapps/timetable/urls.py index 9e7c7dcf253b593405fa9281af470d0bb95c68be..61eb454ca4df89d46680baf71cc59a56e20607d2 100755 --- a/schoolapps/timetable/urls.py +++ b/schoolapps/timetable/urls.py @@ -3,8 +3,14 @@ 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'), + # plan_type = ["teacher", "class", "room"] + path('<str:plan_type>/<int:plan_id>', views.plan, name='timetable_smart_plan'), + path('<str:plan_type>/<int:plan_id>/<str:regular>', views.plan, name='timetable_regular_plan'), + path('<str:plan_type>/<int:plan_id>/<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..76089cd9d97dfad964e1a0841244da178b123692 100755 --- a/schoolapps/timetable/views.py +++ b/schoolapps/timetable/views.py @@ -1,21 +1,21 @@ import datetime import os +from PyPDF2 import PdfFileMerger 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 +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, get_header_information +from untisconnect.api import * +from userinformation import UserInformation -try: - from schoolapps.untisconnect.api import * -except Exception: - pass +from schoolapps.settings import BASE_DIR def get_all_context(): @@ -46,9 +46,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, regular="", year=timezone.datetime.now().year, + calendar_week=timezone.datetime.now().isocalendar()[1]): + if regular == "regular": + smart = False + else: + smart = True + + 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 +100,81 @@ 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) + + # Get next weekday if it is a weekend + next_weekday = get_next_weekday(date) + if next_weekday != date: + return redirect("timetable_my_plan", next_weekday.year, next_weekday.month, next_weekday.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, + "display_date_only": True + } + # print(context["week_day"]) + + return render(request, 'timetable/myplan.html', context) + + def get_next_weekday(date): """Get the next weekday by a datetime object""" @@ -92,19 +193,37 @@ def sub_pdf(request): # Get the next weekday today = timezone.datetime.now() first_day = get_next_weekday(today) + second_day = get_next_weekday(today + datetime.timedelta(days=1)) + print(second_day) # Get subs and generate table - subs = get_substitutions_by_date(first_day) - sub_table = generate_sub_table(subs) - - # Generate LaTeX - tex = generate_class_tex(sub_table, first_day) - - # Generate PDF - generate_pdf(tex, "class") + for i, day in enumerate([first_day, second_day]): + print(i, day) + subs = get_substitutions_by_date(day) + sub_table = generate_sub_table(subs) + header_info = get_header_information(subs) + # print(header_info.affected_teachers) + + # Generate LaTeX + tex = generate_class_tex(sub_table, day, header_info) + + # Generate PDF + generate_pdf(tex, "class{}".format(i)) + + # Merge PDFs + merger = PdfFileMerger() + class0 = open(os.path.join(BASE_DIR, "latex", "class0.pdf"), "rb") + class1 = open(os.path.join(BASE_DIR, "latex", "class1.pdf"), "rb") + merger.append(fileobj=class0) + merger.append(fileobj=class1) + + # Write merged PDF to class.pdf + output = open(os.path.join(BASE_DIR, "latex", "class.pdf"), "wb") + merger.write(output) + output.close() # Read and response PDF - file = open(os.path.join("latex", "class.pdf"), "rb") + file = open(os.path.join(BASE_DIR, "latex", "class.pdf"), "rb") return FileResponse(file, content_type="application/pdf") diff --git a/schoolapps/untisconnect/api.py b/schoolapps/untisconnect/api.py index 598b00ffcad34eba66707d1acd1c193d114ea13c..5a532aad40cada39768c96f83b63a84386baaede 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,37 @@ 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) + + +def format_classes(classes): + """ + Formats a list of Class objects to a combined string + + example return: "9abcd" for classes 9a, 9b, 9c and 9d + + :param classes: Class list + :return: combined string + """ + classes_as_dict = {} + + for _class in classes: + step = _class.name[:-1] + part = _class.name[-1:] + if step not in classes_as_dict.keys(): + classes_as_dict[step] = [part] + else: + classes_as_dict[step].append(part) + + out = "" + for key, value in classes_as_dict.items(): + out += key + "".join(value) + return out + + ######## # ROOM # ######## @@ -185,7 +222,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) @@ -208,8 +245,18 @@ class Subject(object): self.shortcode = db_obj.name self.name = db_obj.longname self.color = db_obj.backcolor + + # Convert UNTIS number to HEX hex_bgr = str(hex(db_obj.backcolor)).replace("0x", "") - hex_rgb = hex_bgr[4:5] + hex_bgr[2:3] + hex_bgr[0:1] + + # Add beginning zeros if len < 6 + if len(hex_bgr) < 6: + hex_bgr = "0" * (6 - len(hex_bgr)) + hex_bgr + + # Change BGR to RGB + hex_rgb = hex_bgr[4:6] + hex_bgr[2:4] + hex_bgr[0:2] + + # Add html # self.hex_color = "#" + hex_rgb diff --git a/schoolapps/untisconnect/parse.py b/schoolapps/untisconnect/parse.py index 1e288895e52ef2c366fb33cdb19006929ddc64bc..7df3cb508d215d3d366df2e79d8856b7bfedb7b4 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(",") @@ -60,7 +55,6 @@ class Lesson(object): for i, el in enumerate(rld2): teacher_id = int(el[0]) subject_id = int(el[2]) - room_ids = untis_split_third(el[4], int) class_ids = untis_split_third(el[17], conv=int) # print("TEACHER – ", teacher_id, "; SUBJECT – ", subject_id, "; ROOMS – ", room_ids, "; CLASSES – ", # class_ids) @@ -75,14 +69,7 @@ class Lesson(object): else: subject = None - # rooms = self.times[i].rooms[i] - # for room_id in room_ids: - # r = drive["rooms"][room_id] - # rooms.append(r) rooms = [] - for room in rooms: - print(room) - print("--") classes = [] for class_id in class_ids: @@ -134,19 +121,13 @@ def build_drive(): "corridors": get_all_corridors(), } - drive = { - # "teachers": {}, - # "rooms": {}, - # "classes": {}, - # "subjects": {} - } + drive = {} for key, value in odrive.items(): drive[key] = {} for el in value: id = el.id drive[key][id] = el - print(drive) return drive @@ -174,39 +155,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,132 +164,35 @@ 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) - print(len(t.rooms)) + # print(t.rooms) + # print(len(t.rooms)) room = t.rooms[i] 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..c87e0431769000c7f698110e9d216914f71b2132 --- /dev/null +++ b/schoolapps/untisconnect/plan.py @@ -0,0 +1,196 @@ +import datetime + +from django.utils import timezone + +from schoolapps import settings +from schoolapps.settings import LESSONS +from untisconnect.api import format_classes +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 + if self.element is not None: + self.classes_formatted = format_classes(self.element.classes) + + +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..01b6fbf46ab0cdd480c5ca160880023a9dca13ed 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, format_classes 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" @@ -76,16 +76,21 @@ class Substitution(object): # print(db_obj.teacher_idlessn) if db_obj.teacher_idlessn != 0: self.teacher_old = drive["teachers"][db_obj.teacher_idlessn] + if db_obj.teacher_idsubst != 0: self.teacher_new = drive["teachers"][db_obj.teacher_idsubst] if self.teacher_old is not None and self.teacher_new.id == self.teacher_old.id: self.teacher_new = None + if self.teacher_old is None and self.teacher_new is not None: + self.teacher_old = self.teacher_new + self.teacher_new = None + 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 +122,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): @@ -154,6 +159,8 @@ class SubRow(object): def generate_teacher_row(sub, full=False): + # print(sub.id) + teacher = "" if sub.type == 1: teacher = "<s>{}</s>".format(sub.teacher_old.shortcode if not full else sub.teacher_old.name) @@ -163,7 +170,7 @@ def generate_teacher_row(sub, full=False): sub.teacher_new.shortcode if not full else sub.teacher_new.name) elif sub.teacher_new and not sub.teacher_old: teacher = "<strong>{}</strong>".format(sub.teacher_new.shortcode if not full else sub.teacher_new.name) - else: + elif sub.teacher_old: teacher = "<strong>{}</strong>".format(sub.teacher_old.shortcode if not full else sub.teacher_old.name) return teacher @@ -172,6 +179,8 @@ def generate_teacher_row(sub, full=False): def generate_subject_row(sub, full=False): if sub.type == 3: subject = "Aufsicht" + elif not sub.subject_new and not sub.subject_old: + subject = "" elif sub.type == 1 or sub.type == 2: subject = "<s>{}</s>".format(sub.subject_old.shortcode if not full else sub.subject_old.name) elif sub.subject_new and sub.subject_old: @@ -180,8 +189,6 @@ def generate_subject_row(sub, full=False): sub.subject_new.shortcode if not full else sub.subject_new.name) elif sub.subject_new and not sub.subject_old: subject = "<strong>{}</strong>".format(sub.subject_new.shortcode if not full else sub.subject_new.name) - elif not sub.subject_new and not sub.subject_old: - subject = "" else: subject = "<strong>{}</strong>".format(sub.subject_old.shortcode if not full else sub.subject_old.name) @@ -225,8 +232,9 @@ def generate_sub_table(subs): else: sub_row.lesson = "{}.".format(sub.lesson) - for class_ in sub.classes: - sub_row.classes += class_.name + # for class_ in sub.classes: + # sub_row.classes += class_.name + sub_row.classes = format_classes(sub.classes) sub_row.teacher = generate_teacher_row(sub) sub_row.teacher_full = generate_teacher_row(sub, full=True) @@ -235,6 +243,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 @@ -249,6 +264,43 @@ def generate_sub_table(subs): return sub_rows +class HeaderInformation: + def __init__(self): + self.missing_teachers = [] + self.missing_classes = [] + self.affected_teachers = [] + self.affected_classes = [] + self.rows = [] + + def is_box_needed(self): + return len(self.missing_teachers) > 0 or len(self.missing_classes) > 0 or len( + self.affected_teachers) > 0 or len(self.affected_classes) > 0 + + +def get_header_information(subs): + info = HeaderInformation() + for sub in subs: + if sub.teacher_old and sub.teacher_old not in info.affected_teachers: + info.affected_teachers.append(sub.teacher_old) + if sub.teacher_new and sub.teacher_new not in info.affected_teachers: + info.affected_teachers.append(sub.teacher_new) + # print(sub.teacher_old) + + for _class in sub.classes: + if _class not in info.affected_classes: + info.affected_classes.append(_class) + + if info.affected_teachers: + joined = ", ".join(sorted([x.shortcode for x in info.affected_teachers])) + # print(joined) + info.rows.append(("Betroffene Lehrkräfte", joined)) + + if info.affected_classes: + joined = ", ".join(sorted([x.name for x in info.affected_classes])) + info.rows.append(("Betroffene Klassen", joined)) + return info + + def get_substitutions_by_date(date): subs_raw = run_default_filter( run_using(models.Substitution.objects.filter(date=date_to_untis_date(date), deleted=0).order_by("classids", @@ -264,3 +316,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 diff --git a/schoolapps/userinformation.py b/schoolapps/userinformation.py index 184b36943095439fb833d0cfae9a9e4397d63b42..69bd84938444dcfe31aa44e4259d7c6d7d84672f 100644 --- a/schoolapps/userinformation.py +++ b/schoolapps/userinformation.py @@ -15,7 +15,7 @@ class UserInformation: def user_groups(user): raw_groups = user.groups.all() groups = [group.name for group in raw_groups] - print(groups) + # print(groups) return groups @staticmethod