From ae29bd6b19a19cae984696850dba557b344f21b3 Mon Sep 17 00:00:00 2001 From: Arne Schauf Date: Wed, 30 Apr 2025 16:42:31 +0200 Subject: [PATCH] Refactor slides to use StreamField in EventPage and HomePage Replaced legacy slide fields with a flexible StreamField approach for EventPage and HomePage models. Includes migrations for data transfer, template updates to support the new StreamField, and deprecation of legacy fields for backward compatibility. --- README.md | 24 +++ .../0016_refactor_eventpage_slides.py | 141 ++++++++++++++++++ core/migrations/0017_homepage_slides.py | 79 ++++++++++ core/models.py | 109 ++++++-------- core/templates/core/event_page.html | 94 ++++-------- core/templates/core/home_page.html | 40 ++++- feo_homepage/settings/prod.py | 15 +- 7 files changed, 372 insertions(+), 130 deletions(-) create mode 100644 README.md create mode 100644 core/migrations/0016_refactor_eventpage_slides.py create mode 100644 core/migrations/0017_homepage_slides.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6d0322 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# HomePage Slides Refactoring + +This change refactors the slides on the HomePage to use a StreamField instead of individual fields for each slide. This makes the slides more flexible and allows for an arbitrary number of slides. + +## Changes Made + +1. Added a new `slides` StreamField to the HomePage model +2. Updated the HomePage content panels to include the new StreamField +3. Updated the home_page.html template to render the new StreamField slides +4. Created a migration for the changes to the HomePage model + +## Data Migration + +The existing slide fields are still available for backward compatibility, but they are marked as deprecated. The template will first check for the new StreamField slides, and fall back to the legacy fields if the StreamField is empty. + +A data migration has been integrated into the Django migration system to automatically transfer existing data from the legacy slide fields to the new StreamField. When you run the migrations, the system will: + +1. Add the new `slides` StreamField to the HomePage model +2. Automatically migrate any existing data from the legacy slide fields to the new StreamField +3. Preserve the legacy fields for backward compatibility + +This means you don't need to manually migrate the data - it's handled automatically by the migration system. + +After confirming that all data has been successfully migrated and that the new StreamField is working correctly in production, you can remove the legacy fields from the HomePage model in a future update. diff --git a/core/migrations/0016_refactor_eventpage_slides.py b/core/migrations/0016_refactor_eventpage_slides.py new file mode 100644 index 0000000..7b0f8fc --- /dev/null +++ b/core/migrations/0016_refactor_eventpage_slides.py @@ -0,0 +1,141 @@ +# Generated by Django 5.2 on 2025-05-01 12:00 + +import json +from django.db import migrations, models +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +def migrate_slides_to_streamfield(apps, schema_editor): + EventPage = apps.get_model('core', 'EventPage') + for page in EventPage.objects.all(): + slides = [] + + # Check if slide1_img exists and add it to the slides list + if page.slide1_img_id: + slides.append({ + 'type': 'slide', + 'value': { + 'image': page.slide1_img_id, + 'headline': page.slide1_headline, + 'subline': page.slide1_subline, + 'link_url': page.slide1_link_url, + 'link_text': page.slide1_link_text, + }, + 'id': '1' + }) + + # Check if slide2_img exists and add it to the slides list + if page.slide2_img_id: + slides.append({ + 'type': 'slide', + 'value': { + 'image': page.slide2_img_id, + 'headline': page.slide2_headline, + 'subline': page.slide2_subline, + 'link_url': page.slide2_link_url, + 'link_text': page.slide2_link_text, + }, + 'id': '2' + }) + + # Check if slide3_img exists and add it to the slides list + if page.slide3_img_id: + slides.append({ + 'type': 'slide', + 'value': { + 'image': page.slide3_img_id, + 'headline': page.slide3_headline, + 'subline': page.slide3_subline, + 'link_url': page.slide3_link_url, + 'link_text': page.slide3_link_text, + }, + 'id': '3' + }) + + # Save the slides to the new StreamField + if slides: + page.slides = json.dumps(slides) + page.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0015_remove_eventpage_external_registration_code_and_more'), + ] + + operations = [ + # Add the new slides StreamField + migrations.AddField( + model_name='eventpage', + name='slides', + field=wagtail.fields.StreamField([('slide', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('headline', wagtail.blocks.CharBlock(max_length=512, required=False)), ('subline', wagtail.blocks.CharBlock(max_length=512, required=False)), ('link_url', wagtail.blocks.URLBlock(required=False)), ('link_text', wagtail.blocks.CharBlock(max_length=64, required=False))]))], blank=True, null=True, use_json_field=True), + ), + + # Run the data migration + migrations.RunPython(migrate_slides_to_streamfield), + + # Remove the old slide fields + migrations.RemoveField( + model_name='eventpage', + name='slide1_img', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide1_headline', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide1_subline', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide1_link_url', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide1_link_text', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide2_img', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide2_headline', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide2_subline', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide2_link_url', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide2_link_text', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide3_img', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide3_headline', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide3_subline', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide3_link_url', + ), + migrations.RemoveField( + model_name='eventpage', + name='slide3_link_text', + ), + ] diff --git a/core/migrations/0017_homepage_slides.py b/core/migrations/0017_homepage_slides.py new file mode 100644 index 0000000..0d3c706 --- /dev/null +++ b/core/migrations/0017_homepage_slides.py @@ -0,0 +1,79 @@ +# Generated by Django 4.2.1 on 2023-11-15 12:00 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +def migrate_legacy_slides_to_streamfield(apps, schema_editor): + """ + Migrate data from legacy slide fields to the new StreamField. + """ + HomePage = apps.get_model('core', 'HomePage') + + # Get all HomePage instances + homepages = HomePage.objects.all() + + # For each HomePage, migrate the legacy slides to the new StreamField + for homepage in homepages: + slides = [] + + # Check if slide1 exists + if homepage.slide1_img: + slides.append({ + 'type': 'slide', + 'value': { + 'image': homepage.slide1_img.id, + 'headline': homepage.slide1_headline, + 'subline': homepage.slide1_subline, + 'link_url': homepage.slide1_link_url, + 'link_text': homepage.slide1_link_text, + } + }) + + # Check if slide2 exists + if homepage.slide2_img: + slides.append({ + 'type': 'slide', + 'value': { + 'image': homepage.slide2_img.id, + 'headline': homepage.slide2_headline, + 'subline': homepage.slide2_subline, + 'link_url': homepage.slide2_link_url, + 'link_text': homepage.slide2_link_text, + } + }) + + # Check if slide3 exists + if homepage.slide3_img: + slides.append({ + 'type': 'slide', + 'value': { + 'image': homepage.slide3_img.id, + 'headline': homepage.slide3_headline, + 'subline': homepage.slide3_subline, + 'link_url': homepage.slide3_link_url, + 'link_text': homepage.slide3_link_text, + } + }) + + # Set the new slides StreamField + homepage.slides = slides + homepage.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0016_refactor_eventpage_slides'), + ] + + operations = [ + migrations.AddField( + model_name='homepage', + name='slides', + field=wagtail.fields.StreamField([('slide', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('headline', wagtail.blocks.CharBlock(max_length=512, required=False)), ('subline', wagtail.blocks.CharBlock(max_length=512, required=False)), ('link_url', wagtail.blocks.URLBlock(required=False)), ('link_text', wagtail.blocks.CharBlock(max_length=64, required=False))]))], blank=True, null=True, use_json_field=True), + ), + migrations.RunPython(migrate_legacy_slides_to_streamfield, migrations.RunPython.noop), + ] diff --git a/core/models.py b/core/models.py index 729eaa2..bcb39af 100644 --- a/core/models.py +++ b/core/models.py @@ -63,7 +63,18 @@ class MediaBlock(AbstractMediaChooserBlock): class HomePage(Page): block1 = RichTextField(blank=True) - # revolution slider + # New StreamField for slides + slides = StreamField([ + ('slide', blocks.StructBlock([ + ('image', ImageChooserBlock()), + ('headline', blocks.CharBlock(max_length=512, required=False)), + ('subline', blocks.CharBlock(max_length=512, required=False)), + ('link_url', blocks.URLBlock(required=False)), + ('link_text', blocks.CharBlock(max_length=64, required=False)), + ])), + ], null=True, blank=True, use_json_field=True) + + # Legacy fields for backward compatibility - deprecated slide1_img = models.ForeignKey( get_image_model(), null=True, @@ -153,31 +164,6 @@ class HomePage(Page): thumbnail3_text = RichTextField(blank=True) -REVOLUTION_SLIDER_FIELDS = [ - MultiFieldPanel([ - FieldPanel('slide1_img'), - FieldPanel('slide1_headline'), - FieldPanel('slide1_subline'), - FieldPanel('slide1_link_url'), - FieldPanel('slide1_link_text'), - ], heading='Slide 1', classname="collapsible"), - - MultiFieldPanel([ - FieldPanel('slide2_img'), - FieldPanel('slide2_headline'), - FieldPanel('slide2_subline'), - FieldPanel('slide2_link_url'), - FieldPanel('slide2_link_text'), - ], heading='Slide 2', classname="collapsible"), - - MultiFieldPanel([ - FieldPanel('slide3_img'), - FieldPanel('slide3_headline'), - FieldPanel('slide3_subline'), - FieldPanel('slide3_link_url'), - FieldPanel('slide3_link_text'), - ], heading='Slide 3'), -] HOME_THUMBNAIL_FIELDS = [ MultiFieldPanel([ @@ -205,9 +191,32 @@ HOME_THUMBNAIL_FIELDS = [ HomePage.content_panels = [ FieldPanel('title', classname="full title"), - MultiFieldPanel(REVOLUTION_SLIDER_FIELDS, heading='Slider elements', classname="collapsible collapsed"), + FieldPanel('slides', heading='Slider elements'), FieldPanel('block1'), MultiFieldPanel(HOME_THUMBNAIL_FIELDS, heading='Thumbnail elements', classname="collapsible collapsed"), + MultiFieldPanel([ + MultiFieldPanel([ + FieldPanel('slide1_img'), + FieldPanel('slide1_headline'), + FieldPanel('slide1_subline'), + FieldPanel('slide1_link_url'), + FieldPanel('slide1_link_text'), + ], heading='Slide 1', classname="collapsible"), + MultiFieldPanel([ + FieldPanel('slide2_img'), + FieldPanel('slide2_headline'), + FieldPanel('slide2_subline'), + FieldPanel('slide2_link_url'), + FieldPanel('slide2_link_text'), + ], heading='Slide 2', classname="collapsible"), + MultiFieldPanel([ + FieldPanel('slide3_img'), + FieldPanel('slide3_headline'), + FieldPanel('slide3_subline'), + FieldPanel('slide3_link_url'), + FieldPanel('slide3_link_text'), + ], heading='Slide 3', classname="collapsible"), + ], heading='Legacy Slider elements (deprecated)', classname="collapsible collapsed"), ] @@ -269,40 +278,16 @@ class EventPage(Page): subtitle = models.CharField(max_length=512, null=True, blank=True) description = RichTextField(blank=True) - # revolution slider - slide1_img = models.ForeignKey( - get_image_model(), - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name='+' - ) - slide1_headline = models.CharField(max_length=512, blank=True) - slide1_subline = models.CharField(max_length=512, blank=True) - slide1_link_url = models.URLField(blank=True) - slide1_link_text = models.CharField(max_length=64, blank=True) - slide2_img = models.ForeignKey( - get_image_model(), - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name='+' - ) - slide2_headline = models.CharField(max_length=512, blank=True) - slide2_subline = models.CharField(max_length=512, blank=True) - slide2_link_url = models.URLField(blank=True) - slide2_link_text = models.CharField(max_length=64, blank=True) - slide3_img = models.ForeignKey( - get_image_model(), - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name='+' - ) - slide3_headline = models.CharField(max_length=512, blank=True) - slide3_subline = models.CharField(max_length=512, blank=True) - slide3_link_url = models.URLField(blank=True) - slide3_link_text = models.CharField(max_length=64, blank=True) + # revolution slider as StreamField + slides = StreamField([ + ('slide', blocks.StructBlock([ + ('image', ImageChooserBlock()), + ('headline', blocks.CharBlock(max_length=512, required=False)), + ('subline', blocks.CharBlock(max_length=512, required=False)), + ('link_url', blocks.URLBlock(required=False)), + ('link_text', blocks.CharBlock(max_length=64, required=False)), + ])), + ], null=True, blank=True, use_json_field=True) img1_img = models.ForeignKey( get_image_model(), @@ -424,10 +409,10 @@ class EventRegistrationField(AbstractFormField): EventPage.content_panels = [ - MultiFieldPanel(REVOLUTION_SLIDER_FIELDS, heading='Slider elements', classname="collapsible collapsed"), FieldPanel('title', classname="full title"), FieldPanel('subtitle'), FieldPanel('description'), + FieldPanel('slides', heading='Slider elements', classname="collapsible collapsed"), FieldPanel('img1_img'), FieldPanel('img1_caption'), FieldPanel('img2_img'), diff --git a/core/templates/core/event_page.html b/core/templates/core/event_page.html index b36e98b..161a180 100644 --- a/core/templates/core/event_page.html +++ b/core/templates/core/event_page.html @@ -23,77 +23,39 @@ {% block content %} - {% if self.slide1_img %} + {% if self.slides %}
+ + + +
+{% elif self.slide1_img %} +