diff --git a/community/migrations/0013_onlineworship_drupal_body_migrated_and_more.py b/community/migrations/0013_onlineworship_drupal_body_migrated_and_more.py new file mode 100644 index 000000000..4f2a2ab46 --- /dev/null +++ b/community/migrations/0013_onlineworship_drupal_body_migrated_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.1 on 2023-06-16 10:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("community", "0001_squashed_0012_alter_communitypage_body"), + ] + + operations = [ + migrations.AddField( + model_name="onlineworship", + name="drupal_body_migrated", + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name="onlineworship", + name="drupal_node_id", + field=models.IntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name="onlineworship", + name="drupal_path", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/community/migrations/0014_rename_drupal_path_onlineworship_drupal_url_path.py b/community/migrations/0014_rename_drupal_path_onlineworship_drupal_url_path.py new file mode 100644 index 000000000..f5ef269a2 --- /dev/null +++ b/community/migrations/0014_rename_drupal_path_onlineworship_drupal_url_path.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2023-06-16 11:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("community", "0013_onlineworship_drupal_body_migrated_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="onlineworship", + old_name="drupal_path", + new_name="drupal_url_path", + ), + ] diff --git a/community/migrations/0015_onlineworship_online_worship_day_and_more.py b/community/migrations/0015_onlineworship_online_worship_day_and_more.py new file mode 100644 index 000000000..284dc25a7 --- /dev/null +++ b/community/migrations/0015_onlineworship_online_worship_day_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.1 on 2023-06-16 11:36 + +from django.db import migrations, models +import timezone_field.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("community", "0014_rename_drupal_path_onlineworship_drupal_url_path"), + ] + + operations = [ + migrations.AddField( + model_name="onlineworship", + name="online_worship_day", + field=models.CharField( + blank=True, + choices=[ + ("Sunday", "Sunday"), + ("Monday", "Monday"), + ("Tuesday", "Tuesday"), + ("Wednesday", "Wednesday"), + ("Thursday", "Thursday"), + ("Friday", "Friday"), + ("Saturday", "Saturday"), + ], + max_length=255, + null=True, + ), + ), + migrations.AddField( + model_name="onlineworship", + name="online_worship_time", + field=models.TimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="onlineworship", + name="online_worship_timezone", + field=timezone_field.fields.TimeZoneField(blank=True, null=True), + ), + ] diff --git a/community/models.py b/community/models.py index 8b97cbddd..e7368e36f 100644 --- a/community/models.py +++ b/community/models.py @@ -6,6 +6,8 @@ from wagtail.models import Page from wagtail.search import index +from timezone_field import TimeZoneField # type: ignore + from blocks import blocks as wf_blocks @@ -47,6 +49,15 @@ class CommunityPage(Page): class OnlineWorship(Page): + class OnlineWorshipDayChoices(models.TextChoices): + SUNDAY = "Sunday", "Sunday" + MONDAY = "Monday", "Monday" + TUESDAY = "Tuesday", "Tuesday" + WEDNESDAY = "Wednesday", "Wednesday" + THURSDAY = "Thursday", "Thursday" + FRIDAY = "Friday", "Friday" + SATURDAY = "Saturday", "Saturday" + description = RichTextField(blank=True) hosted_by = models.ForeignKey( @@ -56,11 +67,27 @@ class OnlineWorship(Page): on_delete=models.SET_NULL, related_name="online_worship", ) - + # TODO: Define a custom, orderable model for this + # to allow for multiple times of worship. times_of_worship = RichTextField(blank=True) + online_worship_day = models.CharField( + max_length=255, + null=True, + blank=True, + choices=OnlineWorshipDayChoices.choices, + ) + online_worship_time = models.TimeField(null=True, blank=True) + online_worship_timezone = TimeZoneField( + null=True, + blank=True, + ) website = models.URLField(null=True, blank=True) + drupal_node_id = models.IntegerField(null=True, blank=True) + drupal_body_migrated = models.TextField(null=True, blank=True) + drupal_url_path = models.CharField(max_length=255, null=True, blank=True) + content_panels = Page.content_panels + [ FieldPanel("description"), PageChooserPanel("hosted_by", ["contact.Meeting", "contact.Organization"]), diff --git a/community/templates/community/online_worship.html b/community/templates/community/online_worship.html index a8a1a4d8d..610e615f7 100644 --- a/community/templates/community/online_worship.html +++ b/community/templates/community/online_worship.html @@ -6,6 +6,7 @@ {% block title %}{{ page.title }}{% endblock %} {% block content %} + Online Worship Directory

{{ page.title }}

{% if page.hosted_by %} diff --git a/community/templates/community/online_worship_index_page.html b/community/templates/community/online_worship_index_page.html index 8808aeb33..3ac88445c 100644 --- a/community/templates/community/online_worship_index_page.html +++ b/community/templates/community/online_worship_index_page.html @@ -11,47 +11,58 @@

{{ page.intro | richtext }} -
- {% for online_worship in self.get_children %} - {% if online_worship.specific.website %} - -
-

{{ online_worship.title }}

-
- - {% if online_worship.specific.description %} - - {{ online_worship.specific.description | richtext }} - - {% endif %} - - {% if online_worship.specific.times_of_worship %} - - Times of worship:  - {{ online_worship.specific.times_of_worship | richtext }} - - {% endif %} -
- {% else %} -
-
-

{{ online_worship.title }}

-
- - {% if online_worship.specific.description %} - - {{ online_worship.specific.description | richtext }} - - {% endif %} - - {% if online_worship.specific.times_of_worship %} - - Times of worship:  - {{ online_worship.specific.times_of_worship | richtext }} - - {% endif %} -
- {% endif %} - {% endfor %} -
+ {% for online_worship in self.get_children|dictsort:"title" %} +
+
+

{{ online_worship.title }}

+ + {% if online_worship.specific.times_of_worship %} +

+ Times of worship:  + {{ online_worship.specific.times_of_worship | richtext }} +

+ {% endif %} + + {% if online_worship.specific.hosted_by %} +

+ Hosted by:  + {{ online_worship.specific.hosted_by }} +

+ {% endif %} + + {% if online_worship.specific.online_worship_day %} +

+ Online worship day:  + {{ online_worship.specific.online_worship_day }} +

+ {% endif %} + + {% if online_worship.specific.online_worship_time %} +

+ Online worship time:  + {{ online_worship.specific.online_worship_time }} + {% if online_worship.specific.online_worship_timezone %} + ({{ online_worship.specific.online_worship_timezone }}) + {% endif %} +

+ {% endif %} + + {% if online_worship.specific.online_worship_url %} +

+ Online worship URL:  + {{ online_worship.specific.online_worship_url }} +

+ {% endif %} + + {% if online_worship.specific.online_worship_notes %} +

+ Online worship notes:  + {{ online_worship.specific.online_worship_notes }} +

+ {% endif %} + View details +
+
+ {% endfor %} + {% endblock %} diff --git a/content_migration/management/commands/import_online_worship.py b/content_migration/management/commands/import_online_worship.py new file mode 100644 index 000000000..595c58477 --- /dev/null +++ b/content_migration/management/commands/import_online_worship.py @@ -0,0 +1,16 @@ +"""Management command to import Online Worship content.""" +from django.core.management.base import BaseCommand + +from content_migration.management.import_online_worship_handler import ( + handle_import_online_worship, +) + + +class Command(BaseCommand): + """Import Online Worship content.""" + + help = "Import Online Worship content" + + def handle(self, *args: tuple, **options: dict) -> None: + """Import Online Worship content.""" + handle_import_online_worship() diff --git a/content_migration/management/import_online_worship_handler.py b/content_migration/management/import_online_worship_handler.py new file mode 100644 index 000000000..994c559f1 --- /dev/null +++ b/content_migration/management/import_online_worship_handler.py @@ -0,0 +1,134 @@ +from datetime import datetime +from tqdm import tqdm +from community.models import OnlineWorship, OnlineWorshipIndexPage +from content_migration.management.errors import ( + CouldNotFindMatchingContactError, + CouldNotParseAuthorIdError, +) +from content_migration.management.shared import ( + construct_import_file_path, + create_permanent_redirect, + get_existing_magazine_author_from_db, + parse_csv_file, +) + + +TIMEZONE_CONVERSIONS = { + "Pacific": "US/Pacific", + "Mountain": "US/Mountain", + "Central": "US/Central", + "Eastern": "US/Eastern", +} + + +def get_time_or_none(time_str: str) -> str | None: + """Take a time in 24 hour integer format and return a time ztring in HH:MM + format.""" + if time_str == "": + return None + + try: + time_obj = datetime.strptime(time_str, "%H%M").time() + except ValueError as error: + raise ValueError(f"Unexpected time: {time_str}") from error + + return time_obj.strftime("%H:%M") + + +def get_timezone_or_none(timezone: str) -> str | None: + """Ensure that the timezone is in the proper format.""" + if timezone == "": + return None + + try: + return TIMEZONE_CONVERSIONS[timezone] + except KeyError as error: + raise ValueError(f"Unexpected timezone: {timezone}") from error + + +def handle_import_online_worship_item( + item: dict, + index_page: OnlineWorshipIndexPage, +) -> OnlineWorship: + """Import a single online worship item from Drupal.""" + + # check if news item exists + online_worship_exists = OnlineWorship.objects.filter( + drupal_node_id=item["drupal_node_id"] + ).exists() + + title = item["title"] + description = item["body"] + hosted_by = get_existing_magazine_author_from_db( + drupal_author_id=item["magazine_author_id"] + ) + # times_of_worship = item["times_of_worship"] + website = item["online_worship_url"] + drupal_node_id = item["drupal_node_id"] + drupal_body_migrated = item["body"] + drupal_url_path = item["url_path"] + online_worship_day = item["online_worship_day"] + online_worship_time = get_time_or_none(item["online_worship_time"]) + online_worship_timezone = get_timezone_or_none(item["online_worship_timezone"]) + + if online_worship_exists: + online_worship_db = OnlineWorship.objects.get( + drupal_node_id=item["drupal_node_id"] + ) + online_worship_db.title = title + online_worship_db.description = description + online_worship_db.hosted_by = hosted_by + # online_worship_db.times_of_worship = item["times_of_worship"] + online_worship_db.website = website + online_worship_db.drupal_body_migrated = drupal_body_migrated + online_worship_db.drupal_url_path = drupal_url_path + online_worship_db.online_worship_day = online_worship_day + online_worship_db.online_worship_time = online_worship_time + online_worship_db.online_worship_timezone = online_worship_timezone + + online_worship_db.save() + else: + online_worship_db = OnlineWorship( + title=title, + description=description, + hosted_by=hosted_by, + # times_of_worship=times_of_worship, + website=website, + drupal_node_id=drupal_node_id, + drupal_body_migrated=drupal_body_migrated, + drupal_url_path=drupal_url_path, + online_worship_day=online_worship_day, + online_worship_time=online_worship_time, + online_worship_timezone=online_worship_timezone, + ) + + index_page.add_child(instance=online_worship_db) + + return online_worship_db + + +def handle_import_online_worship() -> None: + """Import news from Drupal.""" + online_worship_items_data = parse_csv_file( + construct_import_file_path(file_key="online_worship"), + ) + index_page = OnlineWorshipIndexPage.objects.get() + + for online_worship_item_data in tqdm( + online_worship_items_data, + desc="Online worship items", + ): + try: + online_worship_db: OnlineWorship = handle_import_online_worship_item( + item=online_worship_item_data, + index_page=index_page, + ) + except CouldNotFindMatchingContactError: + continue + except CouldNotParseAuthorIdError: + continue + + create_permanent_redirect( + redirect_path=online_worship_item_data["url_path"], + redirect_entity=online_worship_db, + ) diff --git a/content_migration/management/shared.py b/content_migration/management/shared.py index 5ad5a6371..79d44c584 100644 --- a/content_migration/management/shared.py +++ b/content_migration/management/shared.py @@ -152,7 +152,7 @@ def create_block(generic_block: GenericBlock) -> tuple[str, str | dict]: if generic_block.block_type == "rich_text": return ( generic_block.block_type, - RichText(generic_block.block_content), + RichText(generic_block.block_content), # type: ignore ) elif generic_block.block_type == "image": try: @@ -316,11 +316,12 @@ def create_media_embed_block(url: str) -> tuple[str, Embed]: return embed_block -# TODO: remove this function, +# TODO: refactor this function to make it +# less redundant with create_image_block def create_image_block_from_file_bytes( file_name: str, file_bytes: BytesIO, -) -> tuple[str, Image]: +) -> tuple[str, dict]: # create image image_file: ImageFile = ImageFile( file_bytes, @@ -350,7 +351,7 @@ def create_image_block_from_file_bytes( def extract_pullquotes(item: str) -> list[str]: """Get a list of all pullquote strings found within the item.""" - return re.findall(r"\[pullquote\](.+?)\[\/pullquote\]", item) + return re.findall(r"\[pullquote\](.+?)\[\/pullquote\]", item) # type: ignore def fetch_file_bytes(url: str) -> FileBytesWithMimeType: @@ -473,12 +474,16 @@ def get_existing_magazine_author_from_db( results = list(chain(person, meeting, organization)) if len(results) == 0: - raise CouldNotFindMatchingContactError() + error_message = f"Could not find matching author for magazine author ID: { int(drupal_author_id) }" # noqa: E501 + logger.error(error_message) + + raise CouldNotFindMatchingContactError(error_message) elif len(results) > 1: - logger.error( + error_message = ( f"Duplicate authors found for magazine author ID: { int(drupal_author_id) }" ) - raise DuplicateContactError() + logger.error(error_message) + raise DuplicateContactError(error_message) else: return results[0]