Skip to content

Commit

Permalink
Add deadline support
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoacierno committed Dec 29, 2024
1 parent 6cc04c2 commit a392c29
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 29 deletions.
5 changes: 5 additions & 0 deletions backend/api/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,8 @@ class NotFound:
@strawberry.type
class NoAdmissionTicket:
message: str = "User does not have admission ticket"


@strawberry.type
class FormNotAvailable:
message: str = "Form is not available"
9 changes: 7 additions & 2 deletions backend/api/visa/mutations/request_invitation_letter.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from django.db import transaction
from datetime import date
from typing import Annotated
from api.types import BaseErrorType, NoAdmissionTicket
from api.types import BaseErrorType, FormNotAvailable, NoAdmissionTicket
from api.utils import validate_email
from api.visa.types import InvitationLetterOnBehalfOf, InvitationLetterRequest
from api.extensions import RateLimit
from conferences.models.deadline import Deadline
from privacy_policy.record import record_privacy_policy_acceptance
from visa.models import (
InvitationLetterRequest as InvitationLetterRequestModel,
Expand Down Expand Up @@ -104,7 +105,8 @@ def validate(self, conference: Conference) -> RequestInvitationLetterErrors | No
InvitationLetterRequest
| RequestInvitationLetterErrors
| NoAdmissionTicket
| InvitationLetterAlreadyRequested,
| InvitationLetterAlreadyRequested
| FormNotAvailable,
strawberry.union(name="RequestInvitationLetterResult"),
]

Expand All @@ -121,6 +123,9 @@ def request_invitation_letter(
if errors := input.validate(conference):
return errors

if not conference.is_deadline_active(Deadline.TYPES.invitation_letter_request):
return FormNotAvailable()

user = info.context.request.user

if input.on_behalf_of == InvitationLetterOnBehalfOf.SELF:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from conferences.models.deadline import Deadline
from privacy_policy.models import PrivacyPolicyAcceptanceRecord

from datetime import date
Expand All @@ -8,7 +9,12 @@
InvitationLetterRequestOnBehalfOf,
InvitationLetterRequestStatus,
)
from conferences.tests.factories import ConferenceFactory
from conferences.tests.factories import (
PastDeadlineFactory,
ConferenceFactory,
FutureDeadlineFactory,
ActiveDeadlineFactory,
)
import pytest

pytestmark = pytest.mark.django_db
Expand Down Expand Up @@ -46,6 +52,9 @@ def _request_invitation_letter(client, **input):

def test_request_invitation_letter(graphql_client, user, mock_has_ticket):
conference = ConferenceFactory()
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)
mock_has_ticket(conference)

