Skip to content
Snippets Groups Projects
Commit a1a3355d authored by Jonathan Weth's avatar Jonathan Weth :keyboard: Committed by root
Browse files

Merge branch 'dev' into feature/advanced-error-reporting-to-user-dashboard

parents 3bc6b402 22ded6a9
No related branches found
No related tags found
1 merge request!86Merge school-apps
Showing
with 344 additions and 20 deletions
......@@ -76,4 +76,11 @@ Weitere Verbesserungen der vorangegeangen Versionen dieses Releases.
+ Einfachere Einbindung von React für REBUS
## 1.1.2 "Aebli"
* Bugfix in der PWA (zuerst Netzwerk, dann Cache)
\ No newline at end of file
* Bugfix in der PWA (zuerst Netzwerk, dann Cache)
## 1.1.3 "Aebli"
* Bugfix in REBUS (Submit wieder erlaubt) (#335)
* Klassenlehrkräfte werden im Plan angezeigt (#334)
## 1.1.4 "Aebli"
* Bugfix in der PWA (Offline-Fallback-Page wird nicht mehr bei teils aktiver Netzwerkverbindung fälschlicherweise angezeigt)
\ No newline at end of file
......@@ -33,7 +33,6 @@ GRANT SELECT ON Untis.* TO 'www-data'@'localhost';
### UNTIS-Beispieldaten importieren
Zum Testen kann die Datei `untiskath.sql` vom Forum in die Datenbank `Untis` importiert werden.
### SchoolApps clonen
```
git clone git@github.com:Katharineum/school-apps.git
......@@ -49,7 +48,6 @@ pip install -r requirements.txt
- `example_secure_settings.py` zu `secure_settings.py` kopieren und anpassen (hier müssen auch die passenden DB-Zugangsdaten eingetragen werden)
### Migrations durchführen/auflösen
Leider kommt es bei einer Erstinstallation von SchoolApps immer noch zu Problemen mit den Migrations. Sollte es Schwierigkeiten geben, @hansegucker kontaktieren.
......
......@@ -2,6 +2,6 @@
SchoolApps ist ein modulares System zum Bereitstellen von speziellen Anwendungen für den Schulalltag. Besonderer Fokus liegt dabei auf dem Stundenplansystem.
© 2018 by [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank-Poetzsch-Heffter](mailto:p-h@katharineum.de)
© 2018 by [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank Poetzsch-Heffter](mailto:p-h@katharineum.de)
© 2019 by [Hangzhi Yu](mailto:yuha@katharineum.de), [Julian Leucker](mailto:leuckeju@katharineum.de), [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank-Poetzsch-Heffter](mailto:p-h@katharineum.de)
\ No newline at end of file
© 2019 by [Hangzhi Yu](mailto:yuha@katharineum.de), [Julian Leucker](mailto:leuckeju@katharineum.de), [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank Poetzsch-Heffter](mailto:p-h@katharineum.de)
......@@ -222,9 +222,7 @@ function getCategoryOfOption(option) {
function getOption(option) {
for (const category of BASIC_OPTIONS) {
// console.log(category);
for (const opt of category.options) {
// console.log(opt);
if (opt.id === option) {
return opt;
}
......@@ -388,7 +386,7 @@ class REBUSDynSelect extends React.Component {
<Input label={"Art des Problems"} icon={"bug_report"} show={sO === "printerIssue"}>
<Select onChange={this._onSetB}
values={["Papierstau", "Toner leer", "Papier leer", "Drucker bekommt keine Daten"]}
defaultValue={"Anderes Problem"} show={sO === "subMonitorIssue"}/>
defaultValue={"Anderes Problem"} show={sO === "printerIssue"}/>
</Input>
{/* Section B – Substitution Monitor Issue */}
......@@ -432,7 +430,7 @@ class REBUSDynSelect extends React.Component {
<Input label={"Ort des Druckers"} icon={"location_on"}
show={sO === "printerIssue" && step === 2}>
<Select onChange={this._onSetC} values={LOCATIONS}
defaultValue={"Anderer Raum"} show={sO === "presentationDeviceIssue"}/>
defaultValue={"Anderer Raum"} show={sO === "printerIssue"}/>
</Input>
{/* Section C – WLAN Issue */}
......
......@@ -4,6 +4,7 @@ from django.utils import timezone
from datetime import datetime
from material import Layout, Row, Fieldset
from aub.models import Aub
# from tinymce.widgets import TinyMCE
class ApplyForAUBForm(forms.ModelForm):
......@@ -17,6 +18,7 @@ class ApplyForAUBForm(forms.ModelForm):
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': '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.', widget=TinyMCE(attrs={'cols': 80, 'rows': 30}))
description = forms.CharField(label='Bitte begründen Sie Ihren Antrag.')
layout = Layout(Fieldset('Von',
......@@ -33,6 +35,9 @@ class ApplyForAUBForm(forms.ModelForm):
model = Aub
fields = ('id', 'from_date', 'from_time', 'to_date', 'to_time', 'description')
class Media:
js = ('/media/tinymce/jscripts/tiny_mce/tiny_mce.js', '',)
def clean(self):
cleaned_data = super().clean()
......
......@@ -45,7 +45,6 @@ class Aub(models.Model):
created_at = models.DateTimeField(default=timezone.now, verbose_name="Erstellungszeitpunkt")
def getStatus(self):
print(self.status, self.created_by, self.id)
return status_list[self.status]
def __str__(self):
......
......@@ -33,7 +33,7 @@
<div class="col s12 m4">
<p>
{% if aub.status == 0 %}
<form action="{% url 'aub_edit' aub.id %}" class="right">
<form action="{% url 'aub_edit' aub.id %}" class="left">
{% csrf_token %}
<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>
......@@ -41,7 +41,7 @@
</form>
{% endif %}
{% if aub.status == 0 or aub.status == 1 %}
<form action="" method="POST" class="right">
<form action="" method="POST" class="left">
{% 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">
......
......@@ -196,7 +196,10 @@ def check2(request):
def archive(request):
order_crit = '-from_date'
if 'created_by' in request.GET:
item = int(request.GET['created_by'])
try:
item = int(request.GET['created_by'])
except ValueError:
item = ''
aub_list = Aub.objects.filter((Q(status__exact=2) | Q(status__exact=3)) & Q(created_by=item)).order_by(order_crit)
else:
aub_list = Aub.objects.filter(Q(status__exact=2) | Q(status__exact=3)).order_by(order_crit)
......
......@@ -13,8 +13,8 @@ class Migration(migrations.Migration):
name='Cache',
fields=[
('id',
models.CharField(max_length=200, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Name')),
models.CharField(max_length=150, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
('name', models.CharField(max_length=150, verbose_name='Name')),
('expiration_time', models.IntegerField(default=20, verbose_name='Ablaufzeit')),
],
options={
......
......@@ -11,7 +11,7 @@ from mailer import send_mail_with_template
class Activity(models.Model):
user = models.ForeignKey(to=User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
title = models.CharField(max_length=150)
description = models.TextField(max_length=500)
app = models.CharField(max_length=100)
......@@ -24,7 +24,7 @@ class Activity(models.Model):
class Notification(models.Model):
user = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="notifications")
title = models.CharField(max_length=200)
title = models.CharField(max_length=150)
description = models.TextField(max_length=500)
link = models.URLField(blank=True)
......@@ -48,8 +48,8 @@ def register_notification(user, title, description, app="SchoolApps", link=""):
class Cache(models.Model):
id = models.CharField(max_length=200, unique=True, primary_key=True, verbose_name="ID")
name = models.CharField(max_length=200, verbose_name="Name")
id = models.CharField(max_length=150, unique=True, primary_key=True, verbose_name="ID")
name = models.CharField(max_length=150, verbose_name="Name")
expiration_time = models.IntegerField(default=20, verbose_name="Ablaufzeit")
last_time_updated = models.DateTimeField(blank=True, null=True,
verbose_name="Letzter Aktualisierungszeitpunkt des Caches")
......
# SchoolApps: FiBu (Finanzbuchhaltung)
## Workflow
1. Berechtigungen setzen
+ Anwender: request_booking
+ Buchhalter: manage_booking
+ Manager: manage_costcenter, manage_account
2. Manager richtet Kostenstellen (z.B. Schulträger-konsumtiv,
Schulträger-investiv, Elternverein, Fundraising, ...) sowie
Buchungskonten (z.B. Klassenraum-Festwert, Geschäftszimmer,
Fachschaft Mathematik, ...) ein und setzt dabei Budgets fest,
die in der Summe den Haushaltsplan ergeben.
3. Anwender stellt Anträge für Ausgaben.
4. Manager bewilligt Anträge unter Zuordnung zum passenden
Buchungskonto oder lehnt sie ab.
5. Anwender gibt für bewilligte Anträge Bestellung auf und
ändert den Status der Buchung entsprechend.
6. Anwender reicht Rechnung im Geschäftszimmer ein und ändert
den Status der Buchung entsprechend.
7. Buchhalter vervollständigt die Buchung, weist die Rechnung
an und ändert den Status auf bezahlt.
8. Buchhalter und Manager kontrollieren durch Aufruf von Berichten
den Haushalt.
\ No newline at end of file
from django.contrib import admin
from .models import CostCenter, Account, Booking
admin.site.register(CostCenter)
admin.site.register(Account)
admin.site.register(Booking)
from django.apps import AppConfig
class FibuConfig(AppConfig):
name = 'fibu'
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth import REDIRECT_FIELD_NAME
from .models import Booking
# prevent to show booking details from foreign users
def check_own_booking_verification(user):
return Booking.objects.all().filter(created_by=user)
def check_own_booking(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Decorator for views that checks that the user only gets his own bookings, redirecting
to the dashboard if necessary.
"""
actual_decorator = user_passes_test(
check_own_booking_verification,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
from django.contrib.auth.models import User
import django_filters
from .models import Booking
from django.db.utils import ProgrammingError
def get_fibu_users():
""" Find all users who requests a boooking """
try:
fibu_users = Booking.objects.values_list('contact')
users = list(User.objects.filter(id__in=fibu_users))
# user_ids = [(str(user.id),user.username) for user in users]
user_ids = [(str(user.id), user.last_name + ', ' + user.first_name) for user in users]
user_ids_sorted = sorted(user_ids, key=lambda user: user[1])
return user_ids_sorted
except ProgrammingError:
return []
class BookingFilter(django_filters.FilterSet):
contact = django_filters.ChoiceFilter(label='Von', choices=get_fibu_users())
class Meta:
model = Booking
fields = ['contact', ]
from django import forms
from django.utils import timezone
from material import Layout, Row, Fieldset
from .models import Booking, CostCenter, Account
class SimpleBookingForm(forms.ModelForm):
description = forms.CharField(label='Beschreibung – Was soll angeschafft werden?')
planned_amount = forms.IntegerField(
label='Erwarteter Betrag – Welcher Betrag ist erforderlich?', help_text="in Euro, ohne Komma")
justification = forms.CharField(label='Begründung – Begründe ggf. deinen Antrag.', required=False)
layout = Layout(Row('description', 'planned_amount'), Row('justification'))
class Meta:
model = Booking
fields = ['id', 'description', 'planned_amount', 'justification']
class CheckBookingForm(forms.ModelForm):
account = forms.ModelChoiceField(Account.objects.filter().order_by('cost_center', 'name'))
class Meta:
model = Booking
fields = ['account', ]
class CompleteBookingForm(forms.ModelForm):
accounts = Account.objects.filter().order_by('cost_center', 'name')
account = forms.ModelChoiceField(queryset=accounts)
submission_date = forms.DateField(label='Bearbeitungsdatum', initial=timezone.now())
layout = Layout(Fieldset("Allgemeines",
Row('description', 'justification'),
Row("contact", "planned_amount"),
Row('account', 'status')
),
Fieldset('Details',
Row('firma', 'invoice_number', 'amount'),
Row('invoice_date', 'maturity', 'submission_date', 'booking_date'),
Row('payout_number', 'upload')
)
)
class Meta:
model = Booking
fields = ['id', 'description', 'planned_amount', 'justification', 'account', 'contact', 'invoice_date',
'invoice_number', 'firma', 'amount', 'submission_date', 'payout_number', 'booking_date',
'maturity', 'upload', 'status']
class CostCenterForm(forms.ModelForm):
class Meta:
model = CostCenter
fields = ['id', 'name', 'year']
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = ['id', 'name', 'cost_center', 'income', 'budget']
# Generated by Django 2.2.8 on 2019-12-27 06:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Account',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='', max_length=20)),
('income', models.BooleanField(default=False)),
('budget', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
('rest', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
],
options={
'permissions': [('manage_account', 'Can manage account')],
},
),
migrations.CreateModel(
name='CostCenter',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=30)),
('year', models.IntegerField(choices=[(2020, '2020'), (2021, '2021'), (2022, '2022'), (2023, '2023')],
default=2019, verbose_name='Jahr')),
],
options={
'permissions': [('manage_costcenter', 'Can manage costcenter')],
},
),
migrations.CreateModel(
name='Booking',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('invoice_date', models.DateField(default='2000-12-31')),
('invoice_number', models.CharField(default='0', max_length=20)),
('firma', models.CharField(default='', max_length=30)),
('description', models.CharField(max_length=50)),
('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
('planned_amount', models.IntegerField()),
('submission_date', models.DateField(default='2000-12-31')),
('justification', models.CharField(blank=True, max_length=2000, null=True)),
('payout_number', models.IntegerField(default=0)),
('booking_date', models.DateField(default='2000-12-31')),
('maturity', models.DateField(default='2000-12-31')),
('upload', models.FileField(blank=True, default=None, null=True, upload_to='uploads/fibu/%Y/')),
('status', models.IntegerField(choices=[(0, 'beantragt'), (1, 'bewilligt'), (2, 'abgelehnt'), (3, 'bestellt'), (4, 'eingereicht'), (5, 'bezahlt')], default=0, verbose_name='Status')),
('account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='fibu.Account')),
('contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='Erstellt von')),
],
options={
'permissions': [('manage_booking', 'Can manage bookings'), ('request_booking', 'Can request a booking')],
},
),
migrations.AddField(
model_name='account',
name='costcenter',
field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, to='fibu.CostCenter'),
),
]
# Generated by Django 2.2.6 on 2019-12-28 12:57
import datetime
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('fibu', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='account',
options={'permissions': [('manage_account', 'Can manage account')], 'verbose_name': 'Buchungskonto',
'verbose_name_plural': 'Buchungskonten'},
),
migrations.AlterModelOptions(
name='booking',
options={
'permissions': [('manage_booking', 'Can manage bookings'), ('request_booking', 'Can request a booking'),
('check_booking', 'Can check bookings')], 'verbose_name': 'Buchung',
'verbose_name_plural': 'Buchungen'},
),
migrations.AlterModelOptions(
name='costcenter',
options={'permissions': [('manage_costcenter', 'Can manage costcenter')], 'verbose_name': 'Kostenstelle',
'verbose_name_plural': 'Kostenstellen'},
),
migrations.AlterField(
model_name='account',
name='budget',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=9, verbose_name='Budget'),
),
migrations.AlterField(
model_name='account',
name='costcenter',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fibu.CostCenter',
verbose_name='Kostenstelle'),
),
migrations.AlterField(
model_name='account',
name='income',
field=models.BooleanField(default=False, verbose_name='Einnahmekonto'),
),
migrations.AlterField(
model_name='account',
name='name',
field=models.CharField(max_length=20, verbose_name='Buchungskonto'),
),
migrations.AlterField(
model_name='booking',
name='booking_date',
field=models.DateField(default=datetime.date.today),
),
migrations.AlterField(
model_name='booking',
name='invoice_date',
field=models.DateField(default=datetime.date.today),
),
migrations.AlterField(
model_name='booking',
name='maturity',
field=models.DateField(default=datetime.date.today),
),
migrations.AlterField(
model_name='booking',
name='status',
field=models.IntegerField(
choices=[(0, 'beantragt'), (1, 'abgelehnt'), (2, 'bewilligt'), (3, 'bestellt'), (4, 'eingereicht'),
(5, 'bezahlt')], default=0, verbose_name='Status'),
),
migrations.AlterField(
model_name='booking',
name='submission_date',
field=models.DateField(default=datetime.date.today),
),
migrations.AlterField(
model_name='costcenter',
name='name',
field=models.CharField(max_length=30, verbose_name='Kostenstelle'),
),
migrations.AlterField(
model_name='costcenter',
name='year',
field=models.IntegerField(choices=[(2019, '2019'), (2020, '2020'), (2021, '2021'), (2022, '2022')],
default=2019, verbose_name='Jahr'),
),
]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment