From dfa1b434edfedc27ce2f86d23157da866cce28d8 Mon Sep 17 00:00:00 2001 From: Ester Beltrami Date: Wed, 7 Jan 2026 20:33:46 +0100 Subject: [PATCH 1/3] Forbid update of title and abstract when CFP is closed --- backend/api/submissions/mutations.py | 22 ++++ .../submissions/tests/test_edit_submission.py | 121 ++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/backend/api/submissions/mutations.py b/backend/api/submissions/mutations.py index 02c9c14f12..a6b3fa7ec2 100644 --- a/backend/api/submissions/mutations.py +++ b/backend/api/submissions/mutations.py @@ -263,6 +263,28 @@ class UpdateSubmissionInput(BaseSubmissionInput): def validate(self, conference: Conference, submission: SubmissionModel): errors = super().validate(conference) + # Check if CFP is closed and prevent editing of restricted fields + # Exception: accepted submissions can still be edited + if ( + not conference.is_cfp_open + and submission.status != SubmissionModel.STATUS.accepted + ): + restricted_fields = ( + "title", + "abstract", + "elevator_pitch", + ) + + for field_name in restricted_fields: + input_value = getattr(self, field_name) + submission_value = getattr(submission, field_name) + if LazyI18nString(input_value.to_dict()) != submission_value: + field_label = field_name.replace("_", " ") + errors.add_error( + field_name, + f"You cannot edit the {field_label} after the call for proposals deadline has passed.", + ) + if self.materials: if len(self.materials) > 3: errors.add_error( diff --git a/backend/api/submissions/tests/test_edit_submission.py b/backend/api/submissions/tests/test_edit_submission.py index 65c20a06f8..89a336102b 100644 --- a/backend/api/submissions/tests/test_edit_submission.py +++ b/backend/api/submissions/tests/test_edit_submission.py @@ -122,6 +122,7 @@ def _update_submission( validationNotes: notes validationTopic: topic validationAbstract: abstract + validationElevatorPitch: elevatorPitch validationDuration: duration validationAudienceLevel: audienceLevel validationType: type @@ -1070,6 +1071,9 @@ def test_can_edit_submission_outside_cfp(graphql_client, user): new_duration=new_duration, new_type=new_type, new_languages=["en"], + new_title=submission.title.data, # Keep title unchanged + new_abstract=submission.abstract.data, # Keep abstract unchanged + new_elevator_pitch=submission.elevator_pitch.data, # Keep elevator pitch unchanged ) assert response["data"]["updateSubmission"]["__typename"] == "Submission" @@ -1301,3 +1305,120 @@ def test_update_submission_with_do_not_record_true(graphql_client, user): submission.refresh_from_db() assert submission.do_not_record is True + + +def test_accepted_submission_can_edit_restricted_fields_after_cfp_deadline( + graphql_client, user +): + """Accepted submissions can edit title, abstract, and elevator_pitch when CFP is closed.""" + conference = ConferenceFactory( + topics=("life", "diy"), + languages=("en",), + durations=("10", "20"), + active_cfp=False, # CFP deadline is in the past + audience_levels=("adult", "senior"), + submission_types=("talk", "workshop"), + ) + + submission = SubmissionFactory( + speaker_id=user.id, + status=Submission.STATUS.accepted, + custom_topic="life", + custom_duration="10m", + custom_audience_level="adult", + custom_submission_type="talk", + languages=["en"], + tags=["python", "ml"], + conference=conference, + ) + + graphql_client.force_login(user) + + response = _update_submission( + graphql_client, + submission=submission, + new_title={"en": "Updated Title After Deadline - Accepted"}, + new_abstract={"en": "Updated abstract after deadline - Accepted"}, + new_elevator_pitch={"en": "Updated elevator pitch after deadline - Accepted"}, + ) + + assert response["data"]["updateSubmission"]["__typename"] == "Submission" + assert ( + response["data"]["updateSubmission"]["title"] + == "Updated Title After Deadline - Accepted" + ) + assert ( + response["data"]["updateSubmission"]["abstract"] + == "Updated abstract after deadline - Accepted" + ) + assert ( + response["data"]["updateSubmission"]["elevatorPitch"] + == "Updated elevator pitch after deadline - Accepted" + ) + submission.refresh_from_db() + assert submission.title.localize("en") == "Updated Title After Deadline - Accepted" + assert ( + submission.abstract.localize("en") + == "Updated abstract after deadline - Accepted" + ) + assert ( + submission.elevator_pitch.localize("en") + == "Updated elevator pitch after deadline - Accepted" + ) + + +def test_non_accepted_submission_cannot_edit_restricted_fields_after_cfp_deadline( + graphql_client, user +): + """Non-accepted submissions cannot edit title, abstract, or elevator_pitch when CFP is closed.""" + conference = ConferenceFactory( + topics=("life", "diy"), + languages=("en",), + durations=("10", "20"), + active_cfp=False, # CFP deadline is in the past + audience_levels=("adult", "senior"), + submission_types=("talk", "workshop"), + ) + + submission = SubmissionFactory( + speaker_id=user.id, + status=Submission.STATUS.proposed, + custom_topic="life", + custom_duration="10m", + custom_audience_level="adult", + custom_submission_type="talk", + languages=["en"], + tags=["python", "ml"], + conference=conference, + ) + + original_title = submission.title.localize("en") + original_abstract = submission.abstract.localize("en") + original_elevator_pitch = submission.elevator_pitch.localize("en") + + graphql_client.force_login(user) + + response = _update_submission( + graphql_client, + submission=submission, + new_title={"en": "Updated Title After Deadline"}, + new_abstract={"en": "Updated abstract after deadline"}, + new_elevator_pitch={"en": "Updated elevator pitch after deadline"}, + ) + + assert response["data"]["updateSubmission"]["__typename"] == "SendSubmissionErrors" + assert response["data"]["updateSubmission"]["errors"]["validationTitle"] == [ + "You cannot edit the title after the call for proposals deadline has passed." + ] + assert response["data"]["updateSubmission"]["errors"]["validationAbstract"] == [ + "You cannot edit the abstract after the call for proposals deadline has passed." + ] + assert response["data"]["updateSubmission"]["errors"][ + "validationElevatorPitch" + ] == [ + "You cannot edit the elevator pitch after the call for proposals deadline has passed." + ] + submission.refresh_from_db() + assert submission.title.localize("en") == original_title + assert submission.abstract.localize("en") == original_abstract + assert submission.elevator_pitch.localize("en") == original_elevator_pitch From 32809ae655717b012c7e330c1e8e380135cc6242 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 06:27:49 +0000 Subject: [PATCH 2/3] Add test for editing non-restricted fields after CFP deadline This test verifies that non-accepted submissions can still edit non-restricted fields (like tags, short_social_summary, do_not_record) after the CFP deadline has passed, as long as the restricted fields (title, abstract, elevator_pitch) remain unchanged. Co-authored-by: Marco Acierno --- .../submissions/tests/test_edit_submission.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/backend/api/submissions/tests/test_edit_submission.py b/backend/api/submissions/tests/test_edit_submission.py index 89a336102b..ad56df2ea5 100644 --- a/backend/api/submissions/tests/test_edit_submission.py +++ b/backend/api/submissions/tests/test_edit_submission.py @@ -1422,3 +1422,65 @@ def test_non_accepted_submission_cannot_edit_restricted_fields_after_cfp_deadlin assert submission.title.localize("en") == original_title assert submission.abstract.localize("en") == original_abstract assert submission.elevator_pitch.localize("en") == original_elevator_pitch + + +def test_non_accepted_submission_can_edit_non_restricted_fields_after_cfp_deadline( + graphql_client, user +): + """Non-accepted submissions can edit non-restricted fields when CFP is closed.""" + conference = ConferenceFactory( + topics=("life", "diy"), + languages=("en",), + durations=("10", "20"), + active_cfp=False, # CFP deadline is in the past + audience_levels=("adult", "senior"), + submission_types=("talk", "workshop"), + ) + + submission = SubmissionFactory( + speaker_id=user.id, + status=Submission.STATUS.proposed, + custom_topic="life", + custom_duration="10m", + custom_audience_level="adult", + custom_submission_type="talk", + languages=["en"], + tags=["python", "ml"], + conference=conference, + ) + + original_title = submission.title.localize("en") + original_abstract = submission.abstract.localize("en") + original_elevator_pitch = submission.elevator_pitch.localize("en") + + graphql_client.force_login(user) + + # Update non-restricted fields (tags, short_social_summary, do_not_record) + # while keeping restricted fields unchanged + response = _update_submission( + graphql_client, + submission=submission, + new_title={"en": original_title}, # Keep original title + new_abstract={"en": original_abstract}, # Keep original abstract + new_elevator_pitch={"en": original_elevator_pitch}, # Keep original elevator pitch + new_tag=submission.tags.last(), # Change tag + new_short_social_summary="Updated social summary", # Change social summary + new_do_not_record=True, # Change do_not_record flag + ) + + # Should succeed since we're only updating non-restricted fields + assert response["data"]["updateSubmission"]["__typename"] == "Submission" + assert response["data"]["updateSubmission"]["shortSocialSummary"] == "Updated social summary" + assert response["data"]["updateSubmission"]["doNotRecord"] is True + + # Verify restricted fields remain unchanged + assert response["data"]["updateSubmission"]["title"] == original_title + assert response["data"]["updateSubmission"]["abstract"] == original_abstract + assert response["data"]["updateSubmission"]["elevatorPitch"] == original_elevator_pitch + + submission.refresh_from_db() + assert submission.short_social_summary == "Updated social summary" + assert submission.do_not_record is True + assert submission.title.localize("en") == original_title + assert submission.abstract.localize("en") == original_abstract + assert submission.elevator_pitch.localize("en") == original_elevator_pitch From 25f1a32f6b117a52056f65c605520c74bc6a59c4 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Tue, 13 Jan 2026 12:51:48 +0100 Subject: [PATCH 3/3] Fix test --- .../submissions/tests/test_edit_submission.py | 17 ++++++++++++++--- backend/api/types.py | 7 ++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/api/submissions/tests/test_edit_submission.py b/backend/api/submissions/tests/test_edit_submission.py index ad56df2ea5..e54f4e26be 100644 --- a/backend/api/submissions/tests/test_edit_submission.py +++ b/backend/api/submissions/tests/test_edit_submission.py @@ -1,3 +1,4 @@ +from i18n.strings import LazyI18nString import pytest from uuid import uuid4 from users.tests.factories import UserFactory @@ -1444,6 +1445,9 @@ def test_non_accepted_submission_can_edit_non_restricted_fields_after_cfp_deadli custom_duration="10m", custom_audience_level="adult", custom_submission_type="talk", + title=LazyI18nString({"en": "Original Title"}), + abstract=LazyI18nString({"en": "Original Abstract"}), + elevator_pitch=LazyI18nString({"en": "Original Elevator Pitch"}), languages=["en"], tags=["python", "ml"], conference=conference, @@ -1462,7 +1466,9 @@ def test_non_accepted_submission_can_edit_non_restricted_fields_after_cfp_deadli submission=submission, new_title={"en": original_title}, # Keep original title new_abstract={"en": original_abstract}, # Keep original abstract - new_elevator_pitch={"en": original_elevator_pitch}, # Keep original elevator pitch + new_elevator_pitch={ + "en": original_elevator_pitch + }, # Keep original elevator pitch new_tag=submission.tags.last(), # Change tag new_short_social_summary="Updated social summary", # Change social summary new_do_not_record=True, # Change do_not_record flag @@ -1470,13 +1476,18 @@ def test_non_accepted_submission_can_edit_non_restricted_fields_after_cfp_deadli # Should succeed since we're only updating non-restricted fields assert response["data"]["updateSubmission"]["__typename"] == "Submission" - assert response["data"]["updateSubmission"]["shortSocialSummary"] == "Updated social summary" + assert ( + response["data"]["updateSubmission"]["shortSocialSummary"] + == "Updated social summary" + ) assert response["data"]["updateSubmission"]["doNotRecord"] is True # Verify restricted fields remain unchanged assert response["data"]["updateSubmission"]["title"] == original_title assert response["data"]["updateSubmission"]["abstract"] == original_abstract - assert response["data"]["updateSubmission"]["elevatorPitch"] == original_elevator_pitch + assert ( + response["data"]["updateSubmission"]["elevatorPitch"] == original_elevator_pitch + ) submission.refresh_from_db() assert submission.short_social_summary == "Updated social summary" diff --git a/backend/api/types.py b/backend/api/types.py index 469ab453ee..e6a8136e7f 100644 --- a/backend/api/types.py +++ b/backend/api/types.py @@ -111,7 +111,12 @@ def clean(self, languages: list[str]) -> "MultiLingualInput": return new_input def to_dict(self) -> dict: - return {"en": self.en, "it": self.it} + as_dict = {} + if self.en: + as_dict["en"] = self.en + if self.it: + as_dict["it"] = self.it + return as_dict ItemType = TypeVar("ItemType")