Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@


import django.db.models.deletion
import modelcluster.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("locations", "0008_alter_locationpage_body"),
]

operations = [
migrations.CreateModel(
name="LocationWeekDaySlot",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"sort_order",
models.IntegerField(blank=True, editable=False, null=True),
),
(
"day",
models.CharField(
choices=[
("MON", "Monday"),
("TUE", "Tuesday"),
("WED", "Wednesday"),
("THU", "Thursday"),
("FRI", "Friday"),
("SAT", "Saturday"),
("SUN", "Sunday"),
],
default="MON",
help_text="Select the day of the week",
max_length=3,
),
),
(
"location",
modelcluster.fields.ParentalKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="week_day_slots",
to="locations.locationpage",
),
),
],
options={
"verbose_name": "Week Day Slot",
"verbose_name_plural": "Week Day Slots",
"ordering": ["sort_order"],
},
),
migrations.CreateModel(
name="LocationHourSlot",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"sort_order",
models.IntegerField(blank=True, editable=False, null=True),
),
("opening_time", models.TimeField(blank=True, null=True)),
("closing_time", models.TimeField(blank=True, null=True)),
(
"closed",
models.BooleanField(
blank=True,
default=False,
help_text="Tick if location is closed during this time slot",
verbose_name="Closed?",
),
),
(
"week_day_slot",
modelcluster.fields.ParentalKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="hour_slots",
to="locations.locationweekdayslot",
),
),
],
options={
"verbose_name": "Hour Slot",
"verbose_name_plural": "Hour Slots",
"ordering": ["sort_order"],
},
),
]
97 changes: 96 additions & 1 deletion bakerydemo/locations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.core.validators import RegexValidator
from django.db import models
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.admin.panels import FieldPanel, InlinePanel
from wagtail.api import APIField
from wagtail.fields import StreamField
Expand Down Expand Up @@ -71,6 +72,90 @@ class LocationOperatingHours(Orderable, OperatingHours):
)


class LocationWeekDaySlot(ClusterableModel, Orderable):
"""
A model representing a week day slot for nested InlinePanel demonstration.
This is the parent level that contains multiple hour slots.
Demonstrates nested InlinePanel functionality for testing expand/collapse
and scroll issues (related to issue #13352).
"""

location = ParentalKey(
"LocationPage", related_name="week_day_slots", on_delete=models.CASCADE
)
day = models.CharField(
max_length=3,
choices=DAY_CHOICES,
default="MON",
help_text="Select the day of the week",
)

panels = [
FieldPanel("day"),
InlinePanel("hour_slots", heading="Hour Slots", label="Time Slot"),
]

api_fields = [
APIField("day"),
APIField("get_day_display"),
APIField("hour_slots"),
]

class Meta:
ordering = ["sort_order"]
verbose_name = "Week Day Slot"
verbose_name_plural = "Week Day Slots"

def __str__(self):
return f"{self.get_day_display()}"


class LocationHourSlot(Orderable):
"""
A model representing individual hour slots within a week day.
This is the nested/child level of the InlinePanel structure.
Multiple hour slots can exist per day (e.g., for split shifts).
"""

week_day_slot = ParentalKey(
"LocationWeekDaySlot", related_name="hour_slots", on_delete=models.CASCADE
)
opening_time = models.TimeField(blank=True, null=True)
closing_time = models.TimeField(blank=True, null=True)
closed = models.BooleanField(
"Closed?",
default=False,
blank=True,
help_text="Tick if location is closed during this time slot",
)

panels = [
FieldPanel("opening_time"),
FieldPanel("closing_time"),
FieldPanel("closed"),
]

api_fields = [
APIField("opening_time"),
APIField("closing_time"),
APIField("closed"),
]

class Meta:
ordering = ["sort_order"]
verbose_name = "Hour Slot"
verbose_name_plural = "Hour Slots"

def __str__(self):
if self.closed:
return "Closed"
if self.opening_time and self.closing_time:
opening = self.opening_time.strftime("%H:%M")
closing = self.closing_time.strftime("%H:%M")
return f"{opening} - {closing}"
return "Time not set"


