Skip to content

Commit

Permalink
Cospeakers support
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoacierno committed Feb 23, 2025
1 parent b5be418 commit 63a2f05
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 3 deletions.
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

0 comments on commit 63a2f05

Please sign in to comment.