Skip to content
Snippets Groups Projects

Resolve "Improve publishing process of validity ranges"

Merged Jonathan Weth requested to merge 10-improve-publishing-process-of-validity-ranges into master
  1. Jul 21, 2024
    • Jonathan Weth's avatar
    • Jonathan Weth's avatar
      Clear groups if no course is set · f65733e1
      Jonathan Weth authored
      Verified
      f65733e1
    • Jonathan Weth's avatar
      Verified
      722795d2
    • Jonathan Weth's avatar
    • Jonathan Weth's avatar
      Reformat and fix tests · 3782e520
      Jonathan Weth authored
      Verified
      3782e520
    • Jonathan Weth's avatar
    • Jonathan Weth's avatar
      Fix status filter for validity ranges · 1f2ee1bc
      Jonathan Weth authored
      Verified
      1f2ee1bc
    • Jonathan Weth's avatar
    • Jonathan Weth's avatar
      [Validity ranges] Use CRUDList instead of InlineCRUDList, fix filtering and... · e744eba5
      Jonathan Weth authored
      [Validity ranges] Use CRUDList instead of InlineCRUDList, fix filtering and deactivate editing of status
      Verified
      e744eba5
    • Jonathan Weth's avatar
    • Jonathan Weth's avatar
      Improve validation of validity ranges and add tests for it · d6654a66
      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
      Verified
      d6654a66
    • Jonathan Weth's avatar
      Verified
      878f8193
Loading