diff --git a/aplus/settings.py b/aplus/settings.py index eac9c47e1..75787aea1 100644 --- a/aplus/settings.py +++ b/aplus/settings.py @@ -187,6 +187,7 @@ 'apps', 'redirect_old_urls', 'lti_tool', + 'site_alert', 'js_jquery_toggle', 'django_colortag', diff --git a/course/templatetags/base.py b/course/templatetags/base.py index 5cdc1c441..530a5ce38 100644 --- a/course/templatetags/base.py +++ b/course/templatetags/base.py @@ -7,6 +7,7 @@ from django.utils.translation import get_language, gettext_lazy as _ from lib.helpers import remove_query_param_from_url, settings_text, update_url_params from exercise.submission_models import PendingSubmission +from site_alert.models import SiteAlert register = template.Library() @@ -16,7 +17,7 @@ def pick_localized(message): if message and isinstance(message, dict): return (message.get(get_language()) or message.get(settings.LANGUAGE_CODE[:2]) or - message.values()[0]) + list(message.values())[0]) return message @@ -57,11 +58,13 @@ def course_alert(instance): @register.simple_tag def site_alert(): - message = settings.SITEWIDE_ALERT_TEXT - if message: - return mark_safe('
{}
' - .format(pick_localized(message))) - return '' + alerts = SiteAlert.objects.filter(status=SiteAlert.STATUS.ACTIVE) + return mark_safe( + ''.join( + '
{}
'.format(pick_localized(alert.alert)) + for alert in alerts + ) + ) @register.simple_tag diff --git a/deviations/admin.py b/deviations/admin.py index dc2af264c..72bc4cc47 100644 --- a/deviations/admin.py +++ b/deviations/admin.py @@ -20,7 +20,7 @@ class DeadlineRuleDeviationAdmin(admin.ModelAdmin): list_display = ( 'submitter', 'exercise', - 'extra_minutes', + 'extra_seconds', 'granter', 'grant_time', ) diff --git a/deviations/forms.py b/deviations/forms.py index b9576f2e6..04eb3468d 100644 --- a/deviations/forms.py +++ b/deviations/forms.py @@ -80,11 +80,11 @@ def clean(self) -> Dict[str, Any]: class DeadlineRuleDeviationForm(BaseDeviationForm): - minutes = DurationField( + seconds = DurationField( required=False, min_value=1, - label=_('LABEL_MINUTES'), - help_text=_('DEVIATION_EXTRA_MINUTES_HELPTEXT'), + label=_('LABEL_SECONDS'), + help_text=_('DEVIATION_EXTRA_SECONDS_HELPTEXT'), ) new_date = forms.DateTimeField( required=False, @@ -108,10 +108,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def clean(self) -> Dict[str, Any]: cleaned_data = super().clean() new_date = cleaned_data.get("new_date") - minutes = cleaned_data.get("minutes") - if minutes and new_date or not minutes and not new_date: + seconds = cleaned_data.get("seconds") + if seconds and new_date or not seconds and not new_date: raise forms.ValidationError( - _("MINUTES_AND_DATE_MISSING")) + _("SECONDS_AND_DATE_MISSING")) return cleaned_data diff --git a/deviations/migrations/0006_rename_extra_minutes_deadlineruledeviation_extra_seconds.py b/deviations/migrations/0006_rename_extra_minutes_deadlineruledeviation_extra_seconds.py new file mode 100644 index 000000000..8df0ac19e --- /dev/null +++ b/deviations/migrations/0006_rename_extra_minutes_deadlineruledeviation_extra_seconds.py @@ -0,0 +1,30 @@ +from django.db import migrations, models + + +def multiply_by_sixty(apps, schema_editor): + DeadlineRuleDeviation = apps.get_model('deviations', 'DeadlineRuleDeviation') + # Retrieve all instances of DeadlineRuleDeviation and update extra_seconds field + for deviation in DeadlineRuleDeviation.objects.all(): + deviation.extra_seconds *= 60 + deviation.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('deviations', '0005_auto_20220211_1540'), + ] + + operations = [ + migrations.RenameField( + model_name='deadlineruledeviation', + old_name='extra_minutes', + new_name='extra_seconds', + ), + migrations.AlterField( + model_name='deadlineruledeviation', + name='extra_seconds', + field=models.IntegerField(verbose_name='LABEL_EXTRA_SECONDS'), + ), + migrations.RunPython(multiply_by_sixty), + ] diff --git a/deviations/models.py b/deviations/models.py index 1b4d0768a..563fae13b 100644 --- a/deviations/models.py +++ b/deviations/models.py @@ -154,12 +154,12 @@ def get_override_url(cls, instance: CourseInstance) -> str: class DeadlineRuleDeviationManager(SubmissionRuleDeviationManager['DeadlineRuleDeviation']): - max_order_by = "-extra_minutes" + max_order_by = "-extra_seconds" class DeadlineRuleDeviation(SubmissionRuleDeviation): - extra_minutes = models.IntegerField( - verbose_name=_('LABEL_EXTRA_MINUTES'), + extra_seconds = models.IntegerField( + verbose_name=_('LABEL_EXTRA_SECONDS'), ) without_late_penalty = models.BooleanField( verbose_name=_('LABEL_WITHOUT_LATE_PENALTY'), @@ -173,7 +173,7 @@ class Meta(SubmissionRuleDeviation.Meta): verbose_name_plural = _('MODEL_NAME_DEADLINE_RULE_DEVIATION_PLURAL') def get_extra_time(self): - return timedelta(minutes=self.extra_minutes) + return timedelta(seconds=self.extra_seconds) def get_new_deadline(self, normal_deadline: Optional[datetime] = None) -> datetime: """ @@ -191,18 +191,18 @@ def get_normal_deadline(self): return self.exercise.course_module.closing_time def update_by_form(self, form_data: Dict[str, Any]) -> None: - minutes = form_data.get('minutes') + seconds = form_data.get('seconds') new_date = form_data.get('new_date') if new_date: - minutes = self.exercise.delta_in_minutes_from_closing_to_date(new_date) + seconds = self.exercise.delta_in_seconds_from_closing_to_date(new_date) else: - minutes = int(minutes) - self.extra_minutes = minutes + seconds = int(seconds) + self.extra_seconds = seconds self.without_late_penalty = bool(form_data.get('without_late_penalty')) def is_groupable(self, other: 'DeadlineRuleDeviation') -> bool: return ( - self.extra_minutes == other.extra_minutes + self.extra_seconds == other.extra_seconds and self.without_late_penalty == other.without_late_penalty ) diff --git a/deviations/templates/deviations/list_dl.html b/deviations/templates/deviations/list_dl.html index d87b7d8e3..652b9b8c3 100644 --- a/deviations/templates/deviations/list_dl.html +++ b/deviations/templates/deviations/list_dl.html @@ -38,7 +38,7 @@

{% translate "DEADLINE_DEVIATIONS" %}

{% translate "SUBMITTER" %} {% translate "EXERCISE" %} - {% translate "EXTRA_MINUTES" %} + {% translate "EXTRA_SECONDS" %} {% translate "DEADLINE" %} {% translate "DEADLINE_DEVIATIONS" %} {% endblocktranslate %}) - {{ deviations.0.extra_minutes }} + {{ deviations.0.extra_seconds }} {{ deviations.0.get_new_deadline|date:'SHORT_DATETIME_FORMAT' }} @@ -114,7 +114,7 @@

