Skip to content

Commit 741f497

Browse files
authored
Add support for uploading slides in submissions (#4425)
1 parent d07501c commit 741f497

File tree

26 files changed

+1278
-141
lines changed

26 files changed

+1278
-141
lines changed

.github/workflows/frontend-lint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ jobs:
3434
run: pnpm install
3535
- name: Codegen
3636
run: pnpm run codegen
37+
env:
38+
API_URL_SERVER: https://pastaporto-admin.pycon.it
3739
- name: Run Biome
3840
run: biome ci .

backend/api/files_upload/permissions.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ def _check_proposal_material(self, user, input: "UploadFileInput") -> bool:
3737
conference_code = input.data.conference_code
3838

3939
try:
40-
proposal = Submission.objects.for_conference_code(
41-
conference_code
42-
).get_by_hashid(proposal_id)
40+
proposal = (
41+
Submission.objects.for_conference_code(conference_code)
42+
.filter(status=Submission.STATUS.accepted)
43+
.get_by_hashid(proposal_id)
44+
)
4345
except (Submission.DoesNotExist, IndexError):
4446
return False
4547

backend/api/files_upload/tests/mutations/test_upload_file.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22
from files_upload.models import File
3-
from submissions.tests.factories import SubmissionFactory
3+
from submissions.tests.factories import AcceptedSubmissionFactory, SubmissionFactory
44
from conferences.tests.factories import ConferenceFactory
55
from django.test import override_settings
66

@@ -64,7 +64,7 @@ def test_upload_participant_avatar_to_invalid_conf_fails(graphql_client, user):
6464

6565

6666
def test_upload_proposal_material_file(graphql_client, user):
67-
proposal = SubmissionFactory(speaker=user)
67+
proposal = AcceptedSubmissionFactory(speaker=user)
6868
graphql_client.force_login(user)
6969

7070
response = _upload_file(
@@ -88,7 +88,26 @@ def test_upload_proposal_material_file(graphql_client, user):
8888

8989

9090
def test_cannot_upload_proposal_material_file_if_not_speaker(graphql_client, user):
91-
proposal = SubmissionFactory()
91+
proposal = AcceptedSubmissionFactory()
92+
graphql_client.force_login(user)
93+
94+
response = _upload_file(
95+
graphql_client,
96+
{
97+
"proposalMaterial": {
98+
"filename": "test.txt",
99+
"proposalId": proposal.hashid,
100+
"conferenceCode": proposal.conference.code,
101+
}
102+
},
103+
)
104+
105+
assert not response["data"]
106+
assert response["errors"][0]["message"] == "You cannot upload files of this type"
107+
108+
109+
def test_cannot_upload_proposal_material_file_if_not_accepted(graphql_client, user):
110+
proposal = SubmissionFactory(status="proposed")
92111
graphql_client.force_login(user)
93112

94113
response = _upload_file(
@@ -129,7 +148,7 @@ def test_cannot_upload_proposal_material_file_with_invalid_proposal_id(
129148
def test_cannot_upload_proposal_material_file_with_invalid_proposal_id_for_conference(
130149
graphql_client, user
131150
):
132-
proposal = SubmissionFactory()
151+
proposal = AcceptedSubmissionFactory()
133152
graphql_client.force_login(user)
134153

135154
response = _upload_file(
@@ -151,7 +170,7 @@ def test_cannot_upload_proposal_material_file_with_invalid_proposal_id_for_confe
151170
"file_type", [File.Type.PARTICIPANT_AVATAR, File.Type.PROPOSAL_MATERIAL]
152171
)
153172
def test_superusers_can_upload_anything(graphql_client, admin_superuser, file_type):
154-
proposal = SubmissionFactory()
173+
proposal = AcceptedSubmissionFactory()
155174
graphql_client.force_login(admin_superuser)
156175

157176
req_input = {}

backend/api/submissions/mutations.py

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from urllib.parse import urljoin
22
from django.conf import settings
3+
from conferences.frontend import trigger_frontend_revalidate
34
from grants.tasks import get_name
45
from notifications.models import EmailTemplate, EmailTemplateIdentifier
56
from strawberry.scalars import JSON
@@ -20,15 +21,22 @@
2021
from i18n.strings import LazyI18nString
2122
from languages.models import Language
2223
from participants.models import Participant
23-
from submissions.models import Submission as SubmissionModel
24+
from submissions.models import ProposalMaterial, Submission as SubmissionModel
2425
from submissions.tasks import notify_new_cfp_submission
2526

26-
from .types import Submission
27+
from .types import Submission, SubmissionMaterialInput
2728

2829
FACEBOOK_LINK_MATCH = re.compile(r"^http(s)?:\/\/(www\.)?facebook\.com\/")
2930
LINKEDIN_LINK_MATCH = re.compile(r"^http(s)?:\/\/(www\.)?linkedin\.com\/")
3031

3132

33+
@strawberry.type
34+
class ProposalMaterialErrors:
35+
file_id: list[str] = strawberry.field(default_factory=list)
36+
url: list[str] = strawberry.field(default_factory=list)
37+
id: list[str] = strawberry.field(default_factory=list)
38+
39+
3240
@strawberry.type
3341
class SendSubmissionErrors(BaseErrorType):
3442
@strawberry.type
@@ -46,6 +54,7 @@ class _SendSubmissionErrors:
4654
audience_level: list[str] = strawberry.field(default_factory=list)
4755
tags: list[str] = strawberry.field(default_factory=list)
4856
short_social_summary: list[str] = strawberry.field(default_factory=list)
57+
materials: list[ProposalMaterialErrors] = strawberry.field(default_factory=list)
4958

5059
speaker_bio: list[str] = strawberry.field(default_factory=list)
5160
speaker_photo: list[str] = strawberry.field(default_factory=list)
@@ -247,6 +256,22 @@ class UpdateSubmissionInput(BaseSubmissionInput):
247256

248257
topic: Optional[ID] = strawberry.field(default=None)
249258
tags: list[ID] = strawberry.field(default_factory=list)
259+
materials: list[SubmissionMaterialInput] = strawberry.field(default_factory=list)
260+
261+
def validate(self, conference: Conference, submission: SubmissionModel):
262+
errors = super().validate(conference)
263+
264+
if self.materials:
265+
if len(self.materials) > 3:
266+
errors.add_error(
267+
"non_field_errors", "You can only add up to 3 materials"
268+
)
269+
else:
270+
for index, material in enumerate(self.materials):
271+
with errors.with_prefix("materials", index):
272+
material.validate(errors, submission)
273+
274+
return errors
250275

251276

252277
SendSubmissionOutput = Annotated[
@@ -276,7 +301,7 @@ def update_submission(
276301

277302
conference = instance.conference
278303

279-
errors = input.validate(conference=conference)
304+
errors = input.validate(conference=conference, submission=instance)
280305

281306
if errors.has_errors:
282307
return errors
@@ -294,13 +319,56 @@ def update_submission(
294319
instance.speaker_level = input.speaker_level
295320
instance.previous_talk_video = input.previous_talk_video
296321
instance.short_social_summary = input.short_social_summary
322+
297323
languages = Language.objects.filter(code__in=input.languages).all()
298324
instance.languages.set(languages)
299325

300326
instance.tags.set(input.tags)
301327

302328
instance.save()
303329

330+
materials_to_create = []
331+
materials_to_update = []
332+
333+
existing_materials = {
334+
existing_material.id: existing_material
335+
for existing_material in instance.materials.all()
336+
}
337+
for material in input.materials:
338+
existing_material = (
339+
existing_materials.get(int(material.id)) if material.id else None
340+
)
341+
342+
if existing_material:
343+
existing_material.name = material.name
344+
existing_material.url = material.url
345+
existing_material.file_id = material.file_id
346+
materials_to_update.append(existing_material)
347+
else:
348+
materials_to_create.append(
349+
ProposalMaterial(
350+
proposal=instance,
351+
name=material.name,
352+
url=material.url,
353+
file_id=material.file_id,
354+
)
355+
)
356+
357+
if to_delete := [
358+
m.id for m in existing_materials.values() if m not in materials_to_update
359+
]:
360+
ProposalMaterial.objects.filter(
361+
proposal=instance,
362+
id__in=to_delete,
363+
).delete()
364+
365+
if materials_to_create:
366+
ProposalMaterial.objects.bulk_create(materials_to_create)
367+
if materials_to_update:
368+
ProposalMaterial.objects.bulk_update(
369+
materials_to_update, fields=["name", "url", "file_id"]
370+
)
371+
304372
Participant.objects.update_or_create(
305373
user_id=request.user.id,
306374
conference=conference,
@@ -319,6 +387,8 @@ def update_submission(
319387
},
320388
)
321389

390+
trigger_frontend_revalidate(conference, instance)
391+
322392
instance.__strawberry_definition__ = Submission.__strawberry_definition__
323393
return instance
324394

0 commit comments

Comments
 (0)