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
10 changes: 0 additions & 10 deletions backend/api/grants/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from typing import Optional

import strawberry
from api.participants.types import Participant
from participants.models import Participant as ParticipantModel

from grants.models import Grant as GrantModel

Expand Down Expand Up @@ -36,15 +34,8 @@ class Grant:
travelling_from: Optional[str]
applicant_reply_deadline: Optional[datetime]

participant: Participant

@classmethod
def from_model(cls, grant: GrantModel) -> Grant:
participant = ParticipantModel.objects.filter(
user_id=grant.user_id,
conference=grant.conference,
).first()

return cls(
id=grant.id,
status=Status(grant.status),
Expand All @@ -64,5 +55,4 @@ def from_model(cls, grant: GrantModel) -> Grant:
notes=grant.notes,
travelling_from=grant.travelling_from,
applicant_reply_deadline=grant.applicant_reply_deadline,
participant=Participant.from_model(participant),
)
3 changes: 3 additions & 0 deletions backend/api/participants/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Optional

from strawberry.scalars import JSON
import strawberry
from strawberry import ID

Expand All @@ -22,6 +23,7 @@ class Participant:
mastodon_handle: str
speaker_id: strawberry.Private[int]
fullname: str
speaker_availabilities: JSON

_speaker_level: strawberry.Private[str]
_previous_talk_video: strawberry.Private[str]
Expand Down Expand Up @@ -59,4 +61,5 @@ def from_model(cls, instance):
linkedin_url=instance.linkedin_url,
facebook_url=instance.facebook_url,
mastodon_handle=instance.mastodon_handle,
speaker_availabilities=instance.speaker_availabilities or {},
)
6 changes: 6 additions & 0 deletions backend/api/submissions/mutations.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from strawberry.scalars import JSON

from django.db import transaction
import math
import re
Expand Down Expand Up @@ -208,6 +210,7 @@ class SendSubmissionInput(BaseSubmissionInput):
speaker_linkedin_url: str
speaker_facebook_url: str
speaker_mastodon_handle: str
speaker_availabilities: JSON

topic: Optional[ID] = strawberry.field(default=None)
tags: list[ID] = strawberry.field(default_factory=list)
Expand Down Expand Up @@ -236,6 +239,7 @@ class UpdateSubmissionInput(BaseSubmissionInput):
speaker_linkedin_url: str
speaker_facebook_url: str
speaker_mastodon_handle: str
speaker_availabilities: JSON

topic: Optional[ID] = strawberry.field(default=None)
tags: list[ID] = strawberry.field(default_factory=list)
Expand Down Expand Up @@ -307,6 +311,7 @@ def update_submission(
"linkedin_url": input.speaker_linkedin_url,
"facebook_url": input.speaker_facebook_url,
"mastodon_handle": input.speaker_mastodon_handle,
"speaker_availabilities": input.speaker_availabilities,
},
)

Expand Down Expand Up @@ -368,6 +373,7 @@ def send_submission(
"linkedin_url": input.speaker_linkedin_url,
"facebook_url": input.speaker_facebook_url,
"mastodon_handle": input.speaker_mastodon_handle,
"speaker_availabilities": input.speaker_availabilities,
},
)

Expand Down
64 changes: 64 additions & 0 deletions backend/api/submissions/tests/test_edit_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ def _update_submission(
new_speaker_linkedin_url="",
new_speaker_facebook_url="",
new_speaker_mastodon_handle="",
new_speaker_availabilities=None,
):
new_title = new_title or {"en": "new title to use"}
new_elevator_pitch = new_elevator_pitch or {"en": "This is an elevator pitch"}
new_abstract = new_abstract or {"en": "abstract here"}
short_social_summary = new_short_social_summary or ""
new_speaker_photo = new_speaker_photo or FileFactory().id
new_speaker_availabilities = new_speaker_availabilities or {}