{% translate "DEADLINE_DEVIATIONS" %}

{{ deviation.exercise.hierarchical_name }} - {{ deviation.extra_minutes }} + {{ deviation.extra_seconds }} {{ deviation.get_new_deadline|date:'SHORT_DATETIME_FORMAT' }} diff --git a/deviations/templates/deviations/override_dl.html b/deviations/templates/deviations/override_dl.html index 8f6f83068..24f5ef806 100644 --- a/deviations/templates/deviations/override_dl.html +++ b/deviations/templates/deviations/override_dl.html @@ -22,8 +22,8 @@

{% translate "SELECT_DEVIATIONS_TO_BE_OVERRIDDEN" %}

{% translate "SUBMITTER" %} {% translate "EXERCISE" %} - {% translate "EXTRA_MINUTES" %} ({% translate "OVERRIDE_BEFORE" %}) - {% translate "EXTRA_MINUTES" %} ({% translate "OVERRIDE_AFTER" %}) + {% translate "EXTRA_SECONDS" %} ({% translate "OVERRIDE_BEFORE" %}) + {% translate "EXTRA_SECONDS" %} ({% translate "OVERRIDE_AFTER" %}) {% translate "DEADLINE" %} ({% translate "OVERRIDE_BEFORE" %}) {% translate "DEADLINE" %} ({% translate "OVERRIDE_AFTER" %}) {% translate "SELECT_DEVIATIONS_TO_BE_OVERRIDDEN" %} {% translate "UNKNOWN" as unknown %} {% for deviations, can_group, group_id, _ in deviation_groups %} - {% new_deviation_minutes deviations.0 session_data.minutes session_data.new_date as new_deviation_minutes %} - {% new_deviation_date deviations.0 session_data.minutes session_data.new_date as new_deviation_date %} + {% new_deviation_seconds deviations.0 session_data.seconds session_data.new_date as new_deviation_seconds %} + {% new_deviation_date deviations.0 session_data.seconds session_data.new_date as new_deviation_date %} {% if can_group %} @@ -63,8 +63,8 @@

{% translate "SELECT_DEVIATIONS_TO_BE_OVERRIDDEN" %}

- {{ deviations.0.extra_minutes }} - {{ new_deviation_minutes }} + {{ deviations.0.extra_seconds }} + {{ new_deviation_seconds }} {{ deviations.0.get_new_deadline|date:'SHORT_DATETIME_FORMAT' }} @@ -102,8 +102,8 @@

{% translate "SELECT_DEVIATIONS_TO_BE_OVERRIDDEN" %}

