diff --git a/district/migrations/0218_auto_20240601_1530.py b/district/migrations/0218_auto_20240601_1530.py index a25b581a37d50477631432fa86a45c431bbcd69b..d1c8953e0550ab6207c82e77e0486985c88ca698 100644 --- a/district/migrations/0218_auto_20240601_1530.py +++ b/district/migrations/0218_auto_20240601_1530.py @@ -4,6 +4,7 @@ import wagtail from django.apps import apps as base_apps from django.contrib.contenttypes.management import create_contenttypes from django.db import migrations, transaction +from django.db.utils import IntegrityError from wagtail.blocks import CharBlock, RichTextBlock, StreamBlock, StructBlock from district.blocks import ( @@ -18,518 +19,525 @@ from shared.blocks import SocialLinkBlock def migrate_programs(apps, schema_editor): - # Copy this block manually here, as it has been changed in future migrations. - class ProgramGroupWithCandidatesBlock(StructBlock): - title = CharBlock( - label="Název programu", - help_text="Např. 'Krajské volby 2024', 'Evropské volby 2024', ...", - ) - - preamble_content = RichTextBlock( - label="Preambule", - help_text="Text, který se zobrazí před přepínačem mezi kandidáty a programem.", - required=False, - ) - - primary_candidates = CandidateListBlock( - label="Osoby na čele kandidátky", - help_text="Zobrazí se ve velkých blocích na začátku stránky.", - ) - - secondary_candidates = CandidateSecondaryListBlock( - label="Ostatní osoby na kandidátce", - help_text="Zobrazí se v kompaktním seznamu pod čelem kandidátky.", - ) - - program = StreamBlock( - [ - ("program_group", ProgramGroupBlock()), - ("program_group_crossroad", ProgramGroupBlockCrossroad()), - ("program_group_popout", ProgramGroupBlockPopout()), - ( - "carousel_program", - CarouselProgramBlock( - template="styleguide2/includes/molecules/program/program_block.html" - ), - ), - ] - ) + try: + with transaction.atomic(): + # Copy this block manually here, as it has been changed in future migrations. + class ProgramGroupWithCandidatesBlock(StructBlock): + title = CharBlock( + label="Název programu", + help_text="Např. 'Krajské volby 2024', 'Evropské volby 2024', ...", + ) + + preamble_content = RichTextBlock( + label="Preambule", + help_text="Text, který se zobrazí před přepínačem mezi kandidáty a programem.", + required=False, + ) + + primary_candidates = CandidateListBlock( + label="Osoby na čele kandidátky", + help_text="Zobrazí se ve velkých blocích na začátku stránky.", + ) + + secondary_candidates = CandidateSecondaryListBlock( + label="Ostatní osoby na kandidátce", + help_text="Zobrazí se v kompaktním seznamu pod čelem kandidátky.", + ) + + program = StreamBlock( + [ + ("program_group", ProgramGroupBlock()), + ("program_group_crossroad", ProgramGroupBlockCrossroad()), + ("program_group_popout", ProgramGroupBlockPopout()), + ( + "carousel_program", + CarouselProgramBlock( + template="styleguide2/includes/molecules/program/program_block.html" + ), + ), + ] + ) - # Get the models - DistrictHomePage = apps.get_model("district", "DistrictHomePage") + # Get the models + DistrictHomePage = apps.get_model("district", "DistrictHomePage") - # Update content types, as they may not yet have been created. - # This is very ugly, but must be done. - create_contenttypes(base_apps.get_app_config("district")) + # Update content types, as they may not yet have been created. + # This is very ugly, but must be done. + create_contenttypes(base_apps.get_app_config("district")) - DistrictElectionRootPage = apps.get_model("district", "DistrictElectionRootPage") - DistrictElectionCampaignPage = apps.get_model( - "district", "DistrictElectionCampaignPage" - ) + DistrictElectionRootPage = apps.get_model("district", "DistrictElectionRootPage") + DistrictElectionCampaignPage = apps.get_model( + "district", "DistrictElectionCampaignPage" + ) - DistrictElectionProgramPage = apps.get_model( - "district", "DistrictElectionProgramPage" - ) - DistrictPostElectionStrategyPage = apps.get_model( - "district", "DistrictPostElectionStrategyPage" - ) + DistrictElectionProgramPage = apps.get_model( + "district", "DistrictElectionProgramPage" + ) + DistrictPostElectionStrategyPage = apps.get_model( + "district", "DistrictPostElectionStrategyPage" + ) - DistrictNewProgramPage = apps.get_model("district", "DistrictNewProgramPage") + DistrictNewProgramPage = apps.get_model("district", "DistrictNewProgramPage") - Locale = apps.get_model("wagtailcore", "Locale") + Locale = apps.get_model("wagtailcore", "Locale") - DistrictPersonPage = apps.get_model("district", "DistrictPersonPage") - DistrictPeoplePage = apps.get_model("district", "DistrictPeoplePage") + DistrictPersonPage = apps.get_model("district", "DistrictPersonPage") + DistrictPeoplePage = apps.get_model("district", "DistrictPeoplePage") - # Get the default locale - default_locale = Locale.objects.first() + # Get the default locale + default_locale = Locale.objects.first() - Page = apps.get_model("wagtailcore", "Page") + Page = apps.get_model("wagtailcore", "Page") - ContentType = apps.get_model("contenttypes", "ContentType") + ContentType = apps.get_model("contenttypes", "ContentType") - try: - root_page_content_type = ContentType.objects.get( - app_label="district", model="districtelectionrootpage" - ) - campaign_page_content_type = ContentType.objects.get( - app_label="district", model="districtelectioncampaignpage" - ) - - people_page_content_type = ContentType.objects.get( - app_label="district", model="districtpeoplepage" - ) - - program_page_content_type = ContentType.objects.get( - app_label="district", model="districtelectionprogrampage" - ) - program_post_election_strategy_content_type = ContentType.objects.get( - app_label="district", model="districtpostelectionstrategypage" - ) - - new_program_page_content_type = ContentType.objects.get( - app_label="district", model="districtnewprogrampage" - ) - except ContentType.DoesNotExist: - # This is a fresh instance and old models don't need to be migrated. - # Not beautiful, but shouldn't cause any trouble. - return - - # Function to get children of a certain type - def get_children_of_type(model, parent_page, content_type): - return model.objects.filter( - path__startswith=parent_page.path, - depth=parent_page.depth + 1, - content_type=content_type, - ) - - for home_page in DistrictHomePage.objects.all(): - # Create a new program page, even if this DistrictHomePage will - # end up having no programs. - - max_path_page = ( - Page.objects.filter( - path__startswith=home_page.path, depth=home_page.depth + 1 - ) - .order_by("-path") - .first() - ) - - if max_path_page: - max_path = max_path_page.path - next_path_suffix = ( - int(max_path[-4:], 36) + 1 - ) # Base-36 to handle alphanumeric paths - next_path = ( - max_path[:-4] + f"{next_path_suffix:04X}" - ) # Convert back to base-36 - else: - next_path = home_page.path + "0001" - - # Create the new program page - with transaction.atomic(): - new_program_page = DistrictNewProgramPage( - title="Programy", - slug="programy", # Make sure the slug is unique among siblings - path=next_path, - depth=home_page.depth + 1, - numchild=0, - content_type_id=new_program_page_content_type.id, - locale_id=default_locale.id, - ) - new_program_page.save() - - # Update numchild on the home page - home_page.numchild = home_page.numchild + 1 - home_page.save(update_fields=["numchild"]) - - # Iterate over all child DistrictElectionRootPage instances - for election_root_page in get_children_of_type( - DistrictElectionRootPage, home_page, root_page_content_type - ): - # Get the children of type DistrictElectionCampaignPage - campaign_pages = get_children_of_type( - DistrictElectionCampaignPage, - election_root_page, - campaign_page_content_type, - ) + try: + root_page_content_type = ContentType.objects.get( + app_label="district", model="districtelectionrootpage" + ) + campaign_page_content_type = ContentType.objects.get( + app_label="district", model="districtelectioncampaignpage" + ) - for campaign_page in campaign_pages: - # Data for a single program - - program_data = { - # Title - "title": "", # Done - "order": 0, - # Post-election - "post_election_strategies": [], - # Candidates - "candidate_list_number": "", - "candidate_list_title": "", # Done - "candidate_pages": [], # Done - "candidate_blocks": [], # Done - "combined_candidates": [], # Done - "primary_candidate_count": 0, # Done - # Program - "program_title": "", - "program_points": [], - "program_is_inline": False, - "program_content_before": "", # Done - # Misc. - "funding_info": "", - } - - program_data["title"] = ( - election_root_page.title - if election_root_page.title - else campaign_page.title + people_page_content_type = ContentType.objects.get( + app_label="district", model="districtpeoplepage" ) - program_data["order"] = campaign_page.order + program_page_content_type = ContentType.objects.get( + app_label="district", model="districtelectionprogrampage" + ) + program_post_election_strategy_content_type = ContentType.objects.get( + app_label="district", model="districtpostelectionstrategypage" + ) - program_data["candidate_list_number"] = campaign_page.number - program_data["candidate_list_title"] = ( - campaign_page.candidate_list_title - if campaign_page.candidate_list_title - else campaign_page.title + new_program_page_content_type = ContentType.objects.get( + app_label="district", model="districtnewprogrampage" + ) + except ContentType.DoesNotExist: + # This is a fresh instance and old models don't need to be migrated. + # Not beautiful, but shouldn't cause any trouble. + return + + # Function to get children of a certain type + def get_children_of_type(model, parent_page, content_type): + return model.objects.filter( + path__startswith=parent_page.path, + depth=parent_page.depth + 1, + content_type=content_type, ) - program_data["program_title"] = ( - campaign_page.program_point_list_title - if campaign_page.program_point_list_title - else campaign_page.title + for home_page in DistrictHomePage.objects.all(): + # Create a new program page, even if this DistrictHomePage will + # end up having no programs. + + max_path_page = ( + Page.objects.filter( + path__startswith=home_page.path, depth=home_page.depth + 1 + ) + .order_by("-path") + .first() ) - program_data[ - "program_is_inline" - ] = campaign_page.show_program_points_inline + if max_path_page: + max_path = max_path_page.path + next_path_suffix = ( + int(max_path[-4:], 36) + 1 + ) # Base-36 to handle alphanumeric paths + next_path = ( + max_path[:-4] + f"{next_path_suffix:04X}" + ) # Convert back to base-36 + else: + next_path = home_page.path + "0001" + + # Create the new program page + with transaction.atomic(): + new_program_page = DistrictNewProgramPage( + title="Programy", + slug="programy", # Make sure the slug is unique among siblings + path=next_path, + depth=home_page.depth + 1, + numchild=0, + content_type_id=new_program_page_content_type.id, + locale_id=default_locale.id, + ) + new_program_page.save() - program_data["funding_info"] = campaign_page.campaign_funding_info + # Update numchild on the home page + home_page.numchild = home_page.numchild + 1 + home_page.save(update_fields=["numchild"]) - position = 0 + # Iterate over all child DistrictElectionRootPage instances + for election_root_page in get_children_of_type( + DistrictElectionRootPage, home_page, root_page_content_type + ): + # Get the children of type DistrictElectionCampaignPage + campaign_pages = get_children_of_type( + DistrictElectionCampaignPage, + election_root_page, + campaign_page_content_type, + ) - # Parse candidates from blocks - for candidate_list_block in campaign_page.candidates.get_prep_value(): - candidate_list = candidate_list_block["value"] + for campaign_page in campaign_pages: + # Data for a single program + + program_data = { + # Title + "title": "", # Done + "order": 0, + # Post-election + "post_election_strategies": [], + # Candidates + "candidate_list_number": "", + "candidate_list_title": "", # Done + "candidate_pages": [], # Done + "candidate_blocks": [], # Done + "combined_candidates": [], # Done + "primary_candidate_count": 0, # Done + # Program + "program_title": "", + "program_points": [], + "program_is_inline": False, + "program_content_before": "", # Done + # Misc. + "funding_info": "", + } - program_data["primary_candidate_count"] = candidate_list[ - "candidate_list_big_count" - ] + program_data["title"] = ( + election_root_page.title + if election_root_page.title + else campaign_page.title + ) - for candidate in candidate_list["candidate_list"]: - if candidate["type"] == "person_page": - if ( - DistrictPersonPage.objects.filter( - id=candidate["value"] - ).first() - is None - ): - # We have nothing to do here, this page ID is either unfilled or points to a missing page - continue - - position = position + 1 - - candidate_data = { - "position": position, - "page": DistrictPersonPage.objects.filter( - id=candidate["value"] - ).first(), - } + program_data["order"] = campaign_page.order - program_data["candidate_pages"].append(candidate_data) - program_data["combined_candidates"].append(candidate_data) + program_data["candidate_list_number"] = campaign_page.number + program_data["candidate_list_title"] = ( + campaign_page.candidate_list_title + if campaign_page.candidate_list_title + else campaign_page.title + ) - continue + program_data["program_title"] = ( + campaign_page.program_point_list_title + if campaign_page.program_point_list_title + else campaign_page.title + ) - elif candidate["type"] == "person_blocks": - position = position + 1 + program_data[ + "program_is_inline" + ] = campaign_page.show_program_points_inline - program_data["candidate_blocks"].append( - {"position": position, "data": candidate["value"]} - ) + program_data["funding_info"] = campaign_page.campaign_funding_info - # Parse program points - program_pages = get_children_of_type( - DistrictElectionProgramPage, - campaign_page, - program_page_content_type, - ) + position = 0 - for program_page in program_pages: - program_data["program_points"].append( - { - "title": program_page.title, # Done - "guarantor_page": program_page.guarantor, # Done - "image": program_page.image, # Ignoring - "perex": program_page.perex, # Done - "content": program_page.content, # Done - "funding_info": program_page.campaign_funding_info, # TODO: Ignoring? - } - ) + # Parse candidates from blocks + for candidate_list_block in campaign_page.candidates.get_prep_value(): + candidate_list = candidate_list_block["value"] - # Parse post-election strategies - post_election_strategy_pages = get_children_of_type( - DistrictPostElectionStrategyPage, - election_root_page, - program_post_election_strategy_content_type, - ) + program_data["primary_candidate_count"] = candidate_list[ + "candidate_list_big_count" + ] - for post_election_strategy_page in post_election_strategy_pages: - program_data["post_election_strategies"].append( - { - "perex": post_election_strategy_page.perex, - } - ) + for candidate in candidate_list["candidate_list"]: + if candidate["type"] == "person_page": + if ( + DistrictPersonPage.objects.filter( + id=candidate["value"] + ).first() + is None + ): + # We have nothing to do here, this page ID is either unfilled or points to a missing page + continue - # If there's no program data, skip this iteration. - if ( - len(program_data["candidate_pages"]) == 0 - and len(program_data["candidate_blocks"]) == 0 - and len(program_data["program_points"]) == 0 - ): - continue # Do nothing for these + position = position + 1 - parent_people_page = get_children_of_type( - DistrictPeoplePage, home_page, people_page_content_type - ) + candidate_data = { + "position": position, + "page": DistrictPersonPage.objects.filter( + id=candidate["value"] + ).first(), + } - parent_people_page = parent_people_page.first() + program_data["candidate_pages"].append(candidate_data) + program_data["combined_candidates"].append(candidate_data) - # Create corresponding pages for candidate blocks with no pages - for candidate_block in program_data["candidate_blocks"]: - if parent_people_page is None: - break # We can't do anything here + continue - social_links = [] + elif candidate["type"] == "person_blocks": + position = position + 1 - if candidate_block["data"]["facebook_url"]: - social_links.append( - SocialLinkBlock().to_python( - { - "icon": "ico--facebook", - "text": "Facebook", - "link": candidate_block["data"]["facebook_url"], - } - ) + program_data["candidate_blocks"].append( + {"position": position, "data": candidate["value"]} + ) + + # Parse program points + program_pages = get_children_of_type( + DistrictElectionProgramPage, + campaign_page, + program_page_content_type, ) - if candidate_block["data"]["instagram_url"]: - social_links.append( - SocialLinkBlock().to_python( + for program_page in program_pages: + program_data["program_points"].append( { - "icon": "ico--instagram", - "text": "Instagram", - "link": candidate_block["data"]["instagram_url"], + "title": program_page.title, # Done + "guarantor_page": program_page.guarantor, # Done + "image": program_page.image, # Ignoring + "perex": program_page.perex, # Done + "content": program_page.content, # Done + "funding_info": program_page.campaign_funding_info, # TODO: Ignoring? } ) + + # Parse post-election strategies + post_election_strategy_pages = get_children_of_type( + DistrictPostElectionStrategyPage, + election_root_page, + program_post_election_strategy_content_type, ) - if candidate_block["data"]["twitter_url"]: - social_links.append( - SocialLinkBlock().to_python( + for post_election_strategy_page in post_election_strategy_pages: + program_data["post_election_strategies"].append( { - "icon": "ico--twitter", - "text": "Twitter", - "link": candidate_block["data"]["twitter_url"], + "perex": post_election_strategy_page.perex, } ) + + # If there's no program data, skip this iteration. + if ( + len(program_data["candidate_pages"]) == 0 + and len(program_data["candidate_blocks"]) == 0 + and len(program_data["program_points"]) == 0 + ): + continue # Do nothing for these + + parent_people_page = get_children_of_type( + DistrictPeoplePage, home_page, people_page_content_type ) - if candidate_block["data"]["youtube_url"]: - social_links.append( - SocialLinkBlock().to_python( - { - "icon": "ico--youtube", - "text": "YouTube", - "link": candidate_block["data"]["youtube_url"], - } + parent_people_page = parent_people_page.first() + + # Create corresponding pages for candidate blocks with no pages + for candidate_block in program_data["candidate_blocks"]: + if parent_people_page is None: + break # We can't do anything here + + social_links = [] + + if candidate_block["data"]["facebook_url"]: + social_links.append( + SocialLinkBlock().to_python( + { + "icon": "ico--facebook", + "text": "Facebook", + "link": candidate_block["data"]["facebook_url"], + } + ) + ) + + if candidate_block["data"]["instagram_url"]: + social_links.append( + SocialLinkBlock().to_python( + { + "icon": "ico--instagram", + "text": "Instagram", + "link": candidate_block["data"]["instagram_url"], + } + ) + ) + + if candidate_block["data"]["twitter_url"]: + social_links.append( + SocialLinkBlock().to_python( + { + "icon": "ico--twitter", + "text": "Twitter", + "link": candidate_block["data"]["twitter_url"], + } + ) + ) + + if candidate_block["data"]["youtube_url"]: + social_links.append( + SocialLinkBlock().to_python( + { + "icon": "ico--youtube", + "text": "YouTube", + "link": candidate_block["data"]["youtube_url"], + } + ) + ) + + if candidate_block["data"]["flickr_url"]: + social_links.append( + SocialLinkBlock().to_python( + { + "icon": "ico--flickr", + "text": "Flickr", + "link": candidate_block["data"]["flickr_url"], + } + ) + ) + + max_path_page = ( + Page.objects.filter( + path__startswith=people_page.path, + depth=parent_people_page.depth + 1, + ) + .order_by("-path") + .first() ) - ) - if candidate_block["data"]["flickr_url"]: - social_links.append( - SocialLinkBlock().to_python( + if max_path_page: + max_path = max_path_page.path + next_path_suffix = ( + int(max_path[-4:], 36) + 1 + ) # Base-36 to handle alphanumeric paths + next_path = ( + max_path[:-4] + f"{next_path_suffix:04X}" + ) # Convert back to base-36 + else: + next_path = parent_people_page.path + "0001" + + with transaction.atomic(): + candidate_page = DistrictPersonPage( + title=candidate_block["data"]["title"], + job=candidate_block["data"]["job"], + profile_image_id=candidate_block["data"]["profile_photo"], + email=candidate_block["data"]["email"], + city=candidate_block["data"]["city"], + age=candidate_block["data"]["age"], + is_pirate=candidate_block["data"]["is_pirate"], + other_party=candidate_block["data"]["other_party"], + other_party_logo_id=candidate_block["data"][ + "other_party_logo" + ], + social_links=social_links, + content_type_id=people_page_content_type.id, + locale_id=default_locale.id, + ) + + candidate_page.numchild = candidate_page.numchild + 1 + candidate_page.save(update_fields=["numchild"]) + + program_data["combined_candidates"].append( { - "icon": "ico--flickr", - "text": "Flickr", - "link": candidate_block["data"]["flickr_url"], + "position": candidate_block["position"], + "page": candidate_page, } ) - ) - max_path_page = ( - Page.objects.filter( - path__startswith=people_page.path, - depth=parent_people_page.depth + 1, + # Sort candidates + program_data["combined_candidates"] = sorted( + program_data["combined_candidates"], + key=lambda value: value["position"], ) - .order_by("-path") - .first() - ) - if max_path_page: - max_path = max_path_page.path - next_path_suffix = ( - int(max_path[-4:], 36) + 1 - ) # Base-36 to handle alphanumeric paths - next_path = ( - max_path[:-4] + f"{next_path_suffix:04X}" - ) # Convert back to base-36 - else: - next_path = parent_people_page.path + "0001" - - with transaction.atomic(): - candidate_page = DistrictPersonPage( - title=candidate_block["data"]["title"], - job=candidate_block["data"]["job"], - profile_image_id=candidate_block["data"]["profile_photo"], - email=candidate_block["data"]["email"], - city=candidate_block["data"]["city"], - age=candidate_block["data"]["age"], - is_pirate=candidate_block["data"]["is_pirate"], - other_party=candidate_block["data"]["other_party"], - other_party_logo_id=candidate_block["data"][ - "other_party_logo" - ], - social_links=social_links, - content_type_id=people_page_content_type.id, - locale_id=default_locale.id, - ) + # Create candidate blocks + primary_candidates = [] + secondary_candidates = [] + + for position, candidate in enumerate( + program_data["combined_candidates"] + ): + if position <= program_data["primary_candidate_count"]: + primary_candidates.append( + { # CandidateBlock value for CandidateListBlock + "page": candidate["page"].id, + "description": ( + candidate["page"].perex + if len(candidate["page"].perex) > 32 + else ( + candidate["page"].job + if candidate["page"].job + else candidate["page"].position + ) + ), + "image": ( + candidate["page"].profile_image.id + # Not sure why there is a need to check for the second condition. + # But, without it, the migration crashes on some images. + if candidate["page"].profile_image is not None + else 0 + ), + } + ) + else: + secondary_candidates.append( + { # SecondaryCandidateBlock value for CandidateSecondaryListBlock + "number": candidate["position"], + "page": candidate["page"].id, + } + ) - candidate_page.numchild = candidate_page.numchild + 1 - candidate_page.save(update_fields=["numchild"]) + primary_candidates_block = { + # Acts as CandidateListBlock + "candidates": primary_candidates, + } - program_data["combined_candidates"].append( - { - "position": candidate_block["position"], - "page": candidate_page, + secondary_candidates_block = { + # Acts as CandidateSecondaryListBlock + "heading": "Další kandidáti", + "candidates": secondary_candidates, } - ) - # Sort candidates - program_data["combined_candidates"] = sorted( - program_data["combined_candidates"], - key=lambda value: value["position"], - ) + # if program_data["program_is_inline"]: - # Create candidate blocks - primary_candidates = [] - secondary_candidates = [] + # There's no time left to set this up properly. We'll just use + # inline points for everything, at least for now. - for position, candidate in enumerate( - program_data["combined_candidates"] - ): - if position <= program_data["primary_candidate_count"]: - primary_candidates.append( - { # CandidateBlock value for CandidateListBlock - "page": candidate["page"].id, - "description": ( - candidate["page"].perex - if len(candidate["page"].perex) > 32 - else ( - candidate["page"].job - if candidate["page"].job - else candidate["page"].position - ) - ), - "image": ( - candidate["page"].profile_image.id - # Not sure why there is a need to check for the second condition. - # But, without it, the migration crashes on some images. - if candidate["page"].profile_image is not None - else 0 - ), + # Create program point blocks + program_points = [] + + for program_point in program_data["program_points"]: + program_points.append( + { # Act as ProgramBlockPopout + "title": program_point["title"], + "content": str(program_point["content"]), + "guarantor": ( + program_point["guarantor_page"].id + if program_point["guarantor_page"] + else None + ), + } + ) + + program_categories = [ + { # Act as ProgramPopoutCategory + "name": program_data["candidate_list_title"], + "point_list": program_points, } - ) - else: - secondary_candidates.append( - { # SecondaryCandidateBlock value for CandidateSecondaryListBlock - "number": candidate["position"], - "page": candidate["page"].id, + ] + + program_block = { + "type": "program_group_popout", + "value": { + "title": program_data["program_title"], + "categories": program_categories, + }, + } + + # Finally, create a block for this program + new_program_block = ProgramGroupWithCandidatesBlock().to_python( + { + "title": program_data["title"], + "preamble_content": program_data["program_content_before"], + "primary_candidates": primary_candidates_block, + "secondary_candidates": secondary_candidates_block, + "program": [program_block], } ) - primary_candidates_block = { - # Acts as CandidateListBlock - "candidates": primary_candidates, - } - - secondary_candidates_block = { - # Acts as CandidateSecondaryListBlock - "heading": "Další kandidáti", - "candidates": secondary_candidates, - } - - # if program_data["program_is_inline"]: - - # There's no time left to set this up properly. We'll just use - # inline points for everything, at least for now. - - # Create program point blocks - program_points = [] - - for program_point in program_data["program_points"]: - program_points.append( - { # Act as ProgramBlockPopout - "title": program_point["title"], - "content": str(program_point["content"]), - "guarantor": ( - program_point["guarantor_page"].id - if program_point["guarantor_page"] - else None - ), - } - ) - - program_categories = [ - { # Act as ProgramPopoutCategory - "name": program_data["candidate_list_title"], - "point_list": program_points, - } - ] - - program_block = { - "type": "program_group_popout", - "value": { - "title": program_data["program_title"], - "categories": program_categories, - }, - } - - # Finally, create a block for this program - new_program_block = ProgramGroupWithCandidatesBlock().to_python( - { - "title": program_data["title"], - "preamble_content": program_data["program_content_before"], - "primary_candidates": primary_candidates_block, - "secondary_candidates": secondary_candidates_block, - "program": [program_block], - } - ) + new_program_page.program.append( + ("program_group_with_candidates", new_program_block) + ) - new_program_page.program.append( - ("program_group_with_candidates", new_program_block) - ) - new_program_page.save() + new_program_page.save() + except Exception as e: + print(f"Ignoring exception during program page migration: {e}") + + pass class Migration(migrations.Migration):