graphql_client.force_login(user)
Expand Down Expand Up @@ -102,6 +111,9 @@ def test_can_request_invitation_letter_to_multiple_conferences(

conference = ConferenceFactory()
mock_has_ticket(conference)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

InvitationLetterRequestFactory(
requester=user,
Expand All @@ -111,6 +123,9 @@ def test_can_request_invitation_letter_to_multiple_conferences(

other_conference = ConferenceFactory()
mock_has_ticket(other_conference)
ActiveDeadlineFactory(
conference=other_conference, type=Deadline.TYPES.invitation_letter_request
)

response = _request_invitation_letter(
graphql_client,
Expand Down Expand Up @@ -165,6 +180,9 @@ def test_request_invitation_letter_email_is_ignored_for_self_requests(
):
conference = ConferenceFactory()
mock_has_ticket(conference)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -210,6 +228,9 @@ def test_request_invitation_letter_on_behalf_of_other(
):
conference = ConferenceFactory()
mock_has_ticket(conference, has_ticket=has_ticket, user=user)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -261,6 +282,9 @@ def test_duplicate_requests_for_others_are_ignored(
):
conference = ConferenceFactory()
mock_has_ticket(conference, has_ticket=True, user=user)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -319,6 +343,9 @@ def test_cannot_request_invitation_letter_if_already_done(
):
conference = ConferenceFactory()
mock_has_ticket(conference)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

InvitationLetterRequestFactory(
requester=user,
Expand Down Expand Up @@ -354,6 +381,9 @@ def test_cannot_request_invitation_letter_without_ticket(
):
conference = ConferenceFactory()
mock_has_ticket(conference, False)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -383,6 +413,9 @@ def test_cannot_request_invitation_letter_for_non_existing_conference(
):
conference = ConferenceFactory()
mock_has_ticket(conference)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -416,6 +449,9 @@ def test_email_is_required_when_requesting_on_behalf_of_other(
):
conference = ConferenceFactory()
mock_has_ticket(conference, has_ticket=True, user=user)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -447,6 +483,9 @@ def test_email_is_required_when_requesting_on_behalf_of_other(
def test_required_fields_are_enforced(graphql_client, user, mock_has_ticket):
conference = ConferenceFactory()
mock_has_ticket(conference, has_ticket=True, user=user)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -486,6 +525,9 @@ def test_required_fields_are_enforced(graphql_client, user, mock_has_ticket):
def test_max_lengths_are_enforced(graphql_client, user, mock_has_ticket):
conference = ConferenceFactory()
mock_has_ticket(conference, has_ticket=True, user=user)
ActiveDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

graphql_client.force_login(user)

Expand Down Expand Up @@ -533,3 +575,44 @@ def test_max_lengths_are_enforced(graphql_client, user, mock_has_ticket):
],
}
assert InvitationLetterRequest.objects.count() == 0


@pytest.mark.parametrize("deadline_status", [None, "past", "future"])
def test_cannot_request_invitation_letter_if_form_is_disabled(
graphql_client, user, mock_has_ticket, deadline_status
):
conference = ConferenceFactory()

if deadline_status == "past":
PastDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)
elif deadline_status == "future":
FutureDeadlineFactory(
conference=conference, type=Deadline.TYPES.invitation_letter_request
)

mock_has_ticket(conference)

graphql_client.force_login(user)

response = _request_invitation_letter(
graphql_client,
input={
"conference": conference.code,
"onBehalfOf": "SELF",
"fullName": "Mario Rossi",
"email": "",
"nationality": "Italian",
"address": "via Roma",
"passportNumber": "YA1234567",
"embassyName": "Italian Embassy in France",
"dateOfBirth": "1999-01-01",
},
)

assert (
response["data"]["requestInvitationLetter"]["__typename"] == "FormNotAvailable"
)

assert InvitationLetterRequest.objects.count() == 0
18 changes: 18 additions & 0 deletions backend/conferences/migrations/0052_alter_deadline_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-29 15:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('conferences', '0051_conference_location'),
]

operations = [
migrations.AlterField(
model_name='deadline',
name='type',
field=models.CharField(choices=[('cfp', 'Call for proposal'), ('voting', 'Voting'), ('refund', 'Ticket refund'), ('grants', 'Grants'), ('badge_preview', 'Badge preview'), ('invitation_letter_request', 'Invitation letter request'), ('custom', 'Custom deadline')], max_length=256, verbose_name='type'),
),
]
7 changes: 7 additions & 0 deletions backend/conferences/models/conference.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ def is_grants_open(self):
except Deadline.DoesNotExist:
return False

def is_deadline_active(self, deadline_type: str):
try:
deadline = self.deadlines.get(type=deadline_type)
return deadline.status == DeadlineStatus.HAPPENING_NOW
except Deadline.DoesNotExist:
return False

def __str__(self):
return f"{self.name} <{self.code}>"

Expand Down
1 change: 1 addition & 0 deletions backend/conferences/models/deadline.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Deadline(TimeFramedModel):
("refund", _("Ticket refund")),
("grants", _("Grants")),
("badge_preview", _("Badge preview")),
("invitation_letter_request", _("Invitation letter request")),
("custom", _("Custom deadline")),
)

Expand Down
15 changes: 15 additions & 0 deletions backend/conferences/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ class Meta:
model = Deadline


class PastDeadlineFactory(DeadlineFactory):
start = factory.Faker("past_datetime", tzinfo=UTC)
end = factory.Faker("past_datetime", tzinfo=UTC)


class FutureDeadlineFactory(DeadlineFactory):
start = factory.Faker("future_datetime", tzinfo=UTC)
end = factory.Faker("future_datetime", tzinfo=UTC)


class ActiveDeadlineFactory(DeadlineFactory):
start = factory.Faker("past_datetime", tzinfo=UTC)
end = factory.Faker("future_datetime", tzinfo=UTC)


class AudienceLevelFactory(DjangoModelFactory):
name = factory.fuzzy.FuzzyChoice(("Beginner", "Intermidiate", "Advanced"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ import {
import { FormattedMessage } from "react-intl";
import { MetaTags } from "~/components/meta-tags";
import { useCurrentLanguage } from "~/locale/context";
import { DeadlineStatus, useRequestInvitationLetterPageQuery } from "~/types";
import { createHref } from "../link";
import { InvitationLetterForm } from "./invitation-letter-form";

export const RequestInvitationLetterPageHandler = () => {
const language = useCurrentLanguage();
const {
data: {
conference: { invitationLetterRequestDeadline },
me: { hasAdmissionTicket, invitationLetterRequest },
},
} = useRequestInvitationLetterPageQuery({
variables: {
conference: process.env.conferenceCode,
},
});

const deadlineStatus = invitationLetterRequestDeadline?.status;

return (
<Page endSeparator={false}>
Expand Down Expand Up @@ -41,8 +54,47 @@ export const RequestInvitationLetterPageHandler = () => {

<Spacer size="xl" />

<InvitationLetterForm />
{(!deadlineStatus || deadlineStatus === DeadlineStatus.InThePast) && (
<FormClosed />
)}
{deadlineStatus === DeadlineStatus.InTheFuture && (
<FormOpeningSoon date={invitationLetterRequestDeadline?.start} />
)}
{deadlineStatus === DeadlineStatus.HappeningNow && (
<InvitationLetterForm
hasAdmissionTicket={hasAdmissionTicket}
invitationLetterRequest={invitationLetterRequest}
/>
)}
</Section>
</Page>
);
};

const FormClosed = () => (
<Text size={2}>
<FormattedMessage id="requestInvitationLetter.formClosed" />
</Text>
);

const FormOpeningSoon = ({ date }) => {
const language = useCurrentLanguage();
const formatter = new Intl.DateTimeFormat(language, {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
});

return (
<Text size={2}>
<FormattedMessage
id="requestInvitationLetter.formOpeningSoon"
values={{
date: formatter.format(new Date(date)),
}}
/>
</Text>
);
};

This file was deleted.

Loading

0 comments on commit a392c29

Please sign in to comment.