|
2 | 2 | import logging |
3 | 3 | import re |
4 | 4 | import zipfile |
| 5 | +from datetime import datetime |
5 | 6 | from os.path import basename, isfile, join |
6 | 7 |
|
7 | 8 | import requests |
|
14 | 15 | Leaderboard, |
15 | 16 | ) |
16 | 17 | from django.core.files.base import ContentFile |
| 18 | +from django.utils import dateparse, timezone |
17 | 19 | from rest_framework import status |
18 | 20 | from yaml.scanner import ScannerError |
19 | 21 |
|
|
58 | 60 | "is_partial_submission_evaluation_enabled", |
59 | 61 | ) |
60 | 62 |
|
| 63 | +_CHALLENGE_PHASE_DATE_LOCK_FIELDS = frozenset({"start_date", "end_date"}) |
| 64 | + |
| 65 | + |
| 66 | +def _parse_lock_datetime(value): |
| 67 | + if value is None: |
| 68 | + return None |
| 69 | + if isinstance(value, datetime): |
| 70 | + dt = value |
| 71 | + elif isinstance(value, str): |
| 72 | + stripped = value.strip() |
| 73 | + dt = dateparse.parse_datetime(stripped) |
| 74 | + if dt is None: |
| 75 | + dt = dateparse.parse_datetime(stripped.replace(" ", "T", 1)) |
| 76 | + else: |
| 77 | + return value |
| 78 | + if timezone.is_naive(dt): |
| 79 | + dt = timezone.make_aware(dt, timezone.utc) |
| 80 | + return dt.astimezone(timezone.utc).replace(microsecond=0) |
| 81 | + |
| 82 | + |
| 83 | +def _normalize_lock_field_value(field, value): |
| 84 | + if field in _CHALLENGE_PHASE_DATE_LOCK_FIELDS: |
| 85 | + return _parse_lock_datetime(value) |
| 86 | + if field == "description" and isinstance(value, str): |
| 87 | + return value.rstrip() |
| 88 | + return value |
| 89 | + |
61 | 90 |
|
62 | 91 | def write_file(output_path, mode, file_content): |
63 | 92 | with open(output_path, mode) as file: |
@@ -462,6 +491,13 @@ def _approved_config_locked(self): |
462 | 491 | def _stable_json(value): |
463 | 492 | return json.dumps(value, sort_keys=True, default=str) |
464 | 493 |
|
| 494 | + def _lock_field_values_equal(self, field, db_value, yaml_value): |
| 495 | + norm_db = _normalize_lock_field_value(field, db_value) |
| 496 | + norm_yaml = _normalize_lock_field_value(field, yaml_value) |
| 497 | + if isinstance(norm_db, datetime) and isinstance(norm_yaml, datetime): |
| 498 | + return norm_db == norm_yaml |
| 499 | + return self._stable_json(norm_db) == self._stable_json(norm_yaml) |
| 500 | + |
465 | 501 | def _locked_leaderboard_modified_message(self, data): |
466 | 502 | lb = Leaderboard.objects.filter( |
467 | 503 | config_id=int(data["id"]), |
@@ -505,8 +541,8 @@ def _locked_challenge_phase_modified_message(self, data): |
505 | 541 | for field in _CHALLENGE_PHASE_APPROVED_LOCK_FIELDS: |
506 | 542 | if field not in data: |
507 | 543 | continue |
508 | | - if self._stable_json(getattr(phase, field)) != self._stable_json( |
509 | | - data.get(field) |
| 544 | + if not self._lock_field_values_equal( |
| 545 | + field, getattr(phase, field), data.get(field) |
510 | 546 | ): |
511 | 547 | return self.error_messages_dict[ |
512 | 548 | "locked_challenge_phase_modified" |
|
0 commit comments