diff --git a/backend/api/submissions/mutations.py b/backend/api/submissions/mutations.py index 0f1c9802be..02c9c14f12 100644 --- a/backend/api/submissions/mutations.py +++ b/backend/api/submissions/mutations.py @@ -227,6 +227,7 @@ class SendSubmissionInput(BaseSubmissionInput): topic: Optional[ID] = strawberry.field(default=None) tags: list[ID] = strawberry.field(default_factory=list) + do_not_record: bool = strawberry.field(default=False) @strawberry.input @@ -257,6 +258,7 @@ class UpdateSubmissionInput(BaseSubmissionInput): topic: Optional[ID] = strawberry.field(default=None) tags: list[ID] = strawberry.field(default_factory=list) materials: list[SubmissionMaterialInput] = strawberry.field(default_factory=list) + do_not_record: bool = strawberry.field(default=False) def validate(self, conference: Conference, submission: SubmissionModel): errors = super().validate(conference) @@ -319,6 +321,7 @@ def update_submission( instance.speaker_level = input.speaker_level instance.previous_talk_video = input.previous_talk_video instance.short_social_summary = input.short_social_summary + instance.do_not_record = input.do_not_record languages = Language.objects.filter(code__in=input.languages).all() instance.languages.set(languages) @@ -437,6 +440,7 @@ def send_submission( notes=input.notes, audience_level_id=input.audience_level, short_social_summary=input.short_social_summary, + do_not_record=input.do_not_record, ) languages = Language.objects.filter(code__in=input.languages).all() diff --git a/backend/api/submissions/tests/test_edit_submission.py b/backend/api/submissions/tests/test_edit_submission.py index 7a671420fb..65c20a06f8 100644 --- a/backend/api/submissions/tests/test_edit_submission.py +++ b/backend/api/submissions/tests/test_edit_submission.py @@ -46,6 +46,7 @@ def _update_submission( new_speaker_mastodon_handle="", new_speaker_availabilities=None, new_materials=None, + new_do_not_record=None, ): new_topic = new_topic or submission.topic new_audience = new_audience or submission.audience_level @@ -59,6 +60,7 @@ def _update_submission( new_speaker_photo = new_speaker_photo or FileFactory().id new_speaker_availabilities = new_speaker_availabilities or {} new_materials = new_materials or [] + new_do_not_record = new_do_not_record or submission.do_not_record return graphql_client.query( """ @@ -110,6 +112,7 @@ def _update_submission( speakerLevel previousTalkVideo + doNotRecord } ... on SendSubmissionErrors { @@ -168,6 +171,7 @@ def _update_submission( "speakerMastodonHandle": new_speaker_mastodon_handle, "speakerAvailabilities": new_speaker_availabilities, "materials": new_materials, + "doNotRecord": new_do_not_record, } }, ) @@ -1256,3 +1260,44 @@ def test_edit_submission_multi_lingual_fields_required(graphql_client, user): ] assert submission.languages.count() == 1 + + +def test_update_submission_with_do_not_record_true(graphql_client, user): + graphql_client.force_login(user) + + conference = ConferenceFactory( + topics=("life", "diy"), + languages=("it", "en"), + durations=("10", "20"), + active_cfp=True, + audience_levels=("adult", "senior"), + submission_types=("talk", "workshop"), + ) + + submission = SubmissionFactory( + speaker_id=user.id, + custom_topic="life", + custom_duration="10m", + custom_audience_level="adult", + custom_submission_type="talk", + languages=["it"], + tags=["python", "ml"], + conference=conference, + speaker_level=Submission.SPEAKER_LEVELS.intermediate, + previous_talk_video="https://www.youtube.com/watch?v=SlPhMPnQ58k", + do_not_record=False, + ) + + graphql_client.force_login(user) + + response = _update_submission( + graphql_client, + submission=submission, + new_do_not_record=True, + ) + + assert response["data"]["updateSubmission"]["__typename"] == "Submission" + assert response["data"]["updateSubmission"]["doNotRecord"] is True + + submission.refresh_from_db() + assert submission.do_not_record is True diff --git a/backend/api/submissions/tests/test_send_submission.py b/backend/api/submissions/tests/test_send_submission.py index 0b37060f5b..0a8156cb29 100644 --- a/backend/api/submissions/tests/test_send_submission.py +++ b/backend/api/submissions/tests/test_send_submission.py @@ -104,6 +104,7 @@ def _submit_proposal(client, conference, submission, **kwargs): tags { name } + doNotRecord } ... on SendSubmissionErrors { @@ -207,6 +208,7 @@ def test_submit_talk( assert talk.speaker_id == user.id assert talk.audience_level.name == "Beginner" assert talk.short_social_summary == "summary" + assert talk.do_not_record is False participant = Participant.objects.get(conference=conference, user_id=user.id) assert participant.bio == "my bio" @@ -1271,3 +1273,29 @@ def test_cannot_submit_more_than_3_proposals(graphql_client, user): assert resp["data"]["sendSubmission"]["errors"]["nonFieldErrors"] == [ "You can only submit up to 3 proposals" ] + + +def test_submit_talk_with_do_not_record_true(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",), + ) + + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + + resp, _ = _submit_talk(graphql_client, conference, doNotRecord=True) + + assert resp["data"]["sendSubmission"]["__typename"] == "Submission" + assert resp["data"]["sendSubmission"]["doNotRecord"] is True + + talk = Submission.objects.get_by_hashid(resp["data"]["sendSubmission"]["id"]) + assert talk.do_not_record is True diff --git a/backend/api/submissions/tests/test_submissions.py b/backend/api/submissions/tests/test_submissions.py index 1379fc4bdf..55333a5e8d 100644 --- a/backend/api/submissions/tests/test_submissions.py +++ b/backend/api/submissions/tests/test_submissions.py @@ -10,9 +10,9 @@ def test_submissions_are_random_by_user(graphql_client, mock_has_ticket): - user_1 = UserFactory(id=100) - user_2 = UserFactory(id=103) - user_3 = UserFactory(id=104) + user_1 = UserFactory() + user_2 = UserFactory() + user_3 = UserFactory() graphql_client.force_login(user_1) diff --git a/backend/api/submissions/types.py b/backend/api/submissions/types.py index 128bcb18b3..5679b5b147 100644 --- a/backend/api/submissions/types.py +++ b/backend/api/submissions/types.py @@ -39,6 +39,7 @@ def resolver(self, info: Info): class SubmissionType: id: strawberry.ID name: str + is_recordable: bool @strawberry.type @@ -114,10 +115,11 @@ class Submission: topic: Annotated["Topic", strawberry.lazy("api.conferences.types")] | None type: SubmissionType | None duration: Annotated["Duration", strawberry.lazy("api.conferences.types")] | None - audience_level: Annotated[ - "AudienceLevel", strawberry.lazy("api.conferences.types") - ] | None + audience_level: ( + Annotated["AudienceLevel", strawberry.lazy("api.conferences.types")] | None + ) notes: str | None = private_field() + do_not_record: bool | None = private_field() @strawberry.field def schedule_items( diff --git a/backend/submissions/admin.py b/backend/submissions/admin.py index 7201ad726e..5e6773820c 100644 --- a/backend/submissions/admin.py +++ b/backend/submissions/admin.py @@ -249,6 +249,7 @@ class SubmissionAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin): "conference", "audience_level", "languages", + "do_not_record", ) }, ), @@ -325,7 +326,7 @@ class Media: @admin.register(SubmissionType) class SubmissionTypeAdmin(admin.ModelAdmin): - list_display = ("name",) + list_display = ("name", "is_recordable") @admin.register(SubmissionTag) diff --git a/backend/submissions/migrations/0029_submission_do_not_record.py b/backend/submissions/migrations/0029_submission_do_not_record.py new file mode 100644 index 0000000000..aa834743ab --- /dev/null +++ b/backend/submissions/migrations/0029_submission_do_not_record.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-12-14 13:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0028_alter_submission_pending_status'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='do_not_record', + field=models.BooleanField(default=False, help_text='If true, the submission will not be recorded.', verbose_name='do not record'), + ), + ] diff --git a/backend/submissions/migrations/0030_submissiontype_is_recordable.py b/backend/submissions/migrations/0030_submissiontype_is_recordable.py new file mode 100644 index 0000000000..01c4c2bced --- /dev/null +++ b/backend/submissions/migrations/0030_submissiontype_is_recordable.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-12-14 14:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0029_submission_do_not_record'), + ] + + operations = [ + migrations.AddField( + model_name='submissiontype', + name='is_recordable', + field=models.BooleanField(default=True, help_text='If true, the proposals of this type can be recorded.', verbose_name='is recordable'), + ), + ] diff --git a/backend/submissions/models.py b/backend/submissions/models.py index 66066c306b..c045f0c4c1 100644 --- a/backend/submissions/models.py +++ b/backend/submissions/models.py @@ -107,6 +107,12 @@ class Submission(TimeStampedModel): _("pending status"), choices=STATUS, max_length=20, null=True, blank=True ) + do_not_record = models.BooleanField( + _("do not record"), + default=False, + help_text=_("If true, the submission will not be recorded."), + ) + objects = SubmissionQuerySet().as_manager() @property @@ -230,6 +236,11 @@ def __str__(self): class SubmissionType(models.Model): name = models.CharField(max_length=100, unique=True) + is_recordable = models.BooleanField( + _("is recordable"), + default=True, + help_text=_("If true, the proposals of this type can be recorded."), + ) def __str__(self): return self.name