return graphql_client.query(
"""
Expand Down Expand Up @@ -141,6 +143,7 @@ def _update_submission(
"speakerLinkedinUrl": new_speaker_linkedin_url,
"speakerFacebookUrl": new_speaker_facebook_url,
"speakerMastodonHandle": new_speaker_mastodon_handle,
"speakerAvailabilities": new_speaker_availabilities,
}
},
)
Expand Down Expand Up @@ -201,6 +204,67 @@ def test_update_submission(graphql_client, user):
assert participant.linkedin_url == "http://linkedin.com/company/pythonpizza"


def test_update_submission_speaker_availabilities(graphql_client, 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",
)

graphql_client.force_login(user)

new_topic = conference.topics.filter(name="diy").first()
new_audience = conference.audience_levels.filter(name="senior").first()
new_tag = SubmissionTagFactory(name="yello")
new_duration = conference.durations.filter(name="20m").first()
new_type = conference.submission_types.filter(name="workshop").first()

response = _update_submission(
graphql_client,
submission=submission,
new_topic=new_topic,
new_audience=new_audience,
new_tag=new_tag,
new_duration=new_duration,
new_type=new_type,
new_speaker_level=Submission.SPEAKER_LEVELS.experienced,
new_speaker_availabilities={
"2023-12-10@am": "unavailable",
"2023-12-11@pm": "unavailable",
"2023-12-12@am": "preferred",
"2023-12-13@am": None,
},
)

submission.refresh_from_db()

assert response["data"]["updateSubmission"]["__typename"] == "Submission"

participant = Participant.objects.first()
assert participant.speaker_availabilities == {
"2023-12-10@am": "unavailable",
"2023-12-11@pm": "unavailable",
"2023-12-12@am": "preferred",
"2023-12-13@am": None,
}


def test_update_submission_with_invalid_facebook_social_url(graphql_client, user):
conference = ConferenceFactory(
topics=("life", "diy"),
Expand Down
11 changes: 11 additions & 0 deletions backend/api/submissions/tests/test_send_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def _submit_proposal(client, conference, submission, **kwargs):
"speakerFacebookUrl": "https://facebook.com/fake-link",
"speakerMastodonHandle": "[email protected]",
"tags": [tag.id],
"speakerAvailabilities": {},
}

override_conference = kwargs.pop("override_conference", None)
Expand Down Expand Up @@ -163,6 +164,11 @@ def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, m
shortSocialSummary="summary",
speakerBio="my bio",
speakerPhoto=speaker_photo,
speakerAvailabilities={
"2023-10-10@am": "preferred",
"2023-10-11@pm": "unavailable",
"2023-10-12@am": "available",
},
)

assert resp["data"]["sendSubmission"]["__typename"] == "Submission"
Expand Down Expand Up @@ -190,6 +196,11 @@ def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, m
participant = Participant.objects.get(conference=conference, user_id=user.id)
assert participant.bio == "my bio"
assert participant.photo_file_id == speaker_photo
assert participant.speaker_availabilities == {
"2023-10-10@am": "preferred",
"2023-10-11@pm": "unavailable",
"2023-10-12@am": "available",
}

assert PrivacyPolicyAcceptanceRecord.objects.filter(
user=user, conference=conference, privacy_policy="cfp"
Expand Down
16 changes: 2 additions & 14 deletions backend/participants/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,7 @@
class ParticipantForm(forms.ModelForm):
class Meta:
model = Participant
fields = [
"conference",
"user",
"photo",
"bio",
"website",
"twitter_handle",
"instagram_handle",
"linkedin_url",
"facebook_url",
"mastodon_handle",
"speaker_level",
"previous_talk_video",
]
fields = "__all__"


@admin.register(Participant)
Expand Down Expand Up @@ -54,6 +41,7 @@ class ParticipantAdmin(admin.ModelAdmin):
"mastodon_handle",
"speaker_level",
"previous_talk_video",
"speaker_availabilities",
),
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.1 on 2024-11-28 22:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('participants', '0011_alter_participant_photo'),
]

operations = [
migrations.AddField(
model_name='participant',
name='speaker_availabilities',
field=models.JSONField(blank=True, null=True, verbose_name='speaker availabilities'),
),
]
3 changes: 3 additions & 0 deletions backend/participants/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class SpeakerLevels(models.TextChoices):
previous_talk_video = models.URLField(
_("previous talk video"), blank=True, max_length=2049
)
speaker_availabilities = models.JSONField(
_("speaker availabilities"), null=True, blank=True
)

objects = ParticipantQuerySet().as_manager()

Expand Down
93 changes: 93 additions & 0 deletions frontend/src/components/cfp-form/about-you-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {
CardPart,
Grid,
Heading,
Input,
InputWrapper,
MultiplePartsCard,
Select,
Text,
} from "@python-italia/pycon-styleguide";
import { FormattedMessage } from "react-intl";
import { useTranslatedMessage } from "~/helpers/use-translated-message";

const SPEAKER_LEVEL_OPTIONS = [
{
value: "",
disabled: true,
messageId: "cfp.selectSpeakerLevel",
},
{
disabled: false,
value: "new",
messageId: "cfp.speakerLevel.new",
},
{
disabled: false,
value: "intermediate",
messageId: "cfp.speakerLevel.intermediate",
},
{
disabled: false,
value: "experienced",
messageId: "cfp.speakerLevel.experienced",
},
];

export const AboutYouSection = ({ formOptions, getErrors }) => {
const inputPlaceholder = useTranslatedMessage("input.placeholder");
const { select, url } = formOptions;

return (
<MultiplePartsCard>
<CardPart contentAlign="left">
<Heading size={3}>
<FormattedMessage id="cfp.aboutYou" />
</Heading>
</CardPart>
<CardPart background="milk" contentAlign="left">
<Grid cols={1} gap="medium">
<Text size={2}>
<FormattedMessage id="cfp.aboutYouDescription" />
</Text>
<InputWrapper
required={true}
title={<FormattedMessage id="cfp.speakerLevel" />}
description={<FormattedMessage id="cfp.speakerLevelDescription" />}
>
<Select
{...select("speakerLevel")}
required={true}
errors={getErrors("validationSpeakerLevel")}
>
{SPEAKER_LEVEL_OPTIONS.map(({ value, disabled, messageId }) => (
<FormattedMessage id={messageId} key={messageId}>
{(copy) => (
<option disabled={disabled} value={value}>
{copy}
</option>
)}
</FormattedMessage>
))}
</Select>
</InputWrapper>

<InputWrapper
title={<FormattedMessage id="cfp.previousTalkVideo" />}
description={
<FormattedMessage id="cfp.previousTalkVideoDescription" />
}
>
<Input
{...url("previousTalkVideo")}
required={false}
maxLength={2048}
errors={getErrors("validationPreviousTalkVideo")}
placeholder={inputPlaceholder}
/>
</InputWrapper>
</Grid>
</CardPart>
</MultiplePartsCard>
);
};
Loading