diff --git a/aleksis/apps/alsijil/migrations/0019_add_documentation_and_participation.py b/aleksis/apps/alsijil/migrations/0020_add_documentation_and_participation.py similarity index 91% rename from aleksis/apps/alsijil/migrations/0019_add_documentation_and_participation.py rename to aleksis/apps/alsijil/migrations/0020_add_documentation_and_participation.py index 9a23bd3f4016d57293367ddd878f7fb184f4659e..4d910b912e038f5e65f2ba9e5b0947e52297f199 100644 --- a/aleksis/apps/alsijil/migrations/0019_add_documentation_and_participation.py +++ b/aleksis/apps/alsijil/migrations/0020_add_documentation_and_participation.py @@ -9,11 +9,10 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ("sites", "0002_alter_domain_unique"), - ("chronos", "0015_add_managed_by_app_label"), + ("chronos", "0016_lessonevent"), ("core", "0052_site_related_name"), ("cursus", "0001_initial"), - ("alsijil", "0018_add_managed_by_app_label"), + ("alsijil", "0019_drop_sites"), ] operations = [ @@ -120,16 +119,6 @@ class Migration(migrations.Migration): to="core.person", ), ), - ( - "site", - models.ForeignKey( - default=1, - editable=False, - on_delete=django.db.models.deletion.CASCADE, - related_name="+", - to="sites.site", - ), - ), ], options={ "verbose_name": "Participation note", diff --git a/aleksis/apps/alsijil/migrations/0021_participationstatus.py b/aleksis/apps/alsijil/migrations/0021_participationstatus.py new file mode 100644 index 0000000000000000000000000000000000000000..5f3a7225f0301609a424aed6354bde13f97b086a --- /dev/null +++ b/aleksis/apps/alsijil/migrations/0021_participationstatus.py @@ -0,0 +1,113 @@ +# Generated by Django 4.2.9 on 2024-01-11 20:43 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cursus', '0001_initial'), + ('core', '0052_site_related_name'), + ('chronos', '0016_lessonevent'), + ('kolego', '0003_refactor_absence'), + ('alsijil', '0020_add_documentation_and_participation'), + ] + + operations = [ + migrations.CreateModel( + name='NewPersonalNote', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('managed_by_app_label', models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance')), + ('extended_data', models.JSONField(default=dict, editable=False)), + ('note', models.TextField(blank=True, verbose_name='Note')), + ], + options={ + 'verbose_name': 'Personal Note', + 'verbose_name_plural': 'Personal Notes', + }, + ), + migrations.CreateModel( + name='ParticipationStatus', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('managed_by_app_label', models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance')), + ('extended_data', models.JSONField(default=dict, editable=False)), + ('absent', models.BooleanField(verbose_name='Absent')), + ('absence_reason', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kolego.absencereason', verbose_name='Absence Reason')), + ('base_absence', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='absences', to='kolego.absence', verbose_name='Base Absence')), + ], + options={ + 'verbose_name': 'Participation Status', + 'verbose_name_plural': 'Participation Status', + 'ordering': ['documentation', 'person__last_name', 'person__first_name'], + }, + ), + migrations.AlterField( + model_name='documentation', + name='course', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='documentations', to='cursus.course', verbose_name='Course'), + ), + migrations.AlterField( + model_name='documentation', + name='group_note', + field=models.CharField(blank=True, max_length=255, verbose_name='Group Note'), + ), + migrations.AlterField( + model_name='documentation', + name='lesson_event', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='documentation', to='chronos.lessonevent', verbose_name='Lesson Event'), + ), + migrations.AlterField( + model_name='documentation', + name='subject', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='cursus.subject', verbose_name='Subject'), + ), + migrations.AlterField( + model_name='documentation', + name='topic', + field=models.CharField(blank=True, max_length=255, verbose_name='Lesson Topic'), + ), + migrations.DeleteModel( + name='Participation', + ), + migrations.AddField( + model_name='participationstatus', + name='documentation', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participations', to='alsijil.documentation', verbose_name='Documentation'), + ), + migrations.AddField( + model_name='participationstatus', + name='groups_of_person', + field=models.ManyToManyField(related_name='+', to='core.group', verbose_name='Groups of Person'), + ), + migrations.AddField( + model_name='participationstatus', + name='person', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participations', to='core.person', verbose_name='Person'), + ), + migrations.AddField( + model_name='newpersonalnote', + name='documentation', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='personal_notes', to='alsijil.documentation', verbose_name='Documentation'), + ), + migrations.AddField( + model_name='newpersonalnote', + name='extra_mark', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='alsijil.extramark', verbose_name='Extra Mark'), + ), + migrations.AddField( + model_name='newpersonalnote', + name='person', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='new_personal_notes', to='core.person', verbose_name='Person'), + ), + migrations.AddConstraint( + model_name='participationstatus', + constraint=models.UniqueConstraint(fields=('documentation', 'person'), name='unique_participation_status_per_documentation'), + ), + migrations.AddConstraint( + model_name='newpersonalnote', + constraint=models.CheckConstraint(check=models.Q(models.Q(('note', ''), _negated=True), ('extra_mark__isnull', False), _connector='OR'), name='unique_absence_per_documentation'), + ), + ] diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 3579a6db4205e2cb13563e5989b976832e83f046..e86bbed4c4d42a6f80de114f7815a79e52af6c20 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -454,7 +454,7 @@ class Documentation(CalendarEvent): course = models.ForeignKey( Course, - models.CASCADE, + models.PROTECT, related_name="documentations", blank=True, null=True, @@ -463,7 +463,7 @@ class Documentation(CalendarEvent): lesson_event = models.ForeignKey( LessonEvent, - models.CASCADE, + models.PROTECT, related_name="documentation", blank=True, null=True, @@ -471,7 +471,7 @@ class Documentation(CalendarEvent): ) subject = models.ForeignKey( - Subject, models.CASCADE, related_name="+", blank=True, null=True, verbose_name=_("Subject") + Subject, models.PROTECT, related_name="+", blank=True, null=True, verbose_name=_("Subject") ) topic = models.CharField(verbose_name=_("Lesson Topic"), max_length=255, blank=True) @@ -496,8 +496,8 @@ class Documentation(CalendarEvent): return self.course.groups.all() def __str__(self) -> str: - start_datetime = CalendarEvent.value_start_datetime(self, None) - end_datetime = CalendarEvent.value_end_datetime(self, None) + start_datetime = CalendarEvent.value_start_datetime(self) + end_datetime = CalendarEvent.value_end_datetime(self) return ( f"{format_m2m(self.get_groups())} {self.get_subject()}" + f" {start_datetime} - {end_datetime}" @@ -514,10 +514,10 @@ class Documentation(CalendarEvent): ] -class Participation(ExtensibleModel): - """A participation record about a single person. +class ParticipationStatus(ExtensibleModel): + """A participation or absence record about a single person. - Used in the class register to note participation of a student + Used in the class register to note participation or absence of a student in a documented unit (e.g. a single lesson event or a custom time frame; see Documentation). """ @@ -537,61 +537,22 @@ class Participation(ExtensibleModel): verbose_name=_("Documentation"), ) - def __str__(self) -> str: - return f"{self.documentation}, {self.person}" - - class Meta: - verbose_name = _("Participation note") - verbose_name_plural = _("Participation notes") - ordering = [ - "documentation", - "person__last_name", - "person__first_name", - ] - constraints = [ - models.UniqueConstraint( - fields=("documentation", "person"), - name="unique_participation_per_documentation", - ), - ] - - -class Absence(ExtensibleModel): - """An absence record about a single person. - - Used in the class register to note absence of a student - in a documented unit (e.g. a single lesson event or a custom time frame; see Documentation). - """ - - # FIXME: DataChecks - - reason = models.ForeignKey( - AbsenceReason, verbose_name=_("Absence Reason"), on_delete=models.CASCADE - ) - - person = models.ForeignKey( - "core.Person", models.CASCADE, related_name="lesson_absences", verbose_name=_("Person") - ) - groups_of_person = models.ManyToManyField( - "core.Group", related_name="+", verbose_name=_("Groups of Person") - ) - - documentation = models.ForeignKey( - Documentation, models.CASCADE, related_name="absences", verbose_name=_("Documentation") + # Absence part + absent = models.BooleanField(verbose_name=_("Absent")) + absence_reason = models.ForeignKey( + AbsenceReason, verbose_name=_("Absence Reason"), on_delete=models.PROTECT ) - excused = models.BooleanField(default=False, verbose_name=_("Excused")) - base_absence = models.ForeignKey( - KolegoAbsence, models.SET_NULL, related_name="absences", verbose_name=_("Base Absence") + KolegoAbsence, models.SET_NULL, blank=True, null=True, related_name="absences", verbose_name=_("Base Absence") ) def __str__(self) -> str: return f"{self.documentation}, {self.person}" class Meta: - verbose_name = _("Absence note") - verbose_name_plural = _("Absence notes") + verbose_name = _("Participation Status") + verbose_name_plural = _("Participation Status") ordering = [ "documentation", "person__last_name", @@ -600,11 +561,13 @@ class Absence(ExtensibleModel): constraints = [ models.UniqueConstraint( fields=("documentation", "person"), - name="unique_absence_per_documentation", + name="unique_participation_status_per_documentation", ), ] + + class NewPersonalNote(ExtensibleModel): person = models.ForeignKey( "core.Person", models.CASCADE, related_name="new_personal_notes", verbose_name=_("Person") @@ -621,7 +584,7 @@ class NewPersonalNote(ExtensibleModel): note = models.TextField(blank=True, verbose_name=_("Note")) extra_mark = models.ForeignKey( - ExtraMark, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_("Extra Mark") + ExtraMark, on_delete=models.PROTECT, blank=True, null=True, verbose_name=_("Extra Mark") ) def __str__(self) -> str: diff --git a/pyproject.toml b/pyproject.toml index c19624053cb6d7c1c5779163f9cb66bda9bf9961..0e35ee18b56fae8ed96d9546021da00c5a4f8e74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ python = "^3.10" aleksis-core = "^4.0.0.dev2" aleksis-app-chronos = "^4.0.0.dev0" aleksis-app-stoelindeling = { version = "^3.0.dev1", optional = true } - +aleksis-app-kolego = { path = "../../onboarding/AlekSIS-App-Kolego", develop = true } [tool.poetry.extras] seatingplans = ["aleksis-app-stoelindeling"]