Skip to content

Commit 2e96a70

Browse files
Make proposal submission limit configurable per conference (#4524)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Marco Acierno <[email protected]>
1 parent d69e0a2 commit 2e96a70

File tree

5 files changed

+78
-10
lines changed

5 files changed

+78
-10
lines changed

backend/api/submissions/mutations.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -412,16 +412,18 @@ def send_submission(
412412
if not conference.is_cfp_open:
413413
errors.add_error("non_field_errors", "The call for paper is not open!")
414414

415-
if (
416-
SubmissionModel.objects.of_user(request.user)
417-
.for_conference(conference)
418-
.non_cancelled()
419-
.count()
420-
>= 3
421-
):
422-
errors.add_error(
423-
"non_field_errors", "You can only submit up to 3 proposals"
415+
if conference.max_proposals_per_user is not None:
416+
user_submissions_count = (
417+
SubmissionModel.objects.of_user(request.user)
418+
.for_conference(conference)
419+
.non_cancelled()
420+
.count()
424421
)
422+
if user_submissions_count >= conference.max_proposals_per_user:
423+
errors.add_error(
424+
"non_field_errors",
425+
f"You can only submit up to {conference.max_proposals_per_user} proposals",
426+
)
425427

426428
if errors.has_errors:
427429
return errors

backend/api/submissions/tests/test_send_submission.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1249,6 +1249,7 @@ def test_cannot_submit_more_than_3_proposals(graphql_client, user):
12491249
active_cfp=True,
12501250
durations=("50",),
12511251
audience_levels=("Beginner",),
1252+
max_proposals_per_user=3,
12521253
)
12531254

12541255
SubmissionFactory(
@@ -1267,14 +1268,53 @@ def test_cannot_submit_more_than_3_proposals(graphql_client, user):
12671268
status=Submission.STATUS.proposed,
12681269
)
12691270

1270-
resp, _ = _submit_talk(graphql_client, conference, title={"en": "My first talk"})
1271+
resp, _ = _submit_talk(
1272+
graphql_client, conference, title={"en": "My first talk"}, languages=["en"]
1273+
)
12711274

12721275
assert resp["data"]["sendSubmission"]["__typename"] == "SendSubmissionErrors"
12731276
assert resp["data"]["sendSubmission"]["errors"]["nonFieldErrors"] == [
12741277
"You can only submit up to 3 proposals"
12751278
]
12761279

12771280

1281+
def test_can_submit_unlimited_proposals_when_max_proposals_is_none(
1282+
graphql_client, user
1283+
):
1284+
graphql_client.force_login(user)
1285+
1286+
conference = ConferenceFactory(
1287+
topics=("my-topic",),
1288+
languages=("en", "it"),
1289+
submission_types=("talk",),
1290+
active_cfp=True,
1291+
durations=("50",),
1292+
audience_levels=("Beginner",),
1293+
# max_proposals_per_user defaults to None (no limit)
1294+
)
1295+
1296+
EmailTemplateFactory(
1297+
conference=conference,
1298+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
1299+
)
1300+
1301+
# Create 3 existing submissions
1302+
for _ in range(3):
1303+
SubmissionFactory(
1304+
speaker_id=user.id,
1305+
conference=conference,
1306+
status=Submission.STATUS.proposed,
1307+
)
1308+
1309+
# Should be able to submit a 4th proposal
1310+
resp, _ = _submit_talk(
1311+
graphql_client, conference, title={"en": "My fourth talk"}, languages=["en"]
1312+
)
1313+
1314+
assert resp["data"]["sendSubmission"]["__typename"] == "Submission"
1315+
assert resp["data"]["sendSubmission"]["title"] == "My fourth talk"
1316+
1317+
12781318
def test_submit_talk_with_do_not_record_true(graphql_client, user):
12791319
graphql_client.force_login(user)
12801320

backend/conferences/admin/conference.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class ConferenceAdmin(
181181
"audience_levels",
182182
"languages",
183183
"proposal_tags",
184+
"max_proposals_per_user",
184185
)
185186
},
186187
),
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.4 on 2026-01-07 01:11
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('conferences', '0055_remove_conference_grants_default_accommodation_amount_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='conference',
15+
name='max_proposals_per_user',
16+
field=models.PositiveIntegerField(blank=True, help_text='Maximum number of proposals a user can submit. Leave empty for no limit.', null=True, verbose_name='max proposals per user'),
17+
),
18+
]

backend/conferences/models/conference.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ class Conference(GeoLocalizedModel, TimeFramedModel, TimeStampedModel):
117117
max_length=32224,
118118
)
119119

120+
max_proposals_per_user = models.PositiveIntegerField(
121+
_("max proposals per user"),
122+
null=True,
123+
blank=True,
124+
help_text=_("Maximum number of proposals a user can submit. Leave empty for no limit."),
125+
)
126+
120127
def get_slack_oauth_token(self):
121128
return self.organizer.slack_oauth_bot_token
122129

0 commit comments

Comments
 (0)