Skip to content

Cospeakers support #4378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions backend/api/conferences/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ def days(self, info: Info) -> list[Day]:
"submission__speaker",
"submission__languages",
"submission__schedule_items",
"submission__co_speakers",
"submission__co_speakers__user",
"keynote",
"keynote__schedule_items",
"keynote__schedule_items__rooms",
Expand Down
10 changes: 10 additions & 0 deletions backend/schedule/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,16 @@ def speakers(self):
speakers.extend(
[speaker.user for speaker in self.additional_speakers.order_by("id").all()]
)

if self.submission_id:
speakers.extend(
[
co_speaker.user
for co_speaker in self.submission.co_speakers.accepted()
.order_by("id")
.all()
]
)
return speakers

def clean(self):
Expand Down
22 changes: 20 additions & 2 deletions backend/submissions/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from ordered_model.admin import (
OrderedInlineModelAdminMixin,
OrderedTabularInline,
)
from django.urls import reverse
from grants.tasks import get_name
from notifications.models import EmailTemplate, EmailTemplateIdentifier
Expand Down Expand Up @@ -26,6 +30,7 @@


from .models import (
ProposalCoSpeaker,
ProposalMaterial,
Submission,
SubmissionComment,
Expand Down Expand Up @@ -214,8 +219,21 @@ class ProposalMaterialInline(admin.TabularInline):
autocomplete_fields = ("file",)


class ProposalCoSpeakerInline(OrderedTabularInline):
model = ProposalCoSpeaker
extra = 0
autocomplete_fields = ("user",)
fields = ("user", "status", "order", "move_up_down_links")
readonly_fields = ("order", "move_up_down_links")


@admin.register(Submission)
class SubmissionAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
class SubmissionAdmin(
ExportMixin,
ConferencePermissionMixin,
OrderedInlineModelAdminMixin,
admin.ModelAdmin,
):
resource_class = SubmissionResource
form = SubmissionAdminForm
list_display = (
Expand Down Expand Up @@ -276,7 +294,7 @@ class SubmissionAdmin(ExportMixin, ConferencePermissionMixin, admin.ModelAdmin):
send_proposal_in_waiting_list_email_action,
]
autocomplete_fields = ("speaker",)
inlines = [ProposalMaterialInline]
inlines = [ProposalMaterialInline, ProposalCoSpeakerInline]

def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
Expand Down
33 changes: 33 additions & 0 deletions backend/submissions/migrations/0028_proposalcospeaker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 5.1.4 on 2025-02-23 18:08

import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('submissions', '0027_submissionconfirmpendingstatusproxy'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='ProposalCoSpeaker',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
('status', models.CharField(choices=[('pending', 'Pending'), ('accepted', 'Accepted'), ('rejected', 'Rejected')], default='pending', max_length=30, verbose_name='status')),
('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='co_speakers', to='submissions.submission', verbose_name='proposal')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'abstract': False,
},
),
]
41 changes: 40 additions & 1 deletion backend/submissions/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ordered_model.models import OrderedModel
from django.core import exceptions
from django.db import models
from django.urls import reverse
Expand All @@ -9,7 +10,7 @@
from api.helpers.ids import encode_hashid
from i18n.fields import I18nCharField, I18nTextField

from .querysets import SubmissionQuerySet
from .querysets import ProposalCoSpeakerQuerySet, SubmissionQuerySet


class SubmissionTag(models.Model):
Expand Down Expand Up @@ -212,6 +213,44 @@ def __str__(self):
)


class ProposalCoSpeakerStatus(models.TextChoices):
pending = "pending", _("Pending")
accepted = "accepted", _("Accepted")
rejected = "rejected", _("Rejected")


class ProposalCoSpeaker(TimeStampedModel, OrderedModel):
conference_reference = "proposal__conference"

status = models.CharField(
_("status"),
choices=ProposalCoSpeakerStatus.choices,
max_length=30,
default=ProposalCoSpeakerStatus.pending,
)
proposal = models.ForeignKey(
"submissions.Submission",
on_delete=models.CASCADE,
verbose_name=_("proposal"),
related_name="co_speakers",
)

user = models.ForeignKey(
"users.User",
on_delete=models.CASCADE,
null=False,
blank=False,
verbose_name=_("user"),
related_name="+",
)

order_with_respect_to = "proposal"
objects = ProposalCoSpeakerQuerySet().as_manager()

def __str__(self):
return f"{self.user_id} {self.proposal.title}"


class ProposalMaterial(TimeStampedModel):
proposal = models.ForeignKey(
"submissions.Submission",
Expand Down
10 changes: 10 additions & 0 deletions backend/submissions/querysets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from api.helpers.ids import decode_hashid
from conferences.querysets import ConferenceQuerySetMixin
from django.db import models
from ordered_model.models import OrderedModelQuerySet


class SubmissionQuerySet(ConferenceQuerySetMixin, models.QuerySet):
Expand All @@ -15,3 +16,12 @@ def accepted(self):

def of_user(self, user):
return self.filter(speaker=user)


class ProposalCoSpeakerQuerySet(
ConferenceQuerySetMixin, OrderedModelQuerySet, models.QuerySet
):
def accepted(self):
from submissions.models import ProposalCoSpeakerStatus

return self.filter(status=ProposalCoSpeakerStatus.accepted)
42 changes: 42 additions & 0 deletions frontend/src/components/cfp-form/co-speakers-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
CardPart,
Grid,
Heading,
Input,
InputWrapper,
MultiplePartsCard,
Spacer,
Text,
} from "@python-italia/pycon-styleguide";
import { FormattedMessage } from "react-intl";
import { useTranslatedMessage } from "~/helpers/use-translated-message";

export const CoSpeakersSection = () => {
const inputPlaceholder = useTranslatedMessage("input.placeholder");

return (
<MultiplePartsCard>
<CardPart contentAlign="left">
<Heading size={3}>
<FormattedMessage id="cfp.cospeakers.title" />
</Heading>
</CardPart>
<CardPart background="milk" contentAlign="left">
<Text size={2}>
<FormattedMessage id="cfp.cospeakers.description" />
</Text>
<Spacer size="small" />

<InputWrapper
required={true}
title={<FormattedMessage id="cfp.cospeakers.email.title" />}
description={
<FormattedMessage id="cfp.cospeakers.email.description" />
}
>
<Input type="email" placeholder={inputPlaceholder} />
</InputWrapper>
</CardPart>
</MultiplePartsCard>
);
};
5 changes: 5 additions & 0 deletions frontend/src/components/cfp-form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from "../public-profile-card";
import { AboutYouSection } from "./about-you-section";
import { AvailabilitySection } from "./availability-section";
import { CoSpeakersSection } from "./co-speakers-section";
import { ProposalSection } from "./proposal-section";

export type CfpFormFields = ParticipantFormFields & {
Expand Down Expand Up @@ -331,6 +332,10 @@ export const CfpForm = ({

<Spacer size="medium" />

<CoSpeakersSection />

<Spacer size="medium" />

<PublicProfileCard
me={participantData.me}
formOptions={formOptions}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/locale/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export const messages = {

"global.sessions": "Sessions",

"cfp.cospeakers.title": "Co-speakers",
"cfp.cospeakers.description": "If you have co-speakers, add them here!",
"cfp.cospeakers.email.title": "Email",
"cfp.cospeakers.email.description":
"Once added they will receive an email to confirm their participation.",

"input.placeholder": "Type here...",
"global.accordion.close": "Close",
"global.accordion.readMore": "Read more",
Expand Down
Loading