Skip to content

Commit 285e5d5

Browse files
estyxxgithub-actions[bot]marcoacierno
authored
Forbid update of title and abstract when CFP is closed (#4525)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Marco Acierno <[email protected]> Co-authored-by: Marco Acierno <[email protected]>
1 parent c81b5f3 commit 285e5d5

File tree

3 files changed

+222
-1
lines changed

3 files changed

+222
-1
lines changed

backend/api/submissions/mutations.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,28 @@ class UpdateSubmissionInput(BaseSubmissionInput):
263263
def validate(self, conference: Conference, submission: SubmissionModel):
264264
errors = super().validate(conference)
265265

266+
# Check if CFP is closed and prevent editing of restricted fields
267+
# Exception: accepted submissions can still be edited
268+
if (
269+
not conference.is_cfp_open
270+
and submission.status != SubmissionModel.STATUS.accepted
271+
):
272+
restricted_fields = (
273+
"title",
274+
"abstract",
275+
"elevator_pitch",
276+
)
277+
278+
for field_name in restricted_fields:
279+
input_value = getattr(self, field_name)
280+
submission_value = getattr(submission, field_name)
281+
if LazyI18nString(input_value.to_dict()) != submission_value:
282+
field_label = field_name.replace("_", " ")
283+
errors.add_error(
284+
field_name,
285+
f"You cannot edit the {field_label} after the call for proposals deadline has passed.",
286+
)
287+
266288
if self.materials:
267289
if len(self.materials) > 3:
268290
errors.add_error(

backend/api/submissions/tests/test_edit_submission.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from i18n.strings import LazyI18nString
12
import pytest
23
from uuid import uuid4
34
from users.tests.factories import UserFactory
@@ -122,6 +123,7 @@ def _update_submission(
122123
validationNotes: notes
123124
validationTopic: topic
124125
validationAbstract: abstract
126+
validationElevatorPitch: elevatorPitch
125127
validationDuration: duration
126128
validationAudienceLevel: audienceLevel
127129
validationType: type
@@ -1070,6 +1072,9 @@ def test_can_edit_submission_outside_cfp(graphql_client, user):
10701072
new_duration=new_duration,
10711073
new_type=new_type,
10721074
new_languages=["en"],
1075+
new_title=submission.title.data, # Keep title unchanged
1076+
new_abstract=submission.abstract.data, # Keep abstract unchanged
1077+
new_elevator_pitch=submission.elevator_pitch.data, # Keep elevator pitch unchanged
10731078
)
10741079

10751080
assert response["data"]["updateSubmission"]["__typename"] == "Submission"
@@ -1301,3 +1306,192 @@ def test_update_submission_with_do_not_record_true(graphql_client, user):
13011306

13021307
submission.refresh_from_db()
13031308
assert submission.do_not_record is True
1309+
1310+
1311+
def test_accepted_submission_can_edit_restricted_fields_after_cfp_deadline(
1312+
graphql_client, user
1313+
):
1314+
"""Accepted submissions can edit title, abstract, and elevator_pitch when CFP is closed."""
1315+
conference = ConferenceFactory(
1316+
topics=("life", "diy"),
1317+
languages=("en",),
1318+
durations=("10", "20"),
1319+
active_cfp=False, # CFP deadline is in the past
1320+
audience_levels=("adult", "senior"),
1321+
submission_types=("talk", "workshop"),
1322+
)
1323+
1324+
submission = SubmissionFactory(
1325+
speaker_id=user.id,
1326+
status=Submission.STATUS.accepted,
1327+
custom_topic="life",
1328+
custom_duration="10m",
1329+
custom_audience_level="adult",
1330+
custom_submission_type="talk",
1331+
languages=["en"],
1332+
tags=["python", "ml"],
1333+
conference=conference,
1334+
)
1335+
1336+
graphql_client.force_login(user)
1337+
1338+
response = _update_submission(
1339+
graphql_client,
1340+
submission=submission,
1341+
new_title={"en": "Updated Title After Deadline - Accepted"},
1342+
new_abstract={"en": "Updated abstract after deadline - Accepted"},
1343+
new_elevator_pitch={"en": "Updated elevator pitch after deadline - Accepted"},
1344+
)
1345+
1346+
assert response["data"]["updateSubmission"]["__typename"] == "Submission"
1347+
assert (
1348+
response["data"]["updateSubmission"]["title"]
1349+
== "Updated Title After Deadline - Accepted"
1350+
)
1351+
assert (
1352+
response["data"]["updateSubmission"]["abstract"]
1353+
== "Updated abstract after deadline - Accepted"
1354+
)
1355+
assert (
1356+
response["data"]["updateSubmission"]["elevatorPitch"]
1357+
== "Updated elevator pitch after deadline - Accepted"
1358+
)
1359+
submission.refresh_from_db()
1360+
assert submission.title.localize("en") == "Updated Title After Deadline - Accepted"
1361+
assert (
1362+
submission.abstract.localize("en")
1363+
== "Updated abstract after deadline - Accepted"
1364+
)
1365+
assert (
1366+
submission.elevator_pitch.localize("en")
1367+
== "Updated elevator pitch after deadline - Accepted"
1368+
)
1369+
1370+
1371+
def test_non_accepted_submission_cannot_edit_restricted_fields_after_cfp_deadline(
1372+
graphql_client, user
1373+
):
1374+
"""Non-accepted submissions cannot edit title, abstract, or elevator_pitch when CFP is closed."""
1375+
conference = ConferenceFactory(
1376+
topics=("life", "diy"),
1377+
languages=("en",),
1378+
durations=("10", "20"),
1379+
active_cfp=False, # CFP deadline is in the past
1380+
audience_levels=("adult", "senior"),
1381+
submission_types=("talk", "workshop"),
1382+
)
1383+
1384+
submission = SubmissionFactory(
1385+
speaker_id=user.id,
1386+
status=Submission.STATUS.proposed,
1387+
custom_topic="life",
1388+
custom_duration="10m",
1389+
custom_audience_level="adult",
1390+
custom_submission_type="talk",
1391+
languages=["en"],
1392+
tags=["python", "ml"],
1393+
conference=conference,
1394+
)
1395+
1396+
original_title = submission.title.localize("en")
1397+
original_abstract = submission.abstract.localize("en")
1398+
original_elevator_pitch = submission.elevator_pitch.localize("en")
1399+
1400+
graphql_client.force_login(user)
1401+
1402+
response = _update_submission(
1403+
graphql_client,
1404+
submission=submission,
1405+
new_title={"en": "Updated Title After Deadline"},
1406+
new_abstract={"en": "Updated abstract after deadline"},
1407+
new_elevator_pitch={"en": "Updated elevator pitch after deadline"},
1408+
)
1409+
1410+
assert response["data"]["updateSubmission"]["__typename"] == "SendSubmissionErrors"
1411+
assert response["data"]["updateSubmission"]["errors"]["validationTitle"] == [
1412+
"You cannot edit the title after the call for proposals deadline has passed."
1413+
]
1414+
assert response["data"]["updateSubmission"]["errors"]["validationAbstract"] == [
1415+
"You cannot edit the abstract after the call for proposals deadline has passed."
1416+
]
1417+
assert response["data"]["updateSubmission"]["errors"][
1418+
"validationElevatorPitch"
1419+
] == [
1420+
"You cannot edit the elevator pitch after the call for proposals deadline has passed."
1421+
]
1422+
submission.refresh_from_db()
1423+
assert submission.title.localize("en") == original_title
1424+
assert submission.abstract.localize("en") == original_abstract
1425+
assert submission.elevator_pitch.localize("en") == original_elevator_pitch
1426+
1427+
1428+
def test_non_accepted_submission_can_edit_non_restricted_fields_after_cfp_deadline(
1429+
graphql_client, user
1430+
):
1431+
"""Non-accepted submissions can edit non-restricted fields when CFP is closed."""
1432+
conference = ConferenceFactory(
1433+
topics=("life", "diy"),
1434+
languages=("en",),
1435+
durations=("10", "20"),
1436+
active_cfp=False, # CFP deadline is in the past
1437+
audience_levels=("adult", "senior"),
1438+
submission_types=("talk", "workshop"),
1439+
)
1440+
1441+
submission = SubmissionFactory(
1442+
speaker_id=user.id,
1443+
status=Submission.STATUS.proposed,
1444+
custom_topic="life",
1445+
custom_duration="10m",
1446+
custom_audience_level="adult",
1447+
custom_submission_type="talk",
1448+
title=LazyI18nString({"en": "Original Title"}),
1449+
abstract=LazyI18nString({"en": "Original Abstract"}),
1450+
elevator_pitch=LazyI18nString({"en": "Original Elevator Pitch"}),
1451+
languages=["en"],
1452+
tags=["python", "ml"],
1453+
conference=conference,
1454+
)
1455+
1456+
original_title = submission.title.localize("en")
1457+
original_abstract = submission.abstract.localize("en")
1458+
original_elevator_pitch = submission.elevator_pitch.localize("en")
1459+
1460+
graphql_client.force_login(user)
1461+
1462+
# Update non-restricted fields (tags, short_social_summary, do_not_record)
1463+
# while keeping restricted fields unchanged
1464+
response = _update_submission(
1465+
graphql_client,
1466+
submission=submission,
1467+
new_title={"en": original_title}, # Keep original title
1468+
new_abstract={"en": original_abstract}, # Keep original abstract
1469+
new_elevator_pitch={
1470+
"en": original_elevator_pitch
1471+
}, # Keep original elevator pitch
1472+
new_tag=submission.tags.last(), # Change tag
1473+
new_short_social_summary="Updated social summary", # Change social summary
1474+
new_do_not_record=True, # Change do_not_record flag
1475+
)
1476+
1477+
# Should succeed since we're only updating non-restricted fields
1478+
assert response["data"]["updateSubmission"]["__typename"] == "Submission"
1479+
assert (
1480+
response["data"]["updateSubmission"]["shortSocialSummary"]
1481+
== "Updated social summary"
1482+
)
1483+
assert response["data"]["updateSubmission"]["doNotRecord"] is True
1484+
1485+
# Verify restricted fields remain unchanged
1486+
assert response["data"]["updateSubmission"]["title"] == original_title
1487+
assert response["data"]["updateSubmission"]["abstract"] == original_abstract
1488+
assert (
1489+
response["data"]["updateSubmission"]["elevatorPitch"] == original_elevator_pitch
1490+
)
1491+
1492+
submission.refresh_from_db()
1493+
assert submission.short_social_summary == "Updated social summary"
1494+
assert submission.do_not_record is True
1495+
assert submission.title.localize("en") == original_title
1496+
assert submission.abstract.localize("en") == original_abstract
1497+
assert submission.elevator_pitch.localize("en") == original_elevator_pitch

backend/api/types.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ def clean(self, languages: list[str]) -> "MultiLingualInput":
111111
return new_input
112112

113113
def to_dict(self) -> dict:
114-
return {"en": self.en, "it": self.it}
114+
as_dict = {}
115+
if self.en:
116+
as_dict["en"] = self.en
117+
if self.it:
118+
as_dict["it"] = self.it
119+
return as_dict
115120

116121

117122
ItemType = TypeVar("ItemType")

0 commit comments

Comments
 (0)