Skip to content

Commit

Permalink
Merge pull request #3564 from webkom/ivarnakken/aba-868-too-late
Browse files Browse the repository at this point in the history
Introduce "too late" as an event presence status
  • Loading branch information
ivarnakken authored Mar 15, 2024
2 parents 153c9c6 + c9acb5f commit 46c514f
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 20 deletions.
1 change: 1 addition & 0 deletions lego/apps/events/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
class PRESENCE_CHOICES(models.TextChoices):
UNKNOWN = "UNKNOWN"
PRESENT = "PRESENT"
LATE = "LATE"
NOT_PRESENT = "NOT_PRESENT"


Expand Down
26 changes: 26 additions & 0 deletions lego/apps/events/migrations/0040_alter_registration_presence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.0.10 on 2024-03-06 22:04

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("events", "0039_remove_event_use_contact_tracing"),
]

operations = [
migrations.AlterField(
model_name="registration",
name="presence",
field=models.CharField(
choices=[
("UNKNOWN", "Unknown"),
("PRESENT", "Present"),
("LATE", "Late"),
("NOT_PRESENT", "Not Present"),
],
default="UNKNOWN",
max_length=20,
),
),
]
41 changes: 31 additions & 10 deletions lego/apps/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from lego.apps.files.models import FileField
from lego.apps.followers.models import FollowEvent
from lego.apps.permissions.models import ObjectPermissionsModel
from lego.apps.users.constants import AUTUMN, SPRING
from lego.apps.users.constants import AUTUMN, PENALTY_TYPES, PENALTY_WEIGHTS, SPRING
from lego.apps.users.models import AbakusGroup, Membership, Penalty, User
from lego.utils.models import BasisModel
from lego.utils.youtube_validator import youtube_validator
Expand Down Expand Up @@ -934,26 +934,47 @@ def set_presence(self, presence: constants.PRESENCE_CHOICES) -> None:
"""Wrap this method in a transaction"""
if presence not in constants.PRESENCE_CHOICES:
raise ValueError("Illegal presence choice")

self.presence = presence
self.handle_user_penalty(presence)
self.save()

def delete_presence_penalties_for_event(self) -> None:
for penalty in self.user.penalties.filter(
source_event=self.event, type=PENALTY_TYPES.PRESENCE
):
penalty.delete()

def handle_user_penalty(self, presence: constants.PRESENCE_CHOICES) -> None:
"""
Previous penalties related to the event are deleted since the
newest presence is the only one that matters
"""

if (
self.event.heed_penalties
and presence == constants.PRESENCE_CHOICES.NOT_PRESENT
and self.event.penalty_weight_on_not_present
):
if not self.user.penalties.filter(source_event=self.event).exists():
Penalty.objects.create(
user=self.user,
reason=f"Møtte ikke opp på {self.event.title}.",
weight=self.event.penalty_weight_on_not_present,
source_event=self.event,
)
self.delete_presence_penalties_for_event()
Penalty.objects.create(
user=self.user,
reason=f"Møtte ikke opp på {self.event.title}.",
weight=self.event.penalty_weight_on_not_present,
source_event=self.event,
type=PENALTY_TYPES.PRESENCE,
)
elif self.event.heed_penalties and presence == constants.PRESENCE_CHOICES.LATE:
self.delete_presence_penalties_for_event()
Penalty.objects.create(
user=self.user,
reason=f"Møtte for sent opp på {self.event.title}.",
weight=PENALTY_WEIGHTS.LATE_PRESENCE,
source_event=self.event,
type=PENALTY_TYPES.PRESENCE,
)
else:
for penalty in self.user.penalties.filter(source_event=self.event):
penalty.delete()
self.delete_presence_penalties_for_event()

def add_to_pool(self, pool: Pool) -> Registration:
allowed: bool = False
Expand Down
12 changes: 5 additions & 7 deletions lego/apps/events/serializers/registrations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.db import transaction
from rest_framework import serializers

from rest_framework_jwt.serializers import ImpersonateAuthTokenSerializer
Expand Down Expand Up @@ -65,13 +64,12 @@ class Meta:
)

def update(self, instance, validated_data):
with transaction.atomic():
presence = validated_data.pop("presence", None)
super().update(instance, validated_data)
if presence:
instance.set_presence(presence)
presence = validated_data.pop("presence", None)
super().update(instance, validated_data)
if presence:
instance.set_presence(presence)

return instance
return instance


class RegistrationAnonymizedReadSerializer(BasisModelSerializer):
Expand Down
66 changes: 65 additions & 1 deletion lego/apps/events/tests/test_penalties.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from lego.apps.events import constants
from lego.apps.events.models import Event, Registration
from lego.apps.users.constants import LATE_PRESENCE_PENALTY_WEIGHT
from lego.apps.users.models import AbakusGroup, Penalty
from lego.utils.test_utils import BaseTestCase

Expand Down Expand Up @@ -398,6 +399,19 @@ def test_penalties_created_when_not_present(self):
self.assertEqual(penalties_before, 0)
self.assertEqual(penalties_after, event.penalty_weight_on_not_present)

def test_penalties_created_when_late_present(self):
"""Test that user gets penalties when late present"""
event = Event.objects.get(title="POOLS_WITH_REGISTRATIONS")

registration = event.registrations.first()
penalties_before = registration.user.number_of_penalties()

registration.set_presence(constants.PRESENCE_CHOICES.LATE)

