diff --git a/aleksis/apps/alsijil/migrations/0023_add_tardiness_and_rework_constraints.py b/aleksis/apps/alsijil/migrations/0023_add_tardiness_and_rework_constraints.py new file mode 100644 index 0000000000000000000000000000000000000000..8f59e274474385145966806b8c776565d015432f --- /dev/null +++ b/aleksis/apps/alsijil/migrations/0023_add_tardiness_and_rework_constraints.py @@ -0,0 +1,96 @@ +# Generated by Django 4.2.10 on 2024-06-18 09:44 +# Updated for more custom logic + +from django.db import migrations, models + + +def forwards__one_value_per_note(apps, schema_editor): + # We get the model from the versioned app registry; + # if we directly import it, it'll be the wrong version + NewPersonalNote = apps.get_model("alsijil", "NewPersonalNote") # noqa + db_alias = schema_editor.connection.alias + + NewPersonalNote.objects.using(db_alias).filter(note="").update(note=None) + + +def reverse__one_value_per_note(apps, schema_editor): + NewPersonalNote = apps.get_model("alsijil", "NewPersonalNote") # noqa + db_alias = schema_editor.connection.alias + + NewPersonalNote.objects.using(db_alias).filter(note=None).update(note="") + + +def forwards__unique_extra_mark_documentation(apps, schema_editor): + NewPersonalNote = apps.get_model("alsijil", "NewPersonalNote") # noqa + db_alias = schema_editor.connection.alias + + duplicates = (NewPersonalNote.objects.using(db_alias) + .values("documentation", "extra_mark", "person") + .annotate(count=models.Count("id")) + .filter(count__gt=1, extra_mark__isnull=False)) + + # Iterate over duplicates and delete the extra instances + for duplicate in duplicates: + pks = (NewPersonalNote + .objects + .using(db_alias) + .filter(person=duplicate["person"], documentation=duplicate["documentation"], extra_mark=duplicate["extra_mark"]) + .values_list("pk", flat=True) + )[1:] + NewPersonalNote.objects.using(db_alias).filter(pk__in=pks).delete() + +def reverse__unique_extra_mark_documentation(apps, schema_editor): + # Nothing to do, we cannot bring back the deleted objects, but they were duplicate data, so they are not needed anyway. + pass + +class Migration(migrations.Migration): + dependencies = [ + ('alsijil', '0022_documentation_participation_touched_at'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='newpersonalnote', + name='unique_absence_per_documentation', + ), + migrations.AddField( + model_name='newpersonalnote', + name='tardiness', + field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Tardiness'), + ), + migrations.AlterField( + model_name='newpersonalnote', + name='note', + field=models.TextField(blank=True, null=True, verbose_name='Note'), + ), + migrations.RunPython(forwards__one_value_per_note, reverse__one_value_per_note), + migrations.AddConstraint( + model_name='newpersonalnote', + constraint=models.CheckConstraint( + check=models.Q( + models.Q(('extra_mark__isnull', True), ('note__isnull', False), ('tardiness__isnull', True)), + models.Q(('extra_mark__isnull', False), ('note__isnull', True), ('tardiness__isnull', True)), + models.Q(('extra_mark__isnull', True), ('note__isnull', True), ('tardiness__isnull', False)), + _connector='OR' + ), + name='one_value_per_personal_note' + ), + ), + migrations.AddConstraint( + model_name='newpersonalnote', + constraint=models.UniqueConstraint( + condition=models.Q(('tardiness', None), _negated=True), + fields=('person', 'documentation', 'tardiness'), + name='unique_person_documentation_tardiness' + ), + ), + migrations.RunPython(forwards__unique_extra_mark_documentation, reverse__unique_extra_mark_documentation), + migrations.AddConstraint( + model_name='newpersonalnote', + constraint=models.UniqueConstraint( + condition=models.Q(('extra_mark', None), _negated=True), + fields=('person', 'documentation', 'extra_mark'), + name='unique_person_documentation_extra_mark' + ), + ), + ] diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 9e60596c343c6917af6894db3272b38a4d591372..73c3c6b43a5cae0021b36a539684601b9ec68410 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -828,7 +828,7 @@ class NewPersonalNote(ExtensibleModel): null=True, ) - note = models.TextField(blank=True, verbose_name=_("Note")) + note = models.TextField(blank=True, null=True, verbose_name=_("Note")) extra_mark = models.ForeignKey( ExtraMark, on_delete=models.PROTECT, blank=True, null=True, verbose_name=_("Extra Mark") ) @@ -842,8 +842,20 @@ class NewPersonalNote(ExtensibleModel): verbose_name_plural = _("Personal Notes") constraints = [ models.CheckConstraint( - check=~Q(note="") | Q(extra_mark__isnull=False), - name="unique_absence_per_documentation", + check=Q(note__isnull=False, extra_mark__isnull=True, tardiness__isnull=True) | + Q(note__isnull=True, extra_mark__isnull=False, tardiness__isnull=True) | + Q(note__isnull=True, extra_mark__isnull=True, tardiness__isnull=False), + name="one_value_per_personal_note", + ), + models.UniqueConstraint( + fields=["person", "documentation", "tardiness"], + name="unique_person_documentation_tardiness", + condition=~Q(tardiness=None) + ), + models.UniqueConstraint( + fields=["person", "documentation", "extra_mark"], + name="unique_person_documentation_extra_mark", + condition=~Q(extra_mark=None) ), ]