Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Alsijil
  • sunweaver/AlekSIS-App-Alsijil
  • 8tincsoVluke/AlekSIS-App-Alsijil
  • perfreicpo/AlekSIS-App-Alsijil
  • noifobarep/AlekSIS-App-Alsijil
  • 7ingannisdo/AlekSIS-App-Alsijil
  • unmruntartpa/AlekSIS-App-Alsijil
  • balrorebta/AlekSIS-App-Alsijil
  • comliFdifwa/AlekSIS-App-Alsijil
  • 3ranaadza/AlekSIS-App-Alsijil
10 results
Show changes
Commits on Source (1)
import pkg_resources import pkg_resources
try: try:
__version__ = pkg_resources.get_distribution('BiscuIT-App-Alsijil').version __version__ = pkg_resources.get_distribution("BiscuIT-App-Alsijil").version
except Exception: except Exception:
__version__ = 'unknown' __version__ = "unknown"
default_app_config = 'biscuit.apps.alsijil.apps.AlsijilConfig' default_app_config = "biscuit.apps.alsijil.apps.AlsijilConfig"
...@@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig ...@@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig
class AlsijilConfig(AppConfig): class AlsijilConfig(AppConfig):
name = 'biscuit.apps.alsijil' name = "biscuit.apps.alsijil"
verbose_name = 'BiscuIT - Alsijil (Class register)' verbose_name = "BiscuIT - Alsijil (Class register)"
from datetime import datetime
from django import forms from django import forms
from django.db.models import Count from django.db.models import Count
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_select2.forms import Select2Widget from django_select2.forms import Select2Widget
from datetime import datetime
from biscuit.apps.chronos.models import Room from biscuit.apps.chronos.models import Room
from biscuit.core.models import Group, Person from biscuit.core.models import Group, Person
...@@ -13,54 +15,75 @@ from .models import LessonDocumentation, PersonalNote, PersonalNoteFilter ...@@ -13,54 +15,75 @@ from .models import LessonDocumentation, PersonalNote, PersonalNoteFilter
class LessonDocumentationForm(forms.ModelForm): class LessonDocumentationForm(forms.ModelForm):
class Meta: class Meta:
model = LessonDocumentation model = LessonDocumentation
fields = ['topic', 'homework'] fields = ["topic", "homework"]
class PersonalNoteForm(forms.ModelForm): class PersonalNoteForm(forms.ModelForm):
class Meta: class Meta:
model = PersonalNote model = PersonalNote
fields = ['absent', 'late', 'excused', 'remarks'] fields = ["absent", "late", "excused", "remarks"]
person_name = forms.CharField(disabled=True) person_name = forms.CharField(disabled=True)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['person_name'].widget.attrs.update( self.fields["person_name"].widget.attrs.update(
{'class': 'alsijil-lesson-personal-note-name'}) {"class": "alsijil-lesson-personal-note-name"}
)
if self.instance and getattr(self.instance, 'person', None): if self.instance and getattr(self.instance, "person", None):
self.fields['person_name'].initial = str(self.instance.person) self.fields["person_name"].initial = str(self.instance.person)
class SelectForm(forms.Form): class SelectForm(forms.Form):
group = forms.ModelChoiceField( group = forms.ModelChoiceField(
queryset=Group.objects.annotate(lessons_count=Count('lessons')).filter(lessons_count__gt=0), queryset=Group.objects.annotate(lessons_count=Count("lessons")).filter(
label=_('Group'), required=False, widget=Select2Widget) lessons_count__gt=0
),
label=_("Group"),
required=False,
widget=Select2Widget,
)
teacher = forms.ModelChoiceField( teacher = forms.ModelChoiceField(
queryset=Person.objects.annotate(lessons_count=Count( queryset=Person.objects.annotate(
'lessons_as_teacher')).filter(lessons_count__gt=0), lessons_count=Count("lessons_as_teacher")
label=_('Teacher'), required=False, widget=Select2Widget) ).filter(lessons_count__gt=0),
label=_("Teacher"),
required=False,
widget=Select2Widget,
)
room = forms.ModelChoiceField( room = forms.ModelChoiceField(
queryset=Room.objects.annotate(lessons_count=Count( queryset=Room.objects.annotate(lessons_count=Count("lesson_periods")).filter(
'lesson_periods')).filter(lessons_count__gt=0), lessons_count__gt=0
label=_('Room'), required=False, widget=Select2Widget) ),
label=_("Room"),
required=False,
widget=Select2Widget,
)
PersonalNoteFormSet = forms.modelformset_factory( PersonalNoteFormSet = forms.modelformset_factory(
PersonalNote, form=PersonalNoteForm, max_num=0, extra=0) PersonalNote, form=PersonalNoteForm, max_num=0, extra=0
)
class RegisterAbsenceForm(forms.Form): class RegisterAbsenceForm(forms.Form):
date_start = forms.DateField(label=_('Start date'), widget=forms.SelectDateWidget, initial=datetime.today) date_start = forms.DateField(
date_end = forms.DateField(label=_('End date'), widget=forms.SelectDateWidget, initial=datetime.today) label=_("Start date"), widget=forms.SelectDateWidget, initial=datetime.today
from_period = forms.IntegerField(label=_('From period'), initial=0, min_value=0) )
person = forms.ModelChoiceField(label=_('Person'), queryset=Person.objects.all(), widget=Select2Widget) date_end = forms.DateField(
absent = forms.BooleanField(label=_('Absent'), initial=True, required=False) label=_("End date"), widget=forms.SelectDateWidget, initial=datetime.today
excused = forms.BooleanField(label=_('Excused'), initial=True, required=False) )
remarks = forms.CharField(label=_('Remarks'), max_length=30, required=False) from_period = forms.IntegerField(label=_("From period"), initial=0, min_value=0)
person = forms.ModelChoiceField(
label=_("Person"), queryset=Person.objects.all(), widget=Select2Widget
)
absent = forms.BooleanField(label=_("Absent"), initial=True, required=False)
excused = forms.BooleanField(label=_("Excused"), initial=True, required=False)
remarks = forms.CharField(label=_("Remarks"), max_length=30, required=False)
class PersonalNoteFilterForm(forms.ModelForm): class PersonalNoteFilterForm(forms.ModelForm):
class Meta: class Meta:
model = PersonalNoteFilter model = PersonalNoteFilter
fields = ['identifier', 'description', 'regex'] fields = ["identifier", "description", "regex"]
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
MENUS = { MENUS = {
'NAV_MENU_CORE': [ "NAV_MENU_CORE": [
{ {
'name': _('Class register'), "name": _("Class register"),
'url': '#', "url": "#",
'root': True, "root": True,
'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'], "validators": [
'submenu': [ "menu_generator.validators.is_authenticated",
"biscuit.core.util.core_helpers.has_person",
],
"submenu": [
{ {
'name': _('Current lesson'), "name": _("Current lesson"),
'url': 'lesson', "url": "lesson",
'validators': ['menu_generator.validators.is_authenticated'] "validators": ["menu_generator.validators.is_authenticated"],
}, },
{ {
'name': _('Current week'), "name": _("Current week"),
'url': 'week_view', "url": "week_view",
'validators': ['menu_generator.validators.is_authenticated'] "validators": ["menu_generator.validators.is_authenticated"],
}, },
{ {
'name': _('Register absence'), "name": _("Register absence"),
'url': 'register_absence', "url": "register_absence",
'validators': ['menu_generator.validators.is_superuser'] "validators": ["menu_generator.validators.is_superuser"],
}, },
{ {
'name': _('Personal note filters'), "name": _("Personal note filters"),
'url': 'list_personal_note_filters', "url": "list_personal_note_filters",
'validators': ['menu_generator.validators.is_superuser'] "validators": ["menu_generator.validators.is_superuser"],
} },
] ],
} }
] ]
} }
# Generated by Django 2.2.5 on 2019-09-03 18:30 # Generated by Django 2.2.5 on 2019-09-03 18:30
import biscuit.core.util.core_helpers
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
import biscuit.core.util.core_helpers
class Migration(migrations.Migration): class Migration(migrations.Migration):
...@@ -10,42 +11,114 @@ class Migration(migrations.Migration): ...@@ -10,42 +11,114 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('chronos', '0001_initial'), ("chronos", "0001_initial"),
('core', '0001_initial'), ("core", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='PersonalNote', name="PersonalNote",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('week', models.IntegerField()), "id",
('absent', models.BooleanField(default=False)), models.AutoField(
('late', models.IntegerField(default=0)), auto_created=True,
('excused', models.BooleanField(default=False)), primary_key=True,
('remarks', models.CharField(blank=True, max_length=200)), serialize=False,
('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chronos.LessonPeriod')), verbose_name="ID",
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='core.Person')), ),
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), ),
("week", models.IntegerField()),
("absent", models.BooleanField(default=False)),
("late", models.IntegerField(default=0)),
("excused", models.BooleanField(default=False)),
("remarks", models.CharField(blank=True, max_length=200)),
(
"lesson_period",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="chronos.LessonPeriod",
),
),
(
"person",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="personal_notes",
to="core.Person",
),
),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
], ],
options={ options={
'ordering': ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period', 'person__last_name', 'person__first_name'], "ordering": [
'unique_together': {('school', 'lesson_period', 'week', 'person')}, "lesson_period__lesson__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
"person__last_name",
"person__first_name",
],
"unique_together": {("school", "lesson_period", "week", "person")},
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='LessonDocumentation', name="LessonDocumentation",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('week', models.IntegerField()), "id",
('topic', models.CharField(blank=True, max_length=200, verbose_name='Lesson topic')), models.AutoField(
('homework', models.CharField(blank=True, max_length=200, verbose_name='Homework')), auto_created=True,
('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documentations', to='chronos.LessonPeriod')), primary_key=True,
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), serialize=False,
verbose_name="ID",
),
),
("week", models.IntegerField()),
(
"topic",
models.CharField(
blank=True, max_length=200, verbose_name="Lesson topic"
),
),
(
"homework",
models.CharField(
blank=True, max_length=200, verbose_name="Homework"
),
),
(
"lesson_period",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="documentations",
to="chronos.LessonPeriod",
),
),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
], ],
options={ options={
'ordering': ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period'], "ordering": [
'unique_together': {('school', 'lesson_period', 'week')}, "lesson_period__lesson__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
],
"unique_together": {("school", "lesson_period", "week")},
}, },
), ),
] ]
# Generated by Django 2.2.5 on 2019-11-20 14:21 # Generated by Django 2.2.5 on 2019-11-20 14:21
import django.db.models.deletion
from django.db import migrations, models
import biscuit.apps.alsijil.models import biscuit.apps.alsijil.models
import biscuit.core.util.core_helpers import biscuit.core.util.core_helpers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0003_school_logo'), ("core", "0003_school_logo"),
('alsijil', '0001_initial'), ("alsijil", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='PersonalNoteFilter', name="PersonalNoteFilter",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('identifier', models.CharField(max_length=30, validators=[biscuit.apps.alsijil.models.isidentifier], verbose_name='Identifier')), "id",
('description', models.CharField(blank=True, max_length=60, verbose_name='Description')), models.AutoField(
('regex', models.CharField(max_length=100, verbose_name='Match expression')), auto_created=True,
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"identifier",
models.CharField(
max_length=30,
validators=[biscuit.apps.alsijil.models.isidentifier],
verbose_name="Identifier",
),
),
(
"description",
models.CharField(
blank=True, max_length=60, verbose_name="Description"
),
),
(
"regex",
models.CharField(max_length=100, verbose_name="Match expression"),
),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
], ],
options={ options={
'ordering': ['identifier'], "ordering": ["identifier"],
'unique_together': {('school', 'regex'), ('school', 'description'), ('school', 'identifier')}, "unique_together": {
("school", "regex"),
("school", "description"),
("school", "identifier"),
},
}, },
), ),
] ]
...@@ -10,7 +10,14 @@ from .models import PersonalNote ...@@ -10,7 +10,14 @@ from .models import PersonalNote
@Person.method @Person.method
def mark_absent(self, day: date, from_period: int = 0, absent: bool = True, excused: bool = False, remarks: str = ''): def mark_absent(
self,
day: date,
from_period: int = 0,
absent: bool = True,
excused: bool = False,
remarks: str = "",
):
""" Mark a person absent for all lessons in a day, optionally starting with """ Mark a person absent for all lessons in a day, optionally starting with
a selected period number. a selected period number.
...@@ -27,9 +34,7 @@ def mark_absent(self, day: date, from_period: int = 0, absent: bool = True, excu ...@@ -27,9 +34,7 @@ def mark_absent(self, day: date, from_period: int = 0, absent: bool = True, excu
wanted_week = CalendarWeek.from_date(day) wanted_week = CalendarWeek.from_date(day)
# Get all lessons of this person on the specified day # Get all lessons of this person on the specified day
lesson_periods = self.lesson_periods_as_participant.on_day( lesson_periods = self.lesson_periods_as_participant.on_day(day).filter(
day
).filter(
period__period__gte=from_period period__period__gte=from_period
) )
...@@ -39,15 +44,12 @@ def mark_absent(self, day: date, from_period: int = 0, absent: bool = True, excu ...@@ -39,15 +44,12 @@ def mark_absent(self, day: date, from_period: int = 0, absent: bool = True, excu
person=self, person=self,
lesson_period=lesson_period, lesson_period=lesson_period,
week=wanted_week.week, week=wanted_week.week,
defaults={ defaults={"absent": absent, "excused": excused},
'absent': absent,
'excused': excused
}
) )
if remarks: if remarks:
if personal_note.remarks: if personal_note.remarks:
personal_note.remarks += '; %s' % remarks personal_note.remarks += "; %s" % remarks
else: else:
personal_note.remarks = remarks personal_note.remarks = remarks
personal_note.save() personal_note.save()
...@@ -69,22 +71,25 @@ def get_personal_notes(self, wanted_week: CalendarWeek): ...@@ -69,22 +71,25 @@ def get_personal_notes(self, wanted_week: CalendarWeek):
# Find all persons in the associated groups that do not yet have a personal note for this lesson # Find all persons in the associated groups that do not yet have a personal note for this lesson
missing_persons = Person.objects.annotate( missing_persons = Person.objects.annotate(
no_personal_notes=~Exists(PersonalNote.objects.filter( no_personal_notes=~Exists(
week=wanted_week.week, PersonalNote.objects.filter(
lesson_period=self, week=wanted_week.week, lesson_period=self, person__pk=OuterRef("pk")
person__pk=OuterRef('pk') )
)) )
).filter( ).filter(
member_of__in=Group.objects.filter(pk__in=self.lesson.groups.all()), member_of__in=Group.objects.filter(pk__in=self.lesson.groups.all()),
is_active=True, is_active=True,
no_personal_notes=True no_personal_notes=True,
) )
# Create all missing personal notes # Create all missing personal notes
PersonalNote.objects.bulk_create([ PersonalNote.objects.bulk_create(
PersonalNote(person=person, lesson_period=self, [
week=wanted_week.week) for person in missing_persons PersonalNote(person=person, lesson_period=self, week=wanted_week.week)
]) for person in missing_persons
]
)
return PersonalNote.objects.select_related('person').filter( return PersonalNote.objects.select_related("person").filter(
lesson_period=self, week=wanted_week.week) lesson_period=self, week=wanted_week.week
)
...@@ -13,10 +13,14 @@ class PersonalNote(models.Model): ...@@ -13,10 +13,14 @@ class PersonalNote(models.Model):
absences, excuses and remarks about a student in a single lesson period. absences, excuses and remarks about a student in a single lesson period.
""" """
person = models.ForeignKey('core.Person', models.CASCADE, related_name='personal_notes') person = models.ForeignKey(
"core.Person", models.CASCADE, related_name="personal_notes"
)
week = models.IntegerField() week = models.IntegerField()
lesson_period = models.ForeignKey('chronos.LessonPeriod', models.CASCADE, related_name='personal_notes') lesson_period = models.ForeignKey(
"chronos.LessonPeriod", models.CASCADE, related_name="personal_notes"
)
absent = models.BooleanField(default=False) absent = models.BooleanField(default=False)
late = models.IntegerField(default=0) late = models.IntegerField(default=0)
...@@ -25,9 +29,15 @@ class PersonalNote(models.Model): ...@@ -25,9 +29,15 @@ class PersonalNote(models.Model):
remarks = models.CharField(max_length=200, blank=True) remarks = models.CharField(max_length=200, blank=True)
class Meta: class Meta:
unique_together = [['lesson_period', 'week', 'person']] unique_together = [["lesson_period", "week", "person"]]
ordering = ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', ordering = [
'lesson_period__period__period', 'person__last_name', 'person__first_name'] "lesson_period__lesson__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
"person__last_name",
"person__first_name",
]
class LessonDocumentation(CRUDMixin): class LessonDocumentation(CRUDMixin):
...@@ -37,27 +47,38 @@ class LessonDocumentation(CRUDMixin): ...@@ -37,27 +47,38 @@ class LessonDocumentation(CRUDMixin):
week = models.IntegerField() week = models.IntegerField()
lesson_period = models.ForeignKey( lesson_period = models.ForeignKey(
'chronos.LessonPeriod', models.CASCADE, related_name='documentations') "chronos.LessonPeriod", models.CASCADE, related_name="documentations"
)
topic = models.CharField(verbose_name=_('Lesson topic'), max_length=200, blank=True) topic = models.CharField(verbose_name=_("Lesson topic"), max_length=200, blank=True)
homework = models.CharField(verbose_name=_('Homework'), max_length=200, blank=True) homework = models.CharField(verbose_name=_("Homework"), max_length=200, blank=True)
class Meta: class Meta:
unique_together = [['lesson_period', 'week']] unique_together = [["lesson_period", "week"]]
ordering = ['lesson_period__lesson__date_start', 'week', ordering = [
'lesson_period__period__weekday', 'lesson_period__period__period'] "lesson_period__lesson__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
]
class PersonalNoteFilter(models.Model): class PersonalNoteFilter(models.Model):
""" A filter definition that can generate statistics on personal note texts. """ """ A filter definition that can generate statistics on personal note texts. """
identifier = models.CharField(verbose_name=_('Identifier'), max_length=30, identifier = models.CharField(
validators=[isidentifier], unique=True) verbose_name=_("Identifier"),
description = models.CharField(verbose_name=_('Description'), max_length=60, max_length=30,
blank=True, unique=True) validators=[isidentifier],
unique=True,
)
description = models.CharField(
verbose_name=_("Description"), max_length=60, blank=True, unique=True
)
regex = models.CharField(verbose_name=_('Match expression'), max_length=100, regex = models.CharField(
unique=True) verbose_name=_("Match expression"), max_length=100, unique=True
)
class Meta: class Meta:
ordering = ['identifier'] ordering = ["identifier"]
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import A from django_tables2.utils import A
class PersonalNoteFilterTable(tables.Table): class PersonalNoteFilterTable(tables.Table):
class Meta: class Meta:
attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'} attrs = {
"class": "table table-striped table-bordered table-hover table-responsive-xl"
}
identifier = tables.Column() identifier = tables.Column()
description = tables.Column() description = tables.Column()
regex = tables.Column() regex = tables.Column()
edit_filter = tables.LinkColumn( edit_filter = tables.LinkColumn(
'edit_personal_note_filter', args=[A('id')], text=_('Edit')) "edit_personal_note_filter", args=[A("id")], text=_("Edit")
)
...@@ -2,24 +2,37 @@ from django.urls import path ...@@ -2,24 +2,37 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('lesson', views.lesson, name='lesson'), path("lesson", views.lesson, name="lesson"),
path('lesson/<int:year>/<int:week>/<int:period_id>', views.lesson, path(
name='lesson_by_week_and_period'), "lesson/<int:year>/<int:week>/<int:period_id>",
path('week', views.week_view, name='week_view'), views.lesson,
path('week/<int:year>/<int:week>', views.week_view, name="lesson_by_week_and_period",
name='week_view_by_week'), ),
path('print/group/<int:id_>', views.full_register_group, path("week", views.week_view, name="week_view"),
name='full_register_group'), path("week/<int:year>/<int:week>", views.week_view, name="week_view_by_week"),
path('absence/new', views.register_absence, path(
name='register_absence'), "print/group/<int:id_>", views.full_register_group, name="full_register_group"
path('filters/list', views.list_personal_note_filters, ),
name='list_personal_note_filters'), path("absence/new", views.register_absence, name="register_absence"),
path('filters/create', views.edit_personal_note_filter, path(
name='create_personal_note_filter'), "filters/list",
path('filters/edit/<int:id>', views.edit_personal_note_filter, views.list_personal_note_filters,
name='edit_personal_note_filter'), name="list_personal_note_filters",
path('filters/delete/<int:id_>', views.delete_personal_note_filter, ),
name='delete_personal_note_filter') path(
"filters/create",
views.edit_personal_note_filter,
name="create_personal_note_filter",
),
path(
"filters/edit/<int:id>",
views.edit_personal_note_filter,
name="edit_personal_note_filter",
),
path(
"filters/delete/<int:id_>",
views.delete_personal_note_filter,
name="delete_personal_note_filter",
),
] ]
...@@ -13,17 +13,28 @@ from django_tables2 import RequestConfig ...@@ -13,17 +13,28 @@ from django_tables2 import RequestConfig
from biscuit.apps.chronos.models import LessonPeriod from biscuit.apps.chronos.models import LessonPeriod
from biscuit.apps.chronos.util import CalendarWeek from biscuit.apps.chronos.util import CalendarWeek
from biscuit.core.models import Group, Person, School
from biscuit.core.decorators import admin_required from biscuit.core.decorators import admin_required
from biscuit.core.models import Group, Person, School
from biscuit.core.util import messages from biscuit.core.util import messages
from .forms import LessonDocumentationForm, PersonalNoteFormSet, RegisterAbsenceForm, SelectForm, PersonalNoteFilterForm from .forms import (
LessonDocumentationForm,
PersonalNoteFilterForm,
PersonalNoteFormSet,
RegisterAbsenceForm,
SelectForm,
)
from .models import LessonDocumentation, PersonalNoteFilter from .models import LessonDocumentation, PersonalNoteFilter
from .tables import PersonalNoteFilterTable from .tables import PersonalNoteFilterTable
@login_required @login_required
def lesson(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None, period_id: Optional[int] = None) -> HttpResponse: def lesson(
request: HttpRequest,
year: Optional[int] = None,
week: Optional[int] = None,
period_id: Optional[int] = None,
) -> HttpResponse:
context = {} context = {}
if year and week and period_id: if year and week and period_id:
...@@ -32,34 +43,60 @@ def lesson(request: HttpRequest, year: Optional[int] = None, week: Optional[int] ...@@ -32,34 +43,60 @@ def lesson(request: HttpRequest, year: Optional[int] = None, week: Optional[int]
wanted_week = CalendarWeek(year=year, week=week) wanted_week = CalendarWeek(year=year, week=week)
else: else:
# Determine current lesson by current date and time # Determine current lesson by current date and time
lesson_period = LessonPeriod.objects.at_time().filter_teacher(request.user.person).first() lesson_period = (
LessonPeriod.objects.at_time().filter_teacher(request.user.person).first()
)
wanted_week = CalendarWeek() wanted_week = CalendarWeek()
if lesson_period: if lesson_period:
return redirect('lesson_by_week_and_period', wanted_week.year, wanted_week.week, lesson_period.pk) return redirect(
"lesson_by_week_and_period",
wanted_week.year,
wanted_week.week,
lesson_period.pk,
)
else: else:
raise Http404(_('You either selected an invalid lesson or there is currently no lesson in progress.')) raise Http404(
_(
"You either selected an invalid lesson or there is currently no lesson in progress."
)
)
if datetime.combine(wanted_week[lesson_period.period.weekday - 1], lesson_period.period.time_start) > datetime.now() and not request.user.is_superuser: if (
raise PermissionDenied(_('You are not allowed to create a lesson documentation for a lesson in the future.')) datetime.combine(
wanted_week[lesson_period.period.weekday - 1],
lesson_period.period.time_start,
)
> datetime.now()
and not request.user.is_superuser
):
raise PermissionDenied(
_(
"You are not allowed to create a lesson documentation for a lesson in the future."
)
)
context['lesson_period'] = lesson_period context["lesson_period"] = lesson_period
context['week'] = wanted_week context["week"] = wanted_week
context['day'] = wanted_week[lesson_period.period.weekday - 1] context["day"] = wanted_week[lesson_period.period.weekday - 1]
# Create or get lesson documentation object; can be empty when first opening lesson # Create or get lesson documentation object; can be empty when first opening lesson
lesson_documentation, created = LessonDocumentation.objects.get_or_create( lesson_documentation, created = LessonDocumentation.objects.get_or_create(
lesson_period=lesson_period, week=wanted_week.week) lesson_period=lesson_period, week=wanted_week.week
)
lesson_documentation_form = LessonDocumentationForm( lesson_documentation_form = LessonDocumentationForm(
request.POST or None, instance=lesson_documentation, prefix='leson_documentation') request.POST or None,
instance=lesson_documentation,
prefix="leson_documentation",
)
# Create a formset that holds all personal notes for all persons in this lesson # Create a formset that holds all personal notes for all persons in this lesson
persons_qs = lesson_period.get_personal_notes(wanted_week) persons_qs = lesson_period.get_personal_notes(wanted_week)
personal_note_formset = PersonalNoteFormSet( personal_note_formset = PersonalNoteFormSet(
request.POST or None, queryset=persons_qs, prefix='personal_notes') request.POST or None, queryset=persons_qs, prefix="personal_notes"
)
if request.method == 'POST': if request.method == "POST":
if lesson_documentation_form.is_valid(): if lesson_documentation_form.is_valid():
lesson_documentation_form.save() lesson_documentation_form.save()
...@@ -69,21 +106,23 @@ def lesson(request: HttpRequest, year: Optional[int] = None, week: Optional[int] ...@@ -69,21 +106,23 @@ def lesson(request: HttpRequest, year: Optional[int] = None, week: Optional[int]
# Iterate over personal notes and carry changed absences to following lessons # Iterate over personal notes and carry changed absences to following lessons
for instance in instances: for instance in instances:
instance.person.mark_absent( instance.person.mark_absent(
wanted_week[lesson_period.period.weekday-1], wanted_week[lesson_period.period.weekday - 1],
lesson_period.period.period+1, lesson_period.period.period + 1,
instance.absent, instance.absent,
instance.excused instance.excused,
) )
context['lesson_documentation'] = lesson_documentation context["lesson_documentation"] = lesson_documentation
context['lesson_documentation_form'] = lesson_documentation_form context["lesson_documentation_form"] = lesson_documentation_form
context['personal_note_formset'] = personal_note_formset context["personal_note_formset"] = personal_note_formset
return render(request, 'alsijil/lesson.html', context) return render(request, "alsijil/lesson.html", context)
@login_required @login_required
def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None) -> HttpResponse: def week_view(
request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None
) -> HttpResponse:
context = {} context = {}
if year and week: if year and week:
...@@ -92,21 +131,25 @@ def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[i ...@@ -92,21 +131,25 @@ def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[i
wanted_week = CalendarWeek() wanted_week = CalendarWeek()
lesson_periods = LessonPeriod.objects.annotate( lesson_periods = LessonPeriod.objects.annotate(
has_documentation=Exists(LessonDocumentation.objects.filter( has_documentation=Exists(
~Q(topic__exact=''), LessonDocumentation.objects.filter(
lesson_period=OuterRef('pk'), ~Q(topic__exact=""), lesson_period=OuterRef("pk"), week=wanted_week.week
week=wanted_week.week )
)) )
).in_week(wanted_week) ).in_week(wanted_week)
group = None # FIXME workaround for #38 group = None # FIXME workaround for #38
if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None): if (
request.GET.get("group", None)
or request.GET.get("teacher", None)
or request.GET.get("room", None)
):
lesson_periods = lesson_periods.filter_from_query(request.GET) lesson_periods = lesson_periods.filter_from_query(request.GET)
if 'group' in request.GET and request.GET['group']: if "group" in request.GET and request.GET["group"]:
group = Group.objects.get(pk=request.GET['group']) group = Group.objects.get(pk=request.GET["group"])
else: else:
group = None group = None
elif hasattr(request, 'user') and hasattr(request.user, 'person'): elif hasattr(request, "user") and hasattr(request.user, "person"):
group = request.user.person.owner_of.first() group = request.user.person.owner_of.first()
if group: if group:
lesson_periods = lesson_periods.filter_group(group) lesson_periods = lesson_periods.filter_group(group)
...@@ -119,28 +162,37 @@ def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[i ...@@ -119,28 +162,37 @@ def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[i
if lesson_periods: if lesson_periods:
# Aggregate all personal notes for this group and week # Aggregate all personal notes for this group and week
persons = Person.objects.filter( persons = (
is_active=True Person.objects.filter(is_active=True)
).filter( .filter(member_of__lessons__lesson_periods__in=lesson_periods)
member_of__lessons__lesson_periods__in=lesson_periods .distinct()
).distinct().prefetch_related( .prefetch_related("personal_notes")
'personal_notes' .annotate(
).annotate( absences=Count(
absences=Count('personal_notes__absent', filter=Q( "personal_notes__absent",
personal_notes__lesson_period__in=lesson_periods, filter=Q(
personal_notes__week=wanted_week.week, personal_notes__lesson_period__in=lesson_periods,
personal_notes__absent=True personal_notes__week=wanted_week.week,
)), personal_notes__absent=True,
unexcused=Count('personal_notes__absent', filter=Q( ),
personal_notes__lesson_period__in=lesson_periods, ),
personal_notes__week=wanted_week.week, unexcused=Count(
personal_notes__absent=True, "personal_notes__absent",
personal_notes__excused=False filter=Q(
)), personal_notes__lesson_period__in=lesson_periods,
tardiness=Sum('personal_notes__late', filter=Q( personal_notes__week=wanted_week.week,
personal_notes__lesson_period__in=lesson_periods, personal_notes__absent=True,
personal_notes__week=wanted_week.week personal_notes__excused=False,
)) ),
),
tardiness=Sum(
"personal_notes__late",
filter=Q(
personal_notes__lesson_period__in=lesson_periods,
personal_notes__week=wanted_week.week,
),
),
)
) )
else: else:
persons = None persons = None
...@@ -148,19 +200,25 @@ def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[i ...@@ -148,19 +200,25 @@ def week_view(request: HttpRequest, year: Optional[int] = None, week: Optional[i
# Add a form to filter the view # Add a form to filter the view
select_form = SelectForm(request.GET or None) select_form = SelectForm(request.GET or None)
context['current_head'] = str(wanted_week) context["current_head"] = str(wanted_week)
context['week'] = wanted_week context["week"] = wanted_week
context['lesson_periods'] = lesson_periods context["lesson_periods"] = lesson_periods
context['persons'] = persons context["persons"] = persons
context['group'] = group context["group"] = group
context['select_form'] = select_form context["select_form"] = select_form
week_prev = wanted_week - 1 week_prev = wanted_week - 1
week_next = wanted_week + 1 week_next = wanted_week + 1
context['url_prev'] = '%s?%s' % (reverse('week_view_by_week', args=[week_prev.year, week_prev.week]), request.GET.urlencode()) context["url_prev"] = "%s?%s" % (
context['url_next'] = '%s?%s' % (reverse('week_view_by_week', args=[week_next.year, week_next.week]), request.GET.urlencode()) reverse("week_view_by_week", args=[week_prev.year, week_prev.week]),
request.GET.urlencode(),
)
context["url_next"] = "%s?%s" % (
reverse("week_view_by_week", args=[week_next.year, week_next.week]),
request.GET.urlencode(),
)
return render(request, 'alsijil/week_view.html', context) return render(request, "alsijil/week_view.html", context)
@login_required @login_required
...@@ -170,55 +228,76 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -170,55 +228,76 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
group = get_object_or_404(Group, pk=id_) group = get_object_or_404(Group, pk=id_)
# Get all lesson periods for the selected group # Get all lesson periods for the selected group
lesson_periods = LessonPeriod.objects.filter_group( lesson_periods = (
group LessonPeriod.objects.filter_group(group)
).distinct().prefetch_related( .distinct()
'documentations', 'personal_notes' .prefetch_related("documentations", "personal_notes")
) )
weeks = CalendarWeek.weeks_within(School.objects.first().current_term.date_start, School.objects.first().current_term.date_end) weeks = CalendarWeek.weeks_within(
School.objects.first().current_term.date_start,
School.objects.first().current_term.date_end,
)
periods_by_day = {} periods_by_day = {}
for lesson_period in lesson_periods: for lesson_period in lesson_periods:
for week in weeks: for week in weeks:
day = week[lesson_period.period.weekday - 1] day = week[lesson_period.period.weekday - 1]
if lesson_period.lesson.date_start <= day and lesson_period.lesson.date_end >= day: if (
documentations = list(filter(lambda d: d.week == week.week, lesson_period.documentations.all())) lesson_period.lesson.date_start <= day
notes = list(filter(lambda d: d.week == week.week, lesson_period.personal_notes.all())) and lesson_period.lesson.date_end >= day
):
documentations = list(
filter(
lambda d: d.week == week.week,
lesson_period.documentations.all(),
)
)
notes = list(
filter(
lambda d: d.week == week.week,
lesson_period.personal_notes.all(),
)
)
substitution = lesson_period.get_substitution(week.week) substitution = lesson_period.get_substitution(week.week)
periods_by_day.setdefault(day, []).append((lesson_period, documentations, notes, substitution)) periods_by_day.setdefault(day, []).append(
(lesson_period, documentations, notes, substitution)
)
persons = group.members.annotate( persons = group.members.annotate(
absences=Count('personal_notes__absent', filter=Q( absences=Count("personal_notes__absent", filter=Q(personal_notes__absent=True)),
personal_notes__absent=True unexcused=Count(
)), "personal_notes__absent",
unexcused=Count('personal_notes__absent', filter=Q( filter=Q(personal_notes__absent=True, personal_notes__excused=False),
personal_notes__absent=True, ),
personal_notes__excused=False tardiness=Sum("personal_notes__late"),
)),
tardiness=Sum('personal_notes__late')
) )
# FIXME Move to manager # FIXME Move to manager
personal_note_filters = PersonalNoteFilter.objects.all() personal_note_filters = PersonalNoteFilter.objects.all()
for personal_note_filter in personal_note_filters: for personal_note_filter in personal_note_filters:
persons = persons.annotate( persons = persons.annotate(
**{'_personal_notes_with_%s' % personal_note_filter.identifier: Count( **{
'personal_notes__remarks', "_personal_notes_with_%s"
filter=Q(personal_notes__remarks__iregex=personal_note_filter.regex) % personal_note_filter.identifier: Count(
)} "personal_notes__remarks",
filter=Q(
personal_notes__remarks__iregex=personal_note_filter.regex
),
)
}
) )
context['persons'] = persons context["persons"] = persons
context['personal_note_filters'] = personal_note_filters context["personal_note_filters"] = personal_note_filters
context['group'] = group context["group"] = group
context['weeks'] = weeks context["weeks"] = weeks
context['periods_by_day'] = periods_by_day context["periods_by_day"] = periods_by_day
context['today'] = date.today() context["today"] = date.today()
context['school'] = School.objects.first() context["school"] = School.objects.first()
return render(request, 'alsijil/print/full_register.html', context) return render(request, "alsijil/print/full_register.html", context)
@admin_required @admin_required
...@@ -227,30 +306,30 @@ def register_absence(request: HttpRequest) -> HttpResponse: ...@@ -227,30 +306,30 @@ def register_absence(request: HttpRequest) -> HttpResponse:
register_absence_form = RegisterAbsenceForm(request.POST or None) register_absence_form = RegisterAbsenceForm(request.POST or None)
if request.method == 'POST': if request.method == "POST":
if register_absence_form.is_valid(): if register_absence_form.is_valid():
# Get data from form # Get data from form
person = register_absence_form.cleaned_data['person'] person = register_absence_form.cleaned_data["person"]
start_date = register_absence_form.cleaned_data['date_start'] start_date = register_absence_form.cleaned_data["date_start"]
end_date = register_absence_form.cleaned_data['date_end'] end_date = register_absence_form.cleaned_data["date_end"]
from_period = register_absence_form.cleaned_data['from_period'] from_period = register_absence_form.cleaned_data["from_period"]
absent = register_absence_form.cleaned_data['absent'] absent = register_absence_form.cleaned_data["absent"]
excused = register_absence_form.cleaned_data['excused'] excused = register_absence_form.cleaned_data["excused"]
remarks = register_absence_form.cleaned_data['remarks'] remarks = register_absence_form.cleaned_data["remarks"]
# Mark person as absent # Mark person as absent
delta = end_date - start_date delta = end_date - start_date
for i in range(delta.days+1): for i in range(delta.days + 1):
from_period = from_period if i == 0 else 0 from_period = from_period if i == 0 else 0
day = start_date + timedelta(days=i) day = start_date + timedelta(days=i)
person.mark_absent(day, from_period, absent, excused, remarks) person.mark_absent(day, from_period, absent, excused, remarks)
messages.success(request, _('The absence has been saved.')) messages.success(request, _("The absence has been saved."))
return redirect('index') return redirect("index")
context['register_absence_form'] = register_absence_form context["register_absence_form"] = register_absence_form
return render(request, 'alsijil/register_absence.html', context) return render(request, "alsijil/register_absence.html", context)
def list_personal_note_filters(request: HttpRequest) -> HttpResponse: def list_personal_note_filters(request: HttpRequest) -> HttpResponse:
...@@ -262,33 +341,36 @@ def list_personal_note_filters(request: HttpRequest) -> HttpResponse: ...@@ -262,33 +341,36 @@ def list_personal_note_filters(request: HttpRequest) -> HttpResponse:
personal_note_filters_table = PersonalNoteFilterTable(personal_note_filters) personal_note_filters_table = PersonalNoteFilterTable(personal_note_filters)
RequestConfig(request).configure(personal_note_filters_table) RequestConfig(request).configure(personal_note_filters_table)
context['personal_note_filters_table'] = personal_note_filters_table context["personal_note_filters_table"] = personal_note_filters_table
return render(request, 'alsijil/personal_note_filters.html', context) return render(request, "alsijil/personal_note_filters.html", context)
def edit_personal_note_filter(request: HttpRequest, id: Optional['int'] = None) -> HttpResponse: def edit_personal_note_filter(
request: HttpRequest, id: Optional["int"] = None
) -> HttpResponse:
context = {} context = {}
if id: if id:
personal_note_filter = PersonalNoteFilter.objects.get(id=id) personal_note_filter = PersonalNoteFilter.objects.get(id=id)
context['personal_note_filter'] = personal_note_filter context["personal_note_filter"] = personal_note_filter
personal_note_filter_form = PersonalNoteFilterForm( personal_note_filter_form = PersonalNoteFilterForm(
request.POST or None, instance=personal_note_filter) request.POST or None, instance=personal_note_filter
)
else: else:
personal_note_filter_form = PersonalNoteFilterForm( personal_note_filter_form = PersonalNoteFilterForm(request.POST or None)
request.POST or None)
if request.method == "POST":
if request.method == 'POST':
if personal_note_filter_form.is_valid(): if personal_note_filter_form.is_valid():
personal_note_filter_form.save(commit=True) personal_note_filter_form.save(commit=True)
messages.success(request, _('The filter has been saved')) messages.success(request, _("The filter has been saved"))
return redirect('list_personal_note_filters') return redirect("list_personal_note_filters")
context['personal_note_filter_form'] = personal_note_filter_form context["personal_note_filter_form"] = personal_note_filter_form
return render(request, "alsijil/manage_personal_note_filter.html", context)
return render(request, 'alsijil/manage_personal_note_filter.html', context)
@admin_required @admin_required
def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse: def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse:
...@@ -297,8 +379,8 @@ def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -297,8 +379,8 @@ def delete_personal_note_filter(request: HttpRequest, id_: int) -> HttpResponse:
personal_note_filter = get_object_or_404(PersonalNoteFilter, pk=id_) personal_note_filter = get_object_or_404(PersonalNoteFilter, pk=id_)
PersonalNoteFilter.objects.filter(pk=id_).delete() PersonalNoteFilter.objects.filter(pk=id_).delete()
messages.success(request, _('The filter has been deleted.'))
context['personal_note_filter'] = personal_note_filter messages.success(request, _("The filter has been deleted."))
return redirect('list_personal_note_filters')
context["personal_note_filter"] = personal_note_filter
return redirect("list_personal_note_filters")