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
60 changes: 60 additions & 0 deletions backend/conferences/tests/test_clone_conference_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@
SponsorSpecialOptionFactory,
)
from submissions.tests.factories import SubmissionTypeFactory
from visa.models import (
InvitationLetterConferenceConfig,
InvitationLetterDocumentInclusionPolicy,
)
from visa.tests.factories import (
InvitationLetterAssetFactory,
InvitationLetterConferenceConfigFactory,
InvitationLetterDocumentFactory,
InvitationLetterDynamicDocumentFactory,
)
from voting.models import IncludedEvent
from voting.tests.factories.included_event import IncludedEventFactory

Expand Down Expand Up @@ -294,11 +304,56 @@ def test_copies_voting_included_events(source_conference):
).exists()


def test_copies_the_invitation_letter_config(source_conference):
config = InvitationLetterConferenceConfigFactory(conference=source_conference)
asset = InvitationLetterAssetFactory(
invitation_letter_conference_config=config, identifier="logo"
)
letter = InvitationLetterDynamicDocumentFactory(
invitation_letter_conference_config=config, name="Letter"
)
hotel = InvitationLetterDocumentFactory(
invitation_letter_conference_config=config,
name="Hotel booking",
inclusion_policy=InvitationLetterDocumentInclusionPolicy.GRANT_ACCOMMODATION,
)

result = run_clone(source_conference.code)

new_config = InvitationLetterConferenceConfig.objects.get(
conference__code="pycon2027"
)
new_asset = new_config.assets.get()
new_letter, new_hotel = new_config.attached_documents.order_by("order")

assert result["copied"]["invitation_letter_config"] == 4
assert new_asset.identifier == "logo"
assert new_asset.image.name == asset.image.name
assert new_letter.name == "Letter"
assert new_letter.dynamic_document == letter.dynamic_document
assert not new_letter.document
assert new_hotel.name == "Hotel booking"
assert (
new_hotel.inclusion_policy
== InvitationLetterDocumentInclusionPolicy.GRANT_ACCOMMODATION
)
assert new_hotel.document.name == hotel.document.name


def test_running_the_workflow_again_does_not_duplicate_anything(source_conference):
DeadlineFactory(conference=source_conference, type=Deadline.TYPES.cfp)
DurationFactory(conference=source_conference, name="Standard")
SponsorLevelFactory(conference=source_conference, name="Gold")
GenericCopyFactory(conference=source_conference, key="homepage-intro")
invitation_letter_config = InvitationLetterConferenceConfigFactory(
conference=source_conference
)
InvitationLetterAssetFactory(
invitation_letter_conference_config=invitation_letter_config
)
InvitationLetterDocumentFactory(
invitation_letter_conference_config=invitation_letter_config, name="Letter"
)

first = run_clone(source_conference.code, workflow_id="clone-first-run")
second = run_clone(source_conference.code, workflow_id="clone-second-run")
Expand All @@ -309,6 +364,11 @@ def test_running_the_workflow_again_does_not_duplicate_anything(source_conferenc
assert Duration.objects.filter(conference__code="pycon2027").count() == 1
assert SponsorLevel.objects.filter(conference__code="pycon2027").count() == 1
assert GenericCopy.objects.filter(conference__code="pycon2027").count() == 1
new_invitation_letter_config = InvitationLetterConferenceConfig.objects.get(
conference__code="pycon2027"
)
assert new_invitation_letter_config.assets.count() == 1
assert new_invitation_letter_config.attached_documents.count() == 1
assert all(count == 0 for count in second["copied"].values())


Expand Down
55 changes: 51 additions & 4 deletions backend/conferences/workflows/clone_conference.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
Setting up the next edition means re-creating, by hand, everything that is
configuration rather than content: deadlines, durations, the schedule days
with their rooms and slots, sponsor levels and their benefits, email templates,
menus, copy, forms. The workflow does that in one durable run: every step is a
Resonate durable child, so a crash (or a database hiccup, which Resonate
retries) resumes from the step that failed instead of leaving a half-built
conference behind.
menus, copy, forms, invitation letter documents. The workflow does that in one
durable run: every step is a Resonate durable child, so a crash (or a database
hiccup, which Resonate retries) resumes from the step that failed instead of
leaving a half-built conference behind.

Content produced *during* an edition is never copied: proposals, the talks
scheduled into those slots, keynotes, sponsors, grants, vouchers, answers.
Expand All @@ -33,6 +33,11 @@
SponsorLevelBenefit,
SponsorSpecialOption,
)
from visa.models import (
InvitationLetterAsset,
InvitationLetterConferenceConfig,
InvitationLetterDocument,
)
from voting.models import IncludedEvent

resonate = get_resonate()
Expand Down Expand Up @@ -460,6 +465,47 @@ def copy_voting_included_events(source: Conference, conference: Conference) -> i
return copied


@copy_step
def copy_invitation_letter_config(source: Conference, conference: Conference) -> int:
"""Copy the invitation letter config with its assets and attached documents.

Assets and documents point at the source edition's stored files.
"""
source_config = InvitationLetterConferenceConfig.objects.filter(
conference=source
).first()

if source_config is None:
return 0

config, created = InvitationLetterConferenceConfig.objects.get_or_create(
conference=conference
)
copied = int(created)

for asset in source_config.assets.order_by("id"):
_, asset_created = InvitationLetterAsset.objects.get_or_create(
invitation_letter_conference_config=config,
identifier=asset.identifier,
defaults={"image": asset.image.name},
)
copied += int(asset_created)

for document in source_config.attached_documents.order_by("order"):
_, document_created = InvitationLetterDocument.objects.get_or_create(
invitation_letter_conference_config=config,
name=document.name,
defaults={
"inclusion_policy": document.inclusion_policy,
"dynamic_document": document.dynamic_document,
"document": document.document.name,
},
)
copied += int(document_created)

return copied


# Everything that only needs the source and the freshly created conference,
# in the order it is copied. Sponsor levels read the benefits copied just
# before them, so the order matters. The names are what the workflow reports
Expand All @@ -476,6 +522,7 @@ def copy_voting_included_events(source: Conference, conference: Conference) -> i
("cms_content", copy_cms_content),
("forms", copy_forms),
("voting_included_events", copy_voting_included_events),
("invitation_letter_config", copy_invitation_letter_config),
)


Expand Down
Loading