penalties_after = registration.user.number_of_penalties()
self.assertEqual(penalties_before, 0)
self.assertEqual(penalties_after, LATE_PRESENCE_PENALTY_WEIGHT)

def test_penalties_removed_when_not_present_changes(self):
"""Test that penalties for not_present gets removed when resetting presence"""
event = Event.objects.get(title="POOLS_WITH_REGISTRATIONS")
Expand All @@ -411,8 +425,23 @@ def test_penalties_removed_when_not_present_changes(self):
self.assertEqual(penalties_before, event.penalty_weight_on_not_present)
self.assertEqual(penalties_after, 0)

def test_penalties_removed_when_late_present_changes(self):
"""Test that penalties for late presence gets removed when changing to present"""
event = Event.objects.get(title="POOLS_WITH_REGISTRATIONS")
registration = event.registrations.first()
registration.set_presence(constants.PRESENCE_CHOICES.LATE)

penalties_before = registration.user.number_of_penalties()
registration.set_presence(constants.PRESENCE_CHOICES.PRESENT)

penalties_after = registration.user.number_of_penalties()
self.assertEqual(penalties_before, LATE_PRESENCE_PENALTY_WEIGHT)
self.assertEqual(penalties_after, 0)

def test_only_correct_penalties_are_removed_on_presence_change(self):
"""Test that only penalties for given event are removed when changing presence"""
"""
Test that only penalties of type presence for given event are removed when changing presence
"""
event = Event.objects.get(title="POOLS_WITH_REGISTRATIONS")
other_event = Event.objects.get(title="POOLS_NO_REGISTRATIONS")
registration = event.registrations.first()
Expand Down Expand Up @@ -447,6 +476,41 @@ def test_only_correct_penalties_are_removed_on_presence_change(self):
)
self.assertEqual(penalties_after, other_event.penalty_weight_on_not_present)

def test_only_correct_penalties_are_removed_on_presence_change_on_same_event(self):
"""Test that only penalties of type presence are removed when changing presence"""
event = Event.objects.get(title="POOLS_WITH_REGISTRATIONS")
registration = event.registrations.first()

registration.set_presence(constants.PRESENCE_CHOICES.NOT_PRESENT)
penalties_before = registration.user.number_of_penalties()
penalties_object_before = list(registration.user.penalties.all())

# Default penalty type is other
Penalty.objects.create(
user=registration.user,
reason="SAME EVENT",
weight=2,
source_event=event,
)
penalties_during = registration.user.number_of_penalties()
penalties_objects_during = list(registration.user.penalties.all())

registration.set_presence(constants.PRESENCE_CHOICES.UNKNOWN)
penalties_after = registration.user.number_of_penalties()
penalties_object_after = list(registration.user.penalties.all())

self.assertEqual(penalties_object_before[0].source_event, event)
self.assertEqual(penalties_object_after[0].source_event, event)
self.assertEqual(len(penalties_object_before), 1)
self.assertEqual(len(penalties_objects_during), 2)
self.assertEqual(len(penalties_object_after), 1)
self.assertEqual(penalties_before, event.penalty_weight_on_not_present)
self.assertEqual(
penalties_during,
event.penalty_weight_on_not_present + event.penalty_weight_on_not_present,
)
self.assertEqual(penalties_after, event.penalty_weight_on_not_present)

def test_able_to_register_when_not_heed_penalties_with_penalties(self):
"""Test that user is able to register when heed_penalties is false and user has penalties"""
event = Event.objects.get(title="POOLS_WITH_REGISTRATIONS")
Expand Down
17 changes: 15 additions & 2 deletions lego/apps/users/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from enum import Enum

from django.db import models

MALE = "male"
FEMALE = "female"
OTHER = "other"
Expand Down Expand Up @@ -95,8 +97,6 @@ def values(cls) -> list[str]:
FSGroup.MSSECCLO: FOURTH_GRADE_KOMTEK,
}

STUDENT_EMAIL_DOMAIN = "stud.ntnu.no"

GROUP_COMMITTEE = "komite"
GROUP_INTEREST = "interesse"
GROUP_BOARD = "styre"
Expand Down Expand Up @@ -142,3 +142,16 @@ def values(cls) -> list[str]:
(LIGHT_THEME, LIGHT_THEME),
(DARK_THEME, DARK_THEME),
)


LATE_PRESENCE_PENALTY_WEIGHT = 1


class PENALTY_WEIGHTS(models.TextChoices):
LATE_PRESENCE = 1


class PENALTY_TYPES(models.TextChoices):
PRESENCE = "presence"
PAYMENT = "payment"
OTHER = "other"
25 changes: 25 additions & 0 deletions lego/apps/users/migrations/0042_penalty_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.0.10 on 2024-03-14 10:27

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("users", "0041_user_linkedin_id_alter_user_github_username"),
]

operations = [
migrations.AddField(
model_name="penalty",
name="type",
field=models.CharField(
choices=[
("presence", "Presence"),
("payment", "Payment"),
("other", "Other"),
],
default="other",
max_length=50,
),
),
]
5 changes: 5 additions & 0 deletions lego/apps/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,11 @@ class Penalty(BasisModel):
source_event = models.ForeignKey(
"events.Event", related_name="penalties", on_delete=models.CASCADE
)
type = models.CharField(
max_length=50,
choices=constants.PENALTY_TYPES.choices,
default=constants.PENALTY_TYPES.OTHER,
)

objects = UserPenaltyManager() # type: ignore

Expand Down

0 comments on commit 46c514f

Please sign in to comment.