{{ deviation.exercise.hierarchical_name }} - {{ deviation.extra_minutes }} - {{ new_deviation_minutes }} + {{ deviation.extra_seconds }} + {{ new_deviation_seconds }} {{ deviation.get_new_deadline|date:'SHORT_DATETIME_FORMAT' }} diff --git a/deviations/templatetags/deviations.py b/deviations/templatetags/deviations.py index 29bc9a31d..679836a70 100644 --- a/deviations/templatetags/deviations.py +++ b/deviations/templatetags/deviations.py @@ -10,23 +10,23 @@ @register.simple_tag -def new_deviation_minutes( +def new_deviation_seconds( deviation: DeadlineRuleDeviation, - minutes: Optional[int], + seconds: Optional[int], date: Optional[datetime.datetime] ) -> int: """ - Get the extra minutes for a deadline deviation after being overridden. + Get the extra seconds for a deadline deviation after being overridden. """ if date: - return deviation.exercise.delta_in_minutes_from_closing_to_date(date) - return minutes + return deviation.exercise.delta_in_seconds_from_closing_to_date(date) + return seconds @register.simple_tag def new_deviation_date( deviation: DeadlineRuleDeviation, - minutes: Optional[int], + seconds: Optional[int], date: Optional[datetime.datetime] ) -> datetime.datetime: """ @@ -34,4 +34,4 @@ def new_deviation_date( """ if date: return date - return deviation.exercise.course_module.closing_time + datetime.timedelta(minutes=minutes) + return deviation.exercise.course_module.closing_time + datetime.timedelta(seconds=seconds) diff --git a/deviations/tests.py b/deviations/tests.py index 77965c627..a746c0cd1 100644 --- a/deviations/tests.py +++ b/deviations/tests.py @@ -42,8 +42,8 @@ def setUp(self): url="Course-Url", ) - # Truncate the datetime to minutes to make testing deadline deviations more convenient - self.today = timezone.now().replace(second=0, microsecond=0) + # Truncate the datetime to seconds to make testing deadline deviations more convenient + self.today = timezone.now().replace(microsecond=0) self.tomorrow = self.today + timedelta(days=1) self.two_days_from_now = self.today + timedelta(days=2) @@ -142,21 +142,21 @@ def setUp(self): exercise=self.exercise_with_attachment, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=1440, # One day + extra_seconds=24*60*60, # One day ) self.deadline_rule_deviation_u1_e2 = DeadlineRuleDeviation.objects.create( exercise=self.exercise_with_attachment_2, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=2880, # Two days + extra_seconds=2*24*60*60, # Two days ) self.deadline_rule_deviation_u2_e1 = DeadlineRuleDeviation.objects.create( exercise=self.exercise_with_attachment, submitter=self.user_2.userprofile, granter=self.teacher.userprofile, - extra_minutes=4320, # Three days + extra_seconds=3*24*60*60, # Three days ) def test_deadline_rule_deviation_extra_time(self): @@ -181,7 +181,7 @@ def test_get_max_deviations(self): ) self.assertIsNotNone(deviation) self.assertEqual(deviation.exercise.id, self.exercise_with_attachment.id) - self.assertEqual(deviation.extra_minutes, 1440) + self.assertEqual(deviation.extra_seconds, 24*60*60) deviation = DeadlineRuleDeviation.objects.get_max_deviation( self.user_2.userprofile, @@ -189,7 +189,7 @@ def test_get_max_deviations(self): ) self.assertIsNotNone(deviation) self.assertEqual(deviation.exercise.id, self.exercise_with_attachment.id) - self.assertEqual(deviation.extra_minutes, 4320) + self.assertEqual(deviation.extra_seconds, 3*24*60*60) def test_get_max_deviations_multiple(self): # Test that the get_max_deviations method returns the correct deviation @@ -203,9 +203,9 @@ def test_get_max_deviations_multiple(self): for deviation in deviations: counter += 1 if deviation.exercise.id == self.exercise_with_attachment.id: - self.assertEqual(deviation.extra_minutes, 1440) + self.assertEqual(deviation.extra_seconds, 24*60*60) elif deviation.exercise.id == self.exercise_with_attachment_2.id: - self.assertEqual(deviation.extra_minutes, 2880) + self.assertEqual(deviation.extra_seconds, 2*24*60*60) else: raise self.failureException('Unexpected exercise returned') self.assertEqual(counter, 2) @@ -218,7 +218,7 @@ def test_get_max_deviations_multiple(self): for deviation in deviations: counter += 1 if deviation.exercise.id == self.exercise_with_attachment.id: - self.assertEqual(deviation.extra_minutes, 4320) + self.assertEqual(deviation.extra_seconds, 3*24*60*60) else: raise self.failureException('Unexpected exercise returned') self.assertEqual(counter, 1) @@ -240,7 +240,7 @@ def test_get_max_deviations_group(self): ) self.assertIsNotNone(deviation) self.assertEqual(deviation.exercise.id, self.exercise_with_attachment.id) - self.assertEqual(deviation.extra_minutes, 4320) + self.assertEqual(deviation.extra_seconds, 3*24*60*60) deviation = DeadlineRuleDeviation.objects.get_max_deviation( self.user_2.userprofile, @@ -248,7 +248,7 @@ def test_get_max_deviations_group(self): ) self.assertIsNotNone(deviation) self.assertEqual(deviation.exercise.id, self.exercise_with_attachment.id) - self.assertEqual(deviation.extra_minutes, 4320) + self.assertEqual(deviation.extra_seconds, 3*24*60*60) def test_update_by_form(self): deviation = DeadlineRuleDeviation( @@ -256,17 +256,17 @@ def test_update_by_form(self): submitter=self.user.userprofile, ) deviation.update_by_form({ - 'minutes': 60, + 'seconds': 60*60, 'without_late_penalty': False, }) - self.assertEqual(deviation.extra_minutes, 60) + self.assertEqual(deviation.extra_seconds, 60*60) self.assertEqual(deviation.without_late_penalty, False) deviation.update_by_form({ 'new_date': timezone.make_naive(self.two_days_from_now), 'without_late_penalty': True, }) - self.assertEqual(deviation.extra_minutes, 1440) + self.assertEqual(deviation.extra_seconds, 24*60*60) self.assertEqual(deviation.without_late_penalty, True) deviation = MaxSubmissionsRuleDeviation( exercise=self.exercise_with_attachment, @@ -282,21 +282,21 @@ def test_is_groupable(self): deviation_1 = DeadlineRuleDeviation( exercise=self.exercise_with_attachment, submitter=self.user.userprofile, - extra_minutes=60, + extra_seconds=60*60, without_late_penalty=False, ) deviation_2 = DeadlineRuleDeviation( exercise=self.exercise_with_attachment_2, submitter=self.user_2.userprofile, - extra_minutes=60, + extra_seconds=60*60, without_late_penalty=False, ) self.assertTrue(deviation_1.is_groupable(deviation_2)) - deviation_2.extra_minutes = 120 + deviation_2.extra_seconds = 120*60 self.assertFalse(deviation_1.is_groupable(deviation_2)) - deviation_2.extra_minutes = 60 + deviation_2.extra_seconds = 60*60 deviation_2.without_late_penalty = True self.assertFalse(deviation_1.is_groupable(deviation_2)) @@ -316,7 +316,7 @@ def test_is_groupable(self): self.assertFalse(deviation_1.is_groupable(deviation_2)) def test_get_deviation_groups(self): - # The deviations of user 1 can't be grouped because of different extra minutes + # The deviations of user 1 can't be grouped because of different extra seconds # The deviations of user 2 can't be grouped because there is only one deviation groups = list(get_deviation_groups(DeadlineRuleDeviation.objects.all())) deviations, can_group, group_id, _ = groups[0] @@ -328,11 +328,11 @@ def test_get_deviation_groups(self): self.assertFalse(can_group) self.assertIsNone(group_id) - self.deadline_rule_deviation_u1_e1.extra_minutes = 60 + self.deadline_rule_deviation_u1_e1.extra_seconds = 60*60 self.deadline_rule_deviation_u1_e1.save() - self.deadline_rule_deviation_u1_e2.extra_minutes = 60 + self.deadline_rule_deviation_u1_e2.extra_seconds = 60*60 self.deadline_rule_deviation_u1_e2.save() - self.deadline_rule_deviation_u2_e1.extra_minutes = 60 + self.deadline_rule_deviation_u2_e1.extra_seconds = 60*60 self.deadline_rule_deviation_u2_e1.save() # The deviations of user 1 can now be grouped @@ -359,11 +359,11 @@ def test_get_deviation_groups(self): self.assertIsNone(group_id) # Reset original values - self.deadline_rule_deviation_u1_e1.extra_minutes = 1440 + self.deadline_rule_deviation_u1_e1.extra_seconds = 24*60*60 self.deadline_rule_deviation_u1_e1.save() - self.deadline_rule_deviation_u1_e2.extra_minutes = 2880 + self.deadline_rule_deviation_u1_e2.extra_seconds = 2*24*60*60 self.deadline_rule_deviation_u1_e2.save() - self.deadline_rule_deviation_u2_e1.extra_minutes = 4320 + self.deadline_rule_deviation_u2_e1.extra_seconds = 3*24*60*60 self.deadline_rule_deviation_u2_e1.save() extra_exercise.delete() @@ -377,7 +377,7 @@ def test_add_deadline_deviations(self): { 'module': [self.course_module_2.id], 'submitter_tag': [self.user_tag.id], - 'minutes': 60, + 'seconds': 60*60, } ) self.assertEqual(response.status_code, 302) @@ -387,13 +387,13 @@ def test_add_deadline_deviations(self): exercise=self.module_2_exercise_1, submitter=self.user.userprofile, ) - self.assertEqual(deviation.extra_minutes, 60) + self.assertEqual(deviation.extra_seconds, 60*60) deviation.delete() deviation = DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_2, submitter=self.user.userprofile, ) - self.assertEqual(deviation.extra_minutes, 60) + self.assertEqual(deviation.extra_seconds, 60*60) deviation.delete() with self.assertRaises(DeadlineRuleDeviation.DoesNotExist): @@ -413,7 +413,7 @@ def test_add_deadline_deviations(self): { 'exercise': [self.module_2_exercise_1.id], 'submitter': [self.user.userprofile.id, self.user_2.userprofile.id], - 'minutes': 120, + 'seconds': 120*60, } ) self.assertEqual(response.status_code, 302) @@ -423,13 +423,13 @@ def test_add_deadline_deviations(self): exercise=self.module_2_exercise_1, submitter=self.user.userprofile, ) - self.assertEqual(deviation.extra_minutes, 120) + self.assertEqual(deviation.extra_seconds, 120*60) deviation.delete() deviation = DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user_2.userprofile, ) - self.assertEqual(deviation.extra_minutes, 120) + self.assertEqual(deviation.extra_seconds, 120*60) deviation.delete() with self.assertRaises(DeadlineRuleDeviation.DoesNotExist): @@ -451,7 +451,7 @@ def test_add_deadline_deviations(self): 'exercise': [self.module_2_exercise_1.id], 'submitter_tag': [self.user_tag.id], 'submitter': [self.user_2.userprofile.id], - 'minutes': 180, + 'seconds': 180*60, } ) self.assertEqual(response.status_code, 302) @@ -461,25 +461,25 @@ def test_add_deadline_deviations(self): exercise=self.module_2_exercise_1, submitter=self.user.userprofile, ) - self.assertEqual(deviation.extra_minutes, 180) + self.assertEqual(deviation.extra_seconds, 180*60) deviation.delete() deviation = DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user_2.userprofile, ) - self.assertEqual(deviation.extra_minutes, 180) + self.assertEqual(deviation.extra_seconds, 180*60) deviation.delete() deviation = DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_2, submitter=self.user.userprofile, ) - self.assertEqual(deviation.extra_minutes, 180) + self.assertEqual(deviation.extra_seconds, 180*60) deviation.delete() deviation = DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_2, submitter=self.user_2.userprofile, ) - self.assertEqual(deviation.extra_minutes, 180) + self.assertEqual(deviation.extra_seconds, 180*60) deviation.delete() # No extra deviations should have been created @@ -498,7 +498,7 @@ def test_override_deadline_deviations(self): { 'exercise': [self.module_2_exercise_1.id], 'submitter': [self.user.userprofile.id], - 'minutes': 30, + 'seconds': 30*60, } ) self.assertEqual(response.status_code, 302) @@ -508,8 +508,8 @@ def test_override_deadline_deviations(self): DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user.userprofile, - ).extra_minutes, - 30, + ).extra_seconds, + 30*60, ) # Create new deviations, one overlapping the initial deviation @@ -518,7 +518,7 @@ def test_override_deadline_deviations(self): { 'exercise': [self.module_2_exercise_1.id, self.module_2_exercise_2.id], 'submitter': [self.user.userprofile.id], - 'minutes': 60, + 'seconds': 60*60, } ) self.assertEqual(response.status_code, 302) @@ -528,8 +528,8 @@ def test_override_deadline_deviations(self): DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user.userprofile, - ).extra_minutes, - 30, + ).extra_seconds, + 30*60, ) with self.assertRaises(DeadlineRuleDeviation.DoesNotExist): DeadlineRuleDeviation.objects.get( @@ -551,15 +551,15 @@ def test_override_deadline_deviations(self): DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user.userprofile, - ).extra_minutes, - 60, + ).extra_seconds, + 60*60, ) self.assertEqual( DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_2, submitter=self.user.userprofile, - ).extra_minutes, - 60, + ).extra_seconds, + 60*60, ) # Create deviations overlapping both deviations, override 2nd one @@ -568,7 +568,7 @@ def test_override_deadline_deviations(self): { 'exercise': [self.module_2_exercise_1.id, self.module_2_exercise_2.id], 'submitter': [self.user.userprofile.id], - 'minutes': 120, + 'seconds': 120*60, } ) self.assertEqual(response.status_code, 302) @@ -587,15 +587,15 @@ def test_override_deadline_deviations(self): DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user.userprofile, - ).extra_minutes, - 60, + ).extra_seconds, + 60*60, ) self.assertEqual( DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_2, submitter=self.user.userprofile, - ).extra_minutes, - 120, + ).extra_seconds, + 120*60, ) DeadlineRuleDeviation.objects.filter(exercise__course_module=self.course_module_2).delete() @@ -611,7 +611,7 @@ def test_remove_deadline_deviations(self): { 'exercise': [self.module_2_exercise_1.id, self.module_2_exercise_2.id], 'submitter': [self.user.userprofile.id], - 'minutes': 45, + 'seconds': 45*60, } ) self.assertEqual(response.status_code, 302) @@ -621,15 +621,15 @@ def test_remove_deadline_deviations(self): DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_1, submitter=self.user.userprofile, - ).extra_minutes, - 45, + ).extra_seconds, + 45*60, ) self.assertEqual( DeadlineRuleDeviation.objects.get( exercise=self.module_2_exercise_2, submitter=self.user.userprofile, - ).extra_minutes, - 45, + ).extra_seconds, + 45*60, ) # Remove one deviation diff --git a/deviations/views.py b/deviations/views.py index 8a09aed0d..4465c2b21 100644 --- a/deviations/views.py +++ b/deviations/views.py @@ -34,7 +34,7 @@ def get_success_no_override_url(self) -> str: def get_initial_get_param_spec(self) -> Dict[str, Optional[Callable[[str], Any]]]: spec = super().get_initial_get_param_spec() spec.update({ - "minutes": int, + "seconds": int, "new_date": None, "without_late_penalty": lambda x: x == "true", }) @@ -43,7 +43,7 @@ def get_initial_get_param_spec(self) -> Dict[str, Optional[Callable[[str], Any]] def serialize_session_data(self, form_data: Dict[str, Any]) -> Dict[str, Any]: result = super().serialize_session_data(form_data) result.update({ - 'minutes': form_data['minutes'], + 'seconds': form_data['seconds'], 'new_date': str(form_data['new_date']) if form_data['new_date'] else None, 'without_late_penalty': form_data['without_late_penalty'], }) @@ -61,7 +61,7 @@ def get_success_url(self) -> str: def deserialize_session_data(self, session_data: Dict[str, Any]) -> Dict[str, Any]: result = super().deserialize_session_data(session_data) result.update({ - 'minutes': session_data['minutes'], + 'seconds': session_data['seconds'], 'new_date': parse_datetime(session_data['new_date']) if session_data['new_date'] else None, 'without_late_penalty': session_data['without_late_penalty'], }) diff --git a/exercise/cache/points.py b/exercise/cache/points.py index 3904a5254..cfc18e8b1 100644 --- a/exercise/cache/points.py +++ b/exercise/cache/points.py @@ -665,7 +665,7 @@ def _generate_data( # noqa: MC0001 self.personal_deadline_has_penalty = None for deviation in deadline_deviations: self.personal_deadline = ( - self.closing_time + datetime.timedelta(minutes=deviation.extra_minutes) + self.closing_time + datetime.timedelta(seconds=deviation.extra_seconds) ) self.personal_deadline_has_penalty = not deviation.without_late_penalty diff --git a/exercise/exercise_models.py b/exercise/exercise_models.py index a70a51e38..96aa58992 100644 --- a/exercise/exercise_models.py +++ b/exercise/exercise_models.py @@ -730,18 +730,18 @@ def get_timing(self, students, when): return self.TIMING.CLOSED_AFTER, dl - def delta_in_minutes_from_closing_to_date(self, future_date): + def delta_in_seconds_from_closing_to_date(self, future_date): module_close = self.course_module.closing_time # module_close is in utc format 2018-04-10 23:59:00+00:00 # while future_date from the teacher submitted form might - # be in different formet, eg. 2018-05-15 23:59:00+03:00 + # be in a different format, e.g., 2018-05-15 23:59:00+03:00 # -> convert future_date to same format as module_close - string_date = str(future_date)[:16] + string_date = str(future_date)[:19] converted = timezone.make_aware( parse_datetime(string_date), timezone.get_current_timezone()) delta = converted - module_close - return delta.days * 24 * 60 + delta.seconds // 60 + return delta.days * 24 * 60 * 60 + delta.seconds def one_has_access(self, students, when=None): """ diff --git a/exercise/management/commands/export_submissions.py b/exercise/management/commands/export_submissions.py index e6b7af944..7ffdf84c3 100644 --- a/exercise/management/commands/export_submissions.py +++ b/exercise/management/commands/export_submissions.py @@ -270,7 +270,7 @@ def handle(self, *args, **options): # noqa: MC0001 'exercise__course_module__closing_time', 'exercise__course_module__course_instance__id', 'submitter__user__id', - 'extra_minutes', + 'extra_seconds', ) for dl_dev in all_deadline_deviations_queryset: diff --git a/exercise/reveal_models.py b/exercise/reveal_models.py index 6c7429689..3053b619e 100644 --- a/exercise/reveal_models.py +++ b/exercise/reveal_models.py @@ -75,11 +75,13 @@ def get_reveal_time(self, state: 'BaseRevealState') -> Optional[datetime.datetim if self.trigger in [RevealRule.TRIGGER.DEADLINE, RevealRule.TRIGGER.DEADLINE_OR_FULL_POINTS]: deadline = state.get_deadline() if deadline is not None: - return deadline + datetime.timedelta(minutes=self.delay_minutes or 0) + seconds = self.delay_minutes * 60 if self.delay_minutes else 0 + return deadline + datetime.timedelta(seconds=seconds) elif self.trigger == RevealRule.TRIGGER.DEADLINE_ALL: latest_deadline = state.get_latest_deadline() if latest_deadline is not None: - return latest_deadline + datetime.timedelta(minutes=self.delay_minutes or 0) + seconds = self.delay_minutes * 60 if self.delay_minutes else 0 + return latest_deadline + datetime.timedelta(seconds=seconds) return None diff --git a/exercise/reveal_states.py b/exercise/reveal_states.py index aa86f6c9d..6466c35fb 100644 --- a/exercise/reveal_states.py +++ b/exercise/reveal_states.py @@ -117,7 +117,7 @@ def get_latest_deadline(self) -> Optional[datetime.datetime]: self.max_deviation = ( DeadlineRuleDeviation.objects .filter(exercise_id=self.cache.id) - .order_by('-extra_minutes').first() + .order_by('-extra_seconds').first() ) self.max_deviation_fetched = True if self.max_deviation is not None: @@ -163,7 +163,7 @@ def get_latest_deadline(self) -> Optional[datetime.datetime]: self.max_deviation = ( DeadlineRuleDeviation.objects .filter(exercise__course_module_id=self.module_id) - .order_by('-extra_minutes').first() + .order_by('-extra_seconds').first() ) self.max_deviation_fetched = True if self.max_deviation is not None: diff --git a/exercise/tests.py b/exercise/tests.py index 7d5d23248..fac2b8fd9 100644 --- a/exercise/tests.py +++ b/exercise/tests.py @@ -259,7 +259,7 @@ def setUp(self): # pylint: disable=too-many-statements exercise=self.exercise_with_attachment, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=1440 # One day + extra_seconds=24*60*60 # One day ) @@ -431,7 +431,7 @@ def test_base_exercise_deadline_deviation(self): exercise=self.old_base_exercise, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=10*24*60 + extra_seconds=10*24*60*60 # Ten days ) self.assertTrue(self.old_base_exercise.one_has_access([self.user.userprofile])[0]) @@ -557,7 +557,7 @@ def test_submission_late_penalty_applied(self): exercise=self.base_exercise_with_late_submission_allowed, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=10*24*60, + extra_seconds=10*24*60*60, without_late_penalty=True ) self.late_late_submission_when_late_allowed.set_points(5, 10) @@ -913,7 +913,7 @@ def test_can_show_model_solutions(self): exercise=self.old_base_exercise, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=1440, # One day + extra_seconds=24*60*60, # One day ) self.assertFalse(self.old_base_exercise.can_show_model_solutions_to_student(self.user)) # Change the deadline extension so that it is not active anymore. @@ -924,7 +924,7 @@ def test_can_show_model_solutions(self): exercise=self.old_base_exercise, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=10, + extra_seconds=10*60, ) self.assertTrue(self.old_base_exercise.can_show_model_solutions_to_student(self.user)) @@ -941,7 +941,7 @@ def test_can_show_model_solutions(self): exercise=base_exercise_with_late_closed, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=60*24*2, + extra_seconds=2*24*60*60, # Two days ) self.assertFalse(base_exercise_with_late_closed.can_show_model_solutions_to_student(self.user)) self.assertFalse(base_exercise_with_late_closed.can_show_model_solutions_to_student(self.user2)) @@ -951,7 +951,7 @@ def test_can_show_model_solutions(self): exercise=base_exercise_with_late_closed, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=10, + extra_seconds=10*60, ) self.assertTrue(base_exercise_with_late_closed.can_show_model_solutions_to_student(self.user)) self.assertTrue(base_exercise_with_late_closed.can_show_model_solutions_to_student(self.user2)) @@ -967,7 +967,7 @@ def test_can_be_shown_as_module_model_solution(self): exercise=self.old_base_exercise, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=1440, # One day + extra_seconds=24*60*60, # One day ) reveal_rule = RevealRule.objects.create( trigger=RevealRule.TRIGGER.DEADLINE, @@ -987,7 +987,7 @@ def test_can_be_shown_as_module_model_solution(self): self.assertFalse(self.static_exercise.can_be_shown_as_module_model_solution(self.user)) self.assertTrue(chapter.can_be_shown_as_module_model_solution(self.user2)) - deadline_deviation_old_base_exercise.extra_minutes = 0 + deadline_deviation_old_base_exercise.extra_seconds = 0 deadline_deviation_old_base_exercise.save() self.assertTrue(chapter.can_be_shown_as_module_model_solution(self.user)) self.assertTrue(self.base_exercise.can_be_shown_as_module_model_solution(self.user)) @@ -1037,7 +1037,7 @@ def test_reveal_rule(self): exercise=self.old_base_exercise, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=30, + extra_seconds=30*60, ) old_reveal_state_deviation = ExerciseRevealState(self.old_base_exercise, self.user) user2_old_reveal_state_deviation = ExerciseRevealState(self.old_base_exercise, self.user2) @@ -1176,7 +1176,7 @@ def test_module_reveal_state(self): exercise=self.old_base_exercise, submitter=self.user.userprofile, granter=self.teacher.userprofile, - extra_minutes=4320, + extra_seconds=3*24*60*60, # Three days ) # this should have not effect on the module reveal state user_reveal_state = ModuleRevealState(self.course_module, self.user) diff --git a/exercise/tests_cache.py b/exercise/tests_cache.py index fdb48c159..98fba60d7 100644 --- a/exercise/tests_cache.py +++ b/exercise/tests_cache.py @@ -604,7 +604,7 @@ def test_is_revealed(self): exercise=self.exercise0, submitter=self.student.userprofile, granter=self.teacher.userprofile, - extra_minutes=2*24*60, + extra_seconds=2*24*60*60, ) reveal_rule = RevealRule.objects.create( trigger=RevealRule.TRIGGER.DEADLINE, diff --git a/lib/fields.py b/lib/fields.py index 8ff67bce3..1ed93654a 100644 --- a/lib/fields.py +++ b/lib/fields.py @@ -31,14 +31,15 @@ class DurationField(forms.MultiValueField): boxes, one for each given unit of time. The units of time are, by default, days, hours and minutes, but they can also be customized by passing a list of tuples where the first item is the name of the unit and the second item - is its factor relative to minutes (e.g. 60 for hours). + is its factor relative to seconds (e.g. 3600 for hours). """ - DAYS = (_('DURATION_UNIT_DAYS'), 60 * 24) - HOURS = (_('DURATION_UNIT_HOURS'), 60) - MINUTES = (_('DURATION_UNIT_MINUTES'), 1) + DAYS = (_('DURATION_UNIT_DAYS'), 60 * 60 * 24) + HOURS = (_('DURATION_UNIT_HOURS'), 60 * 60) + MINUTES = (_('DURATION_UNIT_MINUTES'), 60) + SECONDS = (_('DURATION_UNIT_SECONDS'), 1) # Default units - units: List[Tuple[str, int]] = [DAYS, HOURS, MINUTES] + units: List[Tuple[str, int]] = [DAYS, HOURS, MINUTES, SECONDS] def __init__( # pylint: disable=keyword-arg-before-vararg self, @@ -62,16 +63,16 @@ def __init__( # pylint: disable=keyword-arg-before-vararg def compress(self, data_list: List[Optional[int]]) -> Optional[int]: """ - Convert the values given in different units into minutes. + Convert the values given in different units into seconds. """ - total_minutes = None + total_seconds = None for value, (_name, factor) in zip(data_list, self.units): if value is None: continue - if total_minutes is None: - total_minutes = 0 - total_minutes += value * factor - return total_minutes + if total_seconds is None: + total_seconds = 0 + total_seconds += value * factor + return total_seconds class SearchSelectField(forms.ModelMultipleChoiceField): diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index d1531d404..02ef6a4c3 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -336,11 +336,11 @@ msgstr "Assistant" msgid "TEACHER" msgstr "Teacher" -#: course/models.py +#: course/models.py site_alert/models.py msgid "ACTIVE" msgstr "Active" -#: course/models.py +#: course/models.py site_alert/models.py msgid "REMOVED" msgstr "Removed" @@ -381,7 +381,7 @@ msgid "LABEL_ROLE" msgstr "role" #: course/models.py exercise/admin.py exercise/exercise_models.py -#: exercise/submission_models.py +#: exercise/submission_models.py site_alert/models.py msgid "LABEL_STATUS" msgstr "status" @@ -1524,11 +1524,11 @@ msgid "SUBMITTERS_AND_TAGS_MISSING" msgstr "You have to define submitters or tags." #: deviations/forms.py -msgid "LABEL_MINUTES" +msgid "LABEL_SECONDS" msgstr "Extra time" #: deviations/forms.py -msgid "DEVIATION_EXTRA_MINUTES_HELPTEXT" +msgid "DEVIATION_EXTRA_SECONDS_HELPTEXT" msgstr "Amount of extra time given. Leave blank if you fill in the date below." #: deviations/forms.py @@ -1571,7 +1571,7 @@ msgstr "" "granted to these students IN ADDITION to the ones with the tags." #: deviations/forms.py -msgid "MINUTES_AND_DATE_MISSING" +msgid "SECONDS_AND_DATE_MISSING" msgstr "You have to provide either the extra time or a date in the future." #: deviations/forms.py deviations/models.py @@ -1622,8 +1622,8 @@ msgid "MODEL_NAME_SUBMISSION_RULE_DEVIATION_PLURAL" msgstr "submission rule deviations" #: deviations/models.py -msgid "LABEL_EXTRA_MINUTES" -msgstr "extra minutes" +msgid "LABEL_EXTRA_SECONDS" +msgstr "extra seconds" #: deviations/models.py msgid "MODEL_NAME_DEADLINE_RULE_DEVIATION" @@ -1727,8 +1727,8 @@ msgstr "Assignment" #: deviations/templates/deviations/list_dl.html #: deviations/templates/deviations/override_dl.html -msgid "EXTRA_MINUTES" -msgstr "Extra minutes" +msgid "EXTRA_SECONDS" +msgstr "Extra seconds" #: deviations/templates/deviations/list_dl.html #: deviations/templates/deviations/override_dl.html @@ -5281,6 +5281,10 @@ msgstr "hours" msgid "DURATION_UNIT_MINUTES" msgstr "minutes" +#: lib/fields.py +msgid "DURATION_UNIT_SECONDS" +msgstr "seconds" + #: lib/fields.py msgid "CLIPBOARD_STUDENT_IDS" msgstr "Student IDs" @@ -5454,6 +5458,18 @@ msgstr "" msgid "SHIBBOLETH_ERROR_USER_ACCOUNT_DISABLED" msgstr "The user account has been disabled." +#: site_alert/models.py +msgid "LABEL_ALERT" +msgstr "alert" + +#: site_alert/models.py +msgid "MODEL_NAME_SITEALERT" +msgstr "site alert" + +#: site_alert/models.py +msgid "MODEL_NAME_SITEALERT_PLURAL" +msgstr "site alerts" + #: templates/403.html msgid "ACCESS_RESTRICTED_403" msgstr "Access restricted" diff --git a/locale/fi/LC_MESSAGES/django.po b/locale/fi/LC_MESSAGES/django.po index 614d01ebb..2d158b495 100644 --- a/locale/fi/LC_MESSAGES/django.po +++ b/locale/fi/LC_MESSAGES/django.po @@ -337,11 +337,11 @@ msgstr "Assistentti" msgid "TEACHER" msgstr "Opettaja" -#: course/models.py +#: course/models.py site_alert/models.py msgid "ACTIVE" msgstr "Aktiivinen" -#: course/models.py +#: course/models.py site_alert/models.py msgid "REMOVED" msgstr "Poistettu" @@ -382,7 +382,7 @@ msgid "LABEL_ROLE" msgstr "rooli" #: course/models.py exercise/admin.py exercise/exercise_models.py -#: exercise/submission_models.py +#: exercise/submission_models.py site_alert/models.py msgid "LABEL_STATUS" msgstr "tila" @@ -1533,11 +1533,11 @@ msgid "SUBMITTERS_AND_TAGS_MISSING" msgstr "Valitse opiskelijat tai merkinnät." #: deviations/forms.py -msgid "LABEL_MINUTES" +msgid "LABEL_SECONDS" msgstr "Ylimääräistä palautusaikaa" #: deviations/forms.py -msgid "DEVIATION_EXTRA_MINUTES_HELPTEXT" +msgid "DEVIATION_EXTRA_SECONDS_HELPTEXT" msgstr "" "Annettu ylimääräinen palautusaika. Jätä tyhjäksi mikäli ilmoitat määräajan " "alempana." @@ -1582,7 +1582,7 @@ msgstr "" "joilla on kyseiset merkinnät." #: deviations/forms.py -msgid "MINUTES_AND_DATE_MISSING" +msgid "SECONDS_AND_DATE_MISSING" msgstr "Syötä joko ylimääräinen palautusaika tai uusi määräaika." #: deviations/forms.py deviations/models.py @@ -1633,8 +1633,8 @@ msgid "MODEL_NAME_SUBMISSION_RULE_DEVIATION_PLURAL" msgstr "palautusääntöjen poikkeamat" #: deviations/models.py -msgid "LABEL_EXTRA_MINUTES" -msgstr "ylimääräiset minuutit" +msgid "LABEL_EXTRA_SECONDS" +msgstr "ylimääräiset sekunnit" #: deviations/models.py msgid "MODEL_NAME_DEADLINE_RULE_DEVIATION" @@ -1738,7 +1738,7 @@ msgstr "Tehtävä" #: deviations/templates/deviations/list_dl.html #: deviations/templates/deviations/override_dl.html -msgid "EXTRA_MINUTES" +msgid "EXTRA_SECONDS" msgstr "Ylimääräiset minuutit" #: deviations/templates/deviations/list_dl.html @@ -5302,6 +5302,10 @@ msgstr "tunnit" msgid "DURATION_UNIT_MINUTES" msgstr "minuutit" +#: lib/fields.py +msgid "DURATION_UNIT_SECONDS" +msgstr "sekunnit" + #: lib/fields.py msgid "CLIPBOARD_STUDENT_IDS" msgstr "Opiskelijanumerot" @@ -5474,6 +5478,18 @@ msgstr "" msgid "SHIBBOLETH_ERROR_USER_ACCOUNT_DISABLED" msgstr "Käyttäjätili on poistettu käytöstä." +#: site_alert/models.py +msgid "LABEL_ALERT" +msgstr "varoitus" + +#: site_alert/models.py +msgid "MODEL_NAME_SITEALERT" +msgstr "sivustovaroitus" + +#: site_alert/models.py +msgid "MODEL_NAME_SITEALERT_PLURAL" +msgstr "sivustovaroitukset" + #: templates/403.html msgid "ACCESS_RESTRICTED_403" msgstr "Pääsy kielletty" diff --git a/site_alert/__init__.py b/site_alert/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/site_alert/admin.py b/site_alert/admin.py new file mode 100644 index 000000000..81d080ece --- /dev/null +++ b/site_alert/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +from django.utils.translation import gettext_lazy as _ + +from site_alert.models import SiteAlert + + +class SiteAlertAdmin(admin.ModelAdmin): + search_fields = ( + 'alert', + 'status', + ) + list_display = ( + 'id', + 'alert', + 'status', + ) + + +admin.site.register(SiteAlert, SiteAlertAdmin) diff --git a/site_alert/apps.py b/site_alert/apps.py new file mode 100644 index 000000000..533a1308d --- /dev/null +++ b/site_alert/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SiteAlertConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'site_alert' diff --git a/site_alert/migrations/0001_initial.py b/site_alert/migrations/0001_initial.py new file mode 100644 index 000000000..741054863 --- /dev/null +++ b/site_alert/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.10 on 2024-03-08 07:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='SiteAlert', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('alert', models.JSONField(verbose_name='LABEL_ALERT')), + ('status', models.IntegerField(choices=[(1, 'ACTIVE'), (2, 'REMOVED')], default=1, verbose_name='LABEL_STATUS')), + ], + options={ + 'verbose_name': 'MODEL_NAME_SITEALERT', + 'verbose_name_plural': 'MODEL_NAME_SITEALERT_PLURAL', + }, + ), + ] diff --git a/site_alert/migrations/__init__.py b/site_alert/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/site_alert/models.py b/site_alert/models.py new file mode 100644 index 000000000..29f24b7ee --- /dev/null +++ b/site_alert/models.py @@ -0,0 +1,28 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from lib.helpers import Enum + + +class SiteAlert(models.Model): + """ + SiteAlert models represents sitewide alerts. There can be a single or + multiple alerts active simultaneously. + """ + STATUS = Enum([ + ('ACTIVE', 1, _('ACTIVE')), + ('REMOVED', 2, _('REMOVED')), + ]) + + alert = models.JSONField( + verbose_name=_('LABEL_ALERT') + ) + status = models.IntegerField( + verbose_name=_('LABEL_STATUS'), + choices=STATUS.choices, + default=STATUS.ACTIVE, + ) + + class Meta: + verbose_name = _('MODEL_NAME_SITEALERT') + verbose_name_plural = _('MODEL_NAME_SITEALERT_PLURAL') \ No newline at end of file