Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions backend/api/submissions/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,18 @@ def send_submission(
if not conference.is_cfp_open:
errors.add_error("non_field_errors", "The call for paper is not open!")

if (
SubmissionModel.objects.of_user(request.user)
.for_conference(conference)
.non_cancelled()
.count()
>= 3
):
errors.add_error(
"non_field_errors", "You can only submit up to 3 proposals"
if conference.max_proposals_per_user is not None:
user_submissions_count = (
SubmissionModel.objects.of_user(request.user)
.for_conference(conference)
.non_cancelled()
.count()
)
if user_submissions_count >= conference.max_proposals_per_user:
errors.add_error(
"non_field_errors",
f"You can only submit up to {conference.max_proposals_per_user} proposals",
)

if errors.has_errors:
return errors
Expand Down
42 changes: 41 additions & 1 deletion backend/api/submissions/tests/test_send_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,7 @@ def test_cannot_submit_more_than_3_proposals(graphql_client, user):
active_cfp=True,
durations=("50",),
audience_levels=("Beginner",),
max_proposals_per_user=3,
)

SubmissionFactory(
Expand All @@ -1267,14 +1268,53 @@ def test_cannot_submit_more_than_3_proposals(graphql_client, user):
status=Submission.STATUS.proposed,
)

resp, _ = _submit_talk(graphql_client, conference, title={"en": "My first talk"})
resp, _ = _submit_talk(
graphql_client, conference, title={"en": "My first talk"}, languages=["en"]
)

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


def test_can_submit_unlimited_proposals_when_max_proposals_is_none(
graphql_client, user
):
graphql_client.force_login(user)

conference = ConferenceFactory(
topics=("my-topic",),
languages=("en", "it"),
submission_types=("talk",),
active_cfp=True,
durations=("50",),
audience_levels=("Beginner",),
# max_proposals_per_user defaults to None (no limit)
)

EmailTemplateFactory(
conference=conference,
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
)

# Create 3 existing submissions
for _ in range(3):
SubmissionFactory(
speaker_id=user.id,
conference=conference,
status=Submission.STATUS.proposed,
)

# Should be able to submit a 4th proposal
resp, _ = _submit_talk(
graphql_client, conference, title={"en": "My fourth talk"}, languages=["en"]
)

assert resp["data"]["sendSubmission"]["__typename"] == "Submission"
assert resp["data"]["sendSubmission"]["title"] == "My fourth talk"


def test_submit_talk_with_do_not_record_true(graphql_client, user):
graphql_client.force_login(user)

Expand Down
1 change: 1 addition & 0 deletions backend/conferences/admin/conference.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ class ConferenceAdmin(
"audience_levels",
"languages",
"proposal_tags",
"max_proposals_per_user",
)
},
),
Expand Down
18 changes: 18 additions & 0 deletions backend/conferences/migrations/0056_conference_max_proposals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2026-01-07 01:11

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('conferences', '0055_remove_conference_grants_default_accommodation_amount_and_more'),
]

operations = [
migrations.AddField(
model_name='conference',
name='max_proposals_per_user',
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'),
),
]
7 changes: 7 additions & 0 deletions backend/conferences/models/conference.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ class Conference(GeoLocalizedModel, TimeFramedModel, TimeStampedModel):
max_length=32224,
)

max_proposals_per_user = models.PositiveIntegerField(
_("max proposals per user"),
null=True,
blank=True,
help_text=_("Maximum number of proposals a user can submit. Leave empty for no limit."),
)

def get_slack_oauth_token(self):
return self.organizer.slack_oauth_bot_token

Expand Down
Loading