Skip to content

Commit

Permalink
Add soft_extended_due_date field to Group model
Browse files Browse the repository at this point in the history
Includes server-side validation to ensure `extended_due_date <
soft_extended_due_date`
  • Loading branch information
MattyMay committed Aug 23, 2024
1 parent 9037394 commit 3ddb644
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 6 deletions.
18 changes: 18 additions & 0 deletions autograder/core/migrations/0108_group_soft_extended_due_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.2 on 2024-08-22 19:23

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0107_auto_20240806_1626'),
]

operations = [
migrations.AddField(
model_name='group',
name='soft_extended_due_date',
field=models.DateTimeField(blank=True, default=None, help_text='When this field is set, it indicates the extended due date\n that is visible to members of the group. Members of the group will\n still be able to submit after this date but before the\n extended_due_date.\n Default value: None', null=True),
),
]
34 changes: 31 additions & 3 deletions autograder/core/models/group/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from ..submission import Submission
from . import verification


class GroupManager(ag_model_base.AutograderModelManager['Group']):
# Technically this violates the Liskov Substitution Principal.
# However, Group.objects will always be an instance of
Expand Down Expand Up @@ -49,9 +48,9 @@ def validate_and_create( # type: ignore
member_names = [
user.username for user in sorted(members, key=lambda user: user.username)]
group = self.model(_member_names=member_names, **kwargs)
group.full_clean()
group.save()
group.members.add(*members)
group.full_clean()
return group


Expand Down Expand Up @@ -100,6 +99,14 @@ def member_names(self) -> List[str]:
date, overriding the project closing time.
Default value: None""")

soft_extended_due_date = models.DateTimeField(
null=True, default=None, blank=True,
help_text="""When this field is set, it indicates the extended due date
that is visible to members of the group. Members of the group will
still be able to submit after this date but before the
extended_due_date.
Default value: None""")

# Remove in version 5.0.0
old_bonus_submissions_remaining = models.IntegerField(
blank=True,
Expand Down Expand Up @@ -179,6 +186,22 @@ def _is_towards_limit(submission: Submission) -> bool:

return utils.count_if(self.submissions.all(), _is_towards_limit)

def clean(self) -> None:
super().clean()

if self.extended_due_date is not None:
self.extended_due_date = self.extended_due_date.replace(second=0, microsecond=0)
if self.soft_extended_due_date is not None:
self.soft_extended_due_date = self.soft_extended_due_date.replace(second=0, microsecond=0)

print(f"{self.extended_due_date=}")

if self.extended_due_date is not None and self.soft_extended_due_date is not None:
if self.extended_due_date < self.soft_extended_due_date:
raise ValidationError(
{'soft_extended_due_date': (
'Soft extended due date must be before hard extended due date')})

def save(self, *args: Any, **kwargs: Any) -> None:
super().save(*args, **kwargs)

Expand Down Expand Up @@ -235,6 +258,7 @@ def validate_and_update( # type: ignore
'pk',
'project',
'extended_due_date',
'soft_extended_due_date',
'member_names',
'members',

Expand All @@ -250,7 +274,11 @@ def validate_and_update( # type: ignore
)
SERIALIZE_RELATED = ('members',)

EDITABLE_FIELDS = ('extended_due_date', 'bonus_submissions_remaining')
EDITABLE_FIELDS = (
'extended_due_date',
'soft_extended_due_date',
'bonus_submissions_remaining'
)

def to_dict(self) -> Dict[str, object]:
result = super().to_dict()
Expand Down
36 changes: 33 additions & 3 deletions autograder/core/tests/test_models/test_group/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def test_valid_initialization_no_defaults(self):

group.refresh_from_db()

self.assertEqual(group.extended_due_date, extended_due_date)
self.assertEqual(group.extended_due_date, extended_due_date.replace(second=0, microsecond=0))
self.assertCountEqual(self.student_users, group.members.all())
self.assertEqual(self.project, group.project)

Expand Down Expand Up @@ -202,6 +202,7 @@ def test_serializable_fields(self):
'members',
'project',
'extended_due_date',
'soft_extended_due_date',

'bonus_submissions_remaining',

Expand All @@ -221,8 +222,9 @@ def test_serializable_fields(self):
self.assertIsInstance(serialized['members'][0], dict)

def test_editable_fields(self):
self.assertCountEqual(['extended_due_date', 'bonus_submissions_remaining'],
ag_models.Group.get_editable_fields())
self.assertCountEqual(
['extended_due_date', 'soft_extended_due_date', 'bonus_submissions_remaining'],
ag_models.Group.get_editable_fields())


class BonusSubmissionTokenCountTestCase(test_ut.UnitTestBase):
Expand Down Expand Up @@ -632,3 +634,31 @@ def test_exception_group_mix_of_student_and_staff(self):
ag_models.Group.objects.validate_and_create(
members=self.staff_users + self.student_users,
project=self.project)

class HardAndSoftExtendedDueDateTestCase(_SetUp):
def test_valid_soft_extended_due_date_None_extended_due_date(self):
extended_due_date = timezone.now() + datetime.timedelta(days=1)
group = ag_models.Group.objects.validate_and_create(
members=self.student_users,
project=self.project,
extended_due_date=extended_due_date,
soft_extended_due_date=None)

group.refresh_from_db()
print(f"{extended_due_date.replace(second=0, microsecond=0)}")
print(f"{group.extended_due_date=}")
self.assertEqual(
extended_due_date.replace(second=0, microsecond=0),
group.extended_due_date)
self.assertIsNone(group.soft_extended_due_date)

def test_error_soft_extended_due_date_after_extended_due_date(self):
extended_due_date = timezone.now() + timezone.timedelta(minutes=5)
soft_extended_due_date = extended_due_date + timezone.timedelta(minutes=5)

with self.assertRaises(exceptions.ValidationError):
ag_models.Group.objects.validate_and_create(
members=self.student_users,
project=self.project,
extended_due_date=extended_due_date,
soft_extended_due_date=soft_extended_due_date)
17 changes: 17 additions & 0 deletions autograder/rest_api/schema/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5551,6 +5551,14 @@ components:
nullable: true
type: string
format: date-time
soft_extended_due_date:
description: "When this field is set, it indicates the extended due date\n\
\ that is visible to members of the group. Members of the group\
\ will\n still be able to submit after this date but before\
\ the\n extended_due_date.\n Default value: None"
nullable: true
type: string
format: date-time
member_names:
description: A list of usernames of the group members, sorted alphabetically.
type: array
Expand Down Expand Up @@ -5601,6 +5609,7 @@ components:
- pk
- project
- extended_due_date
- soft_extended_due_date
- member_names
- members
- bonus_submissions_remaining
Expand Down Expand Up @@ -5635,6 +5644,14 @@ components:
nullable: true
type: string
format: date-time
soft_extended_due_date:
description: "When this field is set, it indicates the extended due date\n\
\ that is visible to members of the group. Members of the group\
\ will\n still be able to submit after this date but before\
\ the\n extended_due_date.\n Default value: None"
nullable: true
type: string
format: date-time
bonus_submissions_remaining:
description: The number of unused bonus submission tokens this group has.
type: integer
Expand Down

0 comments on commit 3ddb644

Please sign in to comment.