class LocationsIndexPage(Page):
"""
A Page model that creates an index page (a listview)
Expand Down Expand Up @@ -161,7 +246,17 @@ class LocationPage(Page):
FieldPanel("body"),
FieldPanel("address"),
FieldPanel("lat_long"),
InlinePanel("hours_of_operation", heading="Hours of Operation", label="Slot"),
InlinePanel(
"hours_of_operation",
heading="Hours of Operation (Flat Structure)",
label="Slot",
),
InlinePanel(
"week_day_slots",
heading="Hours of Operation (Nested InlinePanel Demo)",
label="Day",
help_text="Demonstration of nested InlinePanel - each day can have multiple time slots",
),
]

api_fields = [
Expand Down
94 changes: 94 additions & 0 deletions bakerydemo/test_nested_inline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python
"""
Test script to verify the nested InlinePanel implementation
"""
import os
import django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bakerydemo.settings.dev")
django.setup()

from bakerydemo.locations.models import (
LocationPage,
LocationWeekDaySlot,
LocationHourSlot,
)

Comment on lines +13 to +16
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'LocationWeekDaySlot' is not used.
Import of 'LocationHourSlot' is not used.

Suggested change
LocationWeekDaySlot,
LocationHourSlot,
)
)

Copilot uses AI. Check for mistakes.
def test_nested_inline_panel():
"""Test the nested InlinePanel structure"""

print("=" * 80)
print("NESTED INLINEPANEL DEMONSTRATION")
print("=" * 80)

# Get a location page
locations = LocationPage.objects.all()

if not locations.exists():
print("\n❌ No location pages found in the database.")
print("Please visit the admin at http://localhost:8000/admin/ and create a location.")
return

location = locations.first()
print(f"\n📍 Testing with Location: {location.title}")
print(f" URL: http://localhost:8000/admin/pages/{location.id}/edit/")

# Check for week day slots
week_day_slots = location.week_day_slots.all()

if not week_day_slots.exists():
print("\n📝 No nested week day slots found yet.")
print(" Go to the admin interface and add some!")
print(f"\n Steps:")
print(f" 1. Visit: http://localhost:8000/admin/pages/{location.id}/edit/")
print(f" 2. Scroll to 'Hours of Operation (Nested InlinePanel Demo)'")
print(f" 3. Click '+ Day' to add a week day slot")
print(f" 4. Select a day (e.g., Monday)")
print(f" 5. Within that day, click '+ Time Slot' to add hour slots")
print(f" 6. Add multiple time slots (e.g., 9:00-12:00, 13:00-17:00)")
print(f" 7. Save the page")
else:
print(f"\n✅ Found {week_day_slots.count()} nested week day slot(s):")
print("-" * 80)

for day_slot in week_day_slots:
print(f"\n 📅 {day_slot.get_day_display()}")
hour_slots = day_slot.hour_slots.all()

if hour_slots.exists():
print(f" Time slots ({hour_slots.count()}):")
for hour_slot in hour_slots:
if hour_slot.closed:
print(f" 🚫 CLOSED")
else:
opening = hour_slot.opening_time.strftime("%H:%M") if hour_slot.opening_time else "--:--"
closing = hour_slot.closing_time.strftime("%H:%M") if hour_slot.closing_time else "--:--"
print(f" 🕒 {opening} - {closing}")
else:
print(f" (No hour slots defined)")

print("\n" + "-" * 80)
print("✅ Nested InlinePanel structure is working correctly!")

# Show comparison with flat structure
flat_hours = location.hours_of_operation.all()
if flat_hours.exists():
print(f"\n📊 For comparison, flat structure has {flat_hours.count()} slot(s)")

print("\n" + "=" * 80)
print("TESTING SUMMARY:")
print("=" * 80)
print("✓ Models created successfully")
print("✓ Database migrations applied")
print("✓ Nested InlinePanel structure available in admin")
print(f"✓ Admin interface: http://localhost:8000/admin/")
print(f"✓ Location edit: http://localhost:8000/admin/pages/{location.id}/edit/")
print("\n🎯 This demonstrates nested InlinePanel for testing:")
print(" • Expand/collapse functionality per day")
print(" • Multiple time slots within each day")
print(" • Scroll behavior with nested panels")
print(" • Related to Wagtail issue #13352")
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test script references "Wagtail issue #13352", but the PR description mentions "fixes #555". These issue numbers should be consistent. Please verify which issue number is correct and update accordingly.

Copilot uses AI. Check for mistakes.
print("=" * 80)

if __name__ == "__main__":
test_nested_inline_panel()