Resolve "Improve publishing process of validity ranges"
- Jul 21, 2024
-
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Jonathan Weth authored
[Validity ranges] Use CRUDList instead of InlineCRUDList, fix filtering and deactivate editing of status
-
Jonathan Weth authored
-
Jonathan Weth authored
diff --git a/aleksis/apps/lesrooster/models.py b/aleksis/apps/lesrooster/models.py index be46abf..2c1589b 100644 --- a/aleksis/apps/lesrooster/models.py +++ b/aleksis/apps/lesrooster/models.py @@ -55,7 +55,7 @@ class ValidityRange(ExtensibleModel): default=ValidityRangeStatus.DRAFT, ) - status_tracker = FieldTracker(fields=["status"]) + status_tracker = FieldTracker(fields=["status", "date_start", "date_end", "school_term"]) @property def published(self): @@ -84,7 +84,7 @@ class ValidityRange(ExtensibleModel): """Ensure that there is only one validity range at each point of time.""" if self.status_tracker.changed().get("status", "") == ValidityRangeStatus.PUBLISHED.value: - raise ValidationError(_("A published validity range can't be changed.")) + raise ValidationError(_("You can't unpublish a validity range.")) if self.date_end < self.date_start: raise ValidationError(_("The start date must be earlier than the end date.")) @@ -95,7 +95,35 @@ class ValidityRange(ExtensibleModel): ): raise ValidationError(_("The validity range must be within the school term.")) - if self.status == ValidityRangeStatus.PUBLISHED.value: + if self.published: + errors = {} + if "school_term" in self.status_tracker.changed(): + errors["school_term"] = _( + "The school term of a published validity range can't be changed." + ) + + if "date_start" in self.status_tracker.changed(): + if self.status_tracker.changed()["date_start"] < datetime.now().date(): + errors["date_start"] = _( + "You can't change the start date if the validity range is already active." + ) + elif self.date_start < datetime.now().date(): + errors["date_start"] = _("You can't set the start date to a date in the past.") + + if "date_end" in self.status_tracker.changed(): + if self.status_tracker.changed()["date_end"] < datetime.now().date(): + errors["date_end"] = _( + "You can't change the end date " + "if the validity range is already in the past." + ) + elif self.date_end < datetime.now().date(): + errors["date_end"] = _( + "To avoid data loss, the validity range can be only shortened until today." + ) + + if errors: + raise ValidationError(errors) + qs = ValidityRange.objects.within_dates(self.date_start, self.date_end).filter( status=ValidityRangeStatus.PUBLISHED ) @@ -111,8 +139,11 @@ class ValidityRange(ExtensibleModel): def publish(self): self.status = ValidityRangeStatus.PUBLISHED + self.full_clean() self.save() + self._sync() + def _sync(self): objs_to_update = ( list(Lesson.objects.filter(slot_start__time_grid__validity_range=self)) + list(Supervision.objects.filter(break_slot__time_grid__validity_range=self)) diff --git a/aleksis/apps/lesrooster/tests/test_validity_range.py b/aleksis/apps/lesrooster/tests/test_validity_range.py new file mode 100644 index 0000000..356be46 --- /dev/null +++ b/aleksis/apps/lesrooster/tests/test_validity_range.py @@ -0,0 +1,152 @@ +from datetime import date, timedelta +import pytest + +pytestmark = pytest.mark.django_db + +from aleksis.core.models import SchoolTerm +from aleksis.apps.lesrooster.models import ValidityRange, ValidityRangeStatus +from django.core.exceptions import ValidationError +from freezegun import freeze_time + + +def test_validity_range_date_start_before_date_end(): + date_start = date(2024, 1, 2) + date_end = date(2024,1,1) + + school_term = SchoolTerm.objects.create(name="Test", date_start=date_start, date_end=date_end) + + validity_range = ValidityRange(school_term=school_term, date_start=date_start, date_end=date_end) + with pytest.raises(ValidationError, match=r".*The start date must be earlier than the end date\..*"): + validity_range.full_clean() + +def test_validity_range_within_school_term(): + date_start = date(2024, 1, 1) + date_end = date(2024,6,1) + school_term = SchoolTerm.objects.create(name="Test", date_start=date_start, date_end=date_end) + + + dates_fail = [ + (date_start - timedelta(days=1), date_end), + (date_start, date_end + timedelta(days=1)), + (date_start - timedelta(days=1), date_end + timedelta(days=1)) + ] + + dates_success = [ + (date_start, date_end), + (date_start + timedelta(days=1), date_end), + (date_start, date_end - timedelta(days=1)), + (date_start + timedelta(days=1), date_end - timedelta(days=1)) + ] + + for d_start, d_end in dates_fail: + validity_range = ValidityRange(school_term=school_term, date_start=d_start, date_end=d_end) + with pytest.raises(ValidationError, match=r".*The validity range must be within the school term\..*"): + validity_range.full_clean() + + for d_start, d_end in dates_success: + validity_range = ValidityRange(school_term=school_term, date_start=d_start, date_end=d_end) + validity_range.full_clean() + + +def test_validity_range_overlaps(): + date_start = date(2024, 1, 1) + date_end = date(2024,6,1) + school_term = SchoolTerm.objects.create(name="Test", date_start=date_start, date_end=date_end) + + validity_range_1 = ValidityRange.objects.create(date_start=date_start + timedelta(days=10), date_end=date_end - timedelta(days=10), school_term=school_term, status=ValidityRangeStatus.PUBLISHED) + + dates_fail = [ + (date_start, validity_range_1.date_start), + (date_start, date_end), + (date_start, validity_range_1.date_end), + (validity_range_1.date_start, validity_range_1.date_end), + (validity_range_1.date_end, date_end) + ] + + for d_start, d_end in dates_fail: + validity_range_2 = ValidityRange.objects.create(date_start=d_start, date_end=d_end, school_term=school_term) + with pytest.raises(ValidationError, match=r".*There is already a published validity range for this time or a part of this time\..*"): + validity_range_2.publish() + + +def test_change_published_validity_range(): + date_start = date(2024, 1, 1) + date_end = date(2024,6,1) + school_term = SchoolTerm.objects.create(name="Test", date_start=date_start-timedelta(days=5), date_end=date_end+timedelta(days=5)) + school_term_2 = SchoolTerm.objects.create(name="Test 2", date_start=date_end+timedelta(days=6), date_end=date_end+timedelta(days=7)) + + validity_range = ValidityRange.objects.create(date_start=date_start, date_end=date_end, school_term=school_term, status=ValidityRangeStatus.PUBLISHED) + + # School term + validity_range.refresh_from_db() + validity_range.school_term = school_term_2 + with pytest.raises(ValidationError): + validity_range.full_clean() + + # Name + validity_range.refresh_from_db() + validity_range.name = "Test" + validity_range.full_clean() + + # Status + validity_range.refresh_from_db() + validity_range.status = ValidityRangeStatus.DRAFT + with pytest.raises(ValidationError): + validity_range.full_clean() + + + with freeze_time(date_start - timedelta(days=1)): # current date start is in the future + # Date start in the past + validity_range.refresh_from_db() + validity_range.date_start = validity_range.date_start - timedelta(days=2) + with pytest.raises(ValidationError, match=r".*You can't set the start date to a date in the past.*"): + validity_range.full_clean() + + # Date start today + validity_range.refresh_from_db() + validity_range.date_start = validity_range.date_start - timedelta(days=1) + validity_range.full_clean() + + # Date start in the future + validity_range.refresh_from_db() + validity_range.date_start = validity_range.date_start + timedelta(days=2) + validity_range.full_clean() + + with freeze_time(date_start + timedelta(days=1)): # current date start is in the past + # Date start in the past + validity_range.refresh_from_db() + validity_range.date_start = validity_range.date_start - timedelta(days=2) + with pytest.raises(ValidationError, match=r".*You can't change the start date if the validity range is already active.*"): + validity_range.full_clean() + + # Date start in the future + validity_range.refresh_from_db() + validity_range.date_start = validity_range.date_start + timedelta(days=2) + with pytest.raises(ValidationError, match=r".*You can't change the start date if the validity range is already active.*"): + validity_range.full_clean() + + with freeze_time(date_end - timedelta(days=3)): # current date end is in the future + # Date end in the past + validity_range.refresh_from_db() + validity_range.date_end = validity_range.date_end - timedelta(days=4) + with pytest.raises(ValidationError, match=r".*To avoid data loss, the validity range can be only shortened until today.*"): + validity_range.full_clean() + + # Date end today + validity_range.refresh_from_db() + validity_range.date_end = validity_range.date_end - timedelta(days=3) + validity_range.full_clean() + + # Date end in the future + validity_range.refresh_from_db() + validity_range.date_end = validity_range.date_end - timedelta(days=2) + validity_range.full_clean() + + with freeze_time(date_end + timedelta(days=1)): # current date end is in the past + validity_range.refresh_from_db() + validity_range.date_end = validity_range.date_end - timedelta(days=2) + with pytest.raises(ValidationError, match=r".*You can't change the end date if the validity range is already in the past.*"): + validity_range.full_clean() + + +# TODO Test sync with date change
-
Jonathan Weth authored
-