wagtail-cjkcms 24.2.7__py2.py3-none-any.whl → 25.1.6__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. cjkcms/.DS_Store +0 -0
  2. cjkcms/__init__.py +1 -1
  3. cjkcms/blocks/__init__.py +4 -0
  4. cjkcms/blocks/content/countdown.py +116 -0
  5. cjkcms/blocks/content_blocks.py +46 -9
  6. cjkcms/migrations/0018_layoutsettings_search_format.py +10 -5
  7. cjkcms/migrations/0019_layoutsettings_searchbox_input_class_and_more.py +28 -11
  8. cjkcms/migrations/0020_socialmediasettings_github_and_more.py +35 -0
  9. cjkcms/migrations/0021_remove_layoutsettings_navbar_color_scheme_and_more.py +85 -0
  10. cjkcms/migrations/0022_cjkcmspage_breadcrumb_label_and_more.py +23 -0
  11. cjkcms/migrations/0023_alter_navbar_language.py +18 -0
  12. cjkcms/models/admin_sidebar.py +0 -28
  13. cjkcms/models/page_models.py +33 -0
  14. cjkcms/models/snippet_models.py +22 -7
  15. cjkcms/models/wagtailsettings_models.py +61 -22
  16. cjkcms/settings.py +96 -64
  17. cjkcms/static/.DS_Store +0 -0
  18. cjkcms/static/cjkcms/css/cjkcms-custom-theme-disabled.css +73 -0
  19. cjkcms/static/cjkcms/css/cjkcms-front.css +41 -5
  20. cjkcms/static/cjkcms/js/cjkcms-front.js +2 -2
  21. cjkcms/static/vendor/.DS_Store +0 -0
  22. cjkcms/static/vendor/mdb/.DS_Store +0 -0
  23. cjkcms/static/vendor/mdb/css/.DS_Store +0 -0
  24. cjkcms/static/vendor/mdb/css/mdb.min.css +18 -0
  25. cjkcms/static/vendor/mdb/css/mdb.min.css.map +1 -0
  26. cjkcms/static/vendor/mdb/js/.DS_Store +0 -0
  27. cjkcms/static/vendor/mdb/js/mdb.umd.min.js +21 -0
  28. cjkcms/static/vendor/mdb/js/mdb.umd.min.js.map +1 -0
  29. cjkcms/static/vendor/simplycountdown/css/circle.css +73 -0
  30. cjkcms/static/vendor/simplycountdown/css/cyber.css +155 -0
  31. cjkcms/static/vendor/simplycountdown/css/dark.css +85 -0
  32. cjkcms/static/vendor/simplycountdown/css/light.css +85 -0
  33. cjkcms/static/vendor/simplycountdown/css/losange.css +83 -0
  34. cjkcms/static/vendor/simplycountdown/js/simplyCountdown.umd.js +2 -0
  35. cjkcms/static/vendor/simplycountdown/js/simplyCountdown.umd.js.map +1 -0
  36. cjkcms/templates/.DS_Store +0 -0
  37. cjkcms/templates/404.html +72 -104
  38. cjkcms/templates/cjkcms/.DS_Store +0 -0
  39. cjkcms/templates/cjkcms/blocks/base_link_block.html +48 -51
  40. cjkcms/templates/cjkcms/blocks/button_block.html +2 -2
  41. cjkcms/templates/cjkcms/blocks/card_landing1.html +2 -2
  42. cjkcms/templates/cjkcms/blocks/card_landing2.html +3 -3
  43. cjkcms/templates/cjkcms/blocks/countdown.html +24 -0
  44. cjkcms/templates/cjkcms/blocks/highlight_block.html +21 -0
  45. cjkcms/templates/cjkcms/blocks/pricelistitem_block.html +9 -5
  46. cjkcms/templates/cjkcms/pages/base.html +3 -3
  47. cjkcms/templates/cjkcms/pages/page.mini.html +1 -1
  48. cjkcms/templates/cjkcms/pages/search.html +10 -1
  49. cjkcms/templates/cjkcms/robots.txt +0 -4
  50. cjkcms/templates/cjkcms/snippets/breadcrumbs.html +2 -2
  51. cjkcms/templates/cjkcms/snippets/frontend_assets.html +9 -1
  52. cjkcms/templates/cjkcms/snippets/navbar.html +12 -9
  53. cjkcms/templates/cjkcms/snippets/navbar_search.html +6 -35
  54. cjkcms/templates/cjkcms/snippets/navbar_search_modal.html +34 -0
  55. cjkcms/templates/cjkcms/snippets/social_media_icons.html +20 -6
  56. cjkcms/templatetags/cjkcms_tags.py +80 -17
  57. cjkcms/tests/media/images/test_O3GLriA.original.png +0 -0
  58. cjkcms/tests/media/original_images/test_O3GLriA.png +0 -0
  59. cjkcms/tests/test_countdown_block.py +100 -0
  60. cjkcms/tests/test_search_blocks.py +10 -10
  61. cjkcms/tests/test_templatetags.py +19 -1
  62. cjkcms/tests/test_urls.py +8 -6
  63. cjkcms/urls.py +3 -0
  64. cjkcms/views.py +80 -78
  65. cjkcms/wagtail_hooks.py +37 -5
  66. {wagtail_cjkcms-24.2.7.dist-info → wagtail_cjkcms-25.1.6.dist-info}/METADATA +6 -6
  67. {wagtail_cjkcms-24.2.7.dist-info → wagtail_cjkcms-25.1.6.dist-info}/RECORD +71 -40
  68. {wagtail_cjkcms-24.2.7.dist-info → wagtail_cjkcms-25.1.6.dist-info}/WHEEL +1 -1
  69. {wagtail_cjkcms-24.2.7.dist-info → wagtail_cjkcms-25.1.6.dist-info}/LICENSE +0 -0
  70. {wagtail_cjkcms-24.2.7.dist-info → wagtail_cjkcms-25.1.6.dist-info}/entry_points.txt +0 -0
  71. {wagtail_cjkcms-24.2.7.dist-info → wagtail_cjkcms-25.1.6.dist-info}/top_level.txt +0 -0
cjkcms/.DS_Store ADDED
Binary file
cjkcms/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- VERSION = (24, 2, 7, "")
1
+ VERSION = (25, 1, 6, "")
2
2
 
3
3
  __version_info__ = VERSION
4
4
  __version__ = ".".join(map(str, VERSION[:3])) + (f"-{VERSION[3]}" if VERSION[3] else "")
cjkcms/blocks/__init__.py CHANGED
@@ -31,8 +31,10 @@ from .content_blocks import ( # noqa
31
31
  NavPageLinkWithSubLinkBlock,
32
32
  PriceListBlock,
33
33
  ReusableContentBlock,
34
+ HighlightBlock,
34
35
  )
35
36
  from .content.events import PublicEventBlock, EventCalendarBlock
37
+ from .content.countdown import CountdownBlock
36
38
  from .layout_blocks import CardGridBlock, GridBlock, HeroBlock
37
39
  from cjkcms.settings import cms_settings
38
40
 
@@ -75,6 +77,8 @@ CONTENT_STREAMBLOCKS = HTML_STREAMBLOCKS + [
75
77
  ("pricelist", PriceListBlock()),
76
78
  ("reusable_content", ReusableContentBlock()),
77
79
  ("event_calendar", EventCalendarBlock()),
80
+ ("highlight", HighlightBlock()),
81
+ ("countdown", CountdownBlock()),
78
82
  ]
79
83
 
80
84
  NAVIGATION_STREAMBLOCKS = [
@@ -0,0 +1,116 @@
1
+ from cjkcms.blocks.base_blocks import BaseBlock
2
+ from wagtail import blocks
3
+ from django.utils.translation import gettext_lazy as _
4
+ from datetime import datetime, timedelta, timezone
5
+ import re
6
+
7
+
8
+ def convert_to_utc(naive_dt, utc_offset_str):
9
+ # Parse the UTC offset string (e.g., "UTC+05:30" or "UTC-02:00")
10
+ match = re.match(r"UTC([+-])(\d{2}):(\d{2})", utc_offset_str)
11
+ if not match:
12
+ raise ValueError("Invalid UTC offset format. Use 'UTC±HH:MM'.")
13
+
14
+ sign = match.group(1)
15
+ hours = int(match.group(2))
16
+ minutes = int(match.group(3))
17
+
18
+ # Calculate the total offset in minutes
19
+ total_offset = timedelta(hours=hours, minutes=minutes)
20
+ if sign == "-":
21
+ total_offset = -total_offset
22
+
23
+ # Create a timezone with the given offset
24
+ tz = timezone(total_offset)
25
+
26
+ # Attach the offset to the naive datetime, making it timezone-aware
27
+ aware_local_dt = naive_dt.replace(tzinfo=tz)
28
+
29
+ # Convert the timezone-aware datetime to UTC
30
+ utc_dt = aware_local_dt.astimezone(timezone.utc)
31
+
32
+ return utc_dt
33
+
34
+
35
+ class CountdownBlock(BaseBlock):
36
+ """
37
+ Display a countdown to a specific date
38
+ """
39
+
40
+ title = blocks.CharBlock(
41
+ required=False,
42
+ max_length=255,
43
+ label=_("Title"),
44
+ )
45
+
46
+ start_date = blocks.DateTimeBlock(
47
+ required=True,
48
+ label=_("Start date and time"),
49
+ help_text=_("Format: YYYY-MM-DD HH:MM"),
50
+ )
51
+
52
+ timezone = blocks.CharBlock(
53
+ required=True,
54
+ default="UTC",
55
+ max_length=255,
56
+ label=_("Timezone"),
57
+ help_text=_("Timezone relative to UTC, e.g. UTC+01:00"),
58
+ )
59
+
60
+ url = blocks.URLBlock(
61
+ max_length=255,
62
+ label=_("Link"),
63
+ required=False,
64
+ help_text=_("Optional link"),
65
+ )
66
+
67
+ # hide_after_end_date = blocks.BooleanBlock(
68
+ # required=False,
69
+ # default=False,
70
+ # label=_("Hide after end date"),
71
+ # help_text=_("Hide this after end date, or show zeros if false"),
72
+ # )
73
+
74
+ theme = blocks.ChoiceBlock(
75
+ choices=[
76
+ ("light", _("Light")),
77
+ ("dark", _("Dark")),
78
+ ("cyber", _("Cyberpunk")),
79
+ ("losange", _("Losange")),
80
+ ("circle", _("Circle")),
81
+ # ("flipbook", _("Flipbook")),
82
+ ],
83
+ default="light",
84
+ label=_("Theme"),
85
+ )
86
+
87
+ def get_context(self, value, parent_context=None):
88
+ context = super().get_context(value, parent_context=parent_context)
89
+ sd = value.get("start_date")
90
+
91
+ naive_datetime = datetime(
92
+ sd.year, sd.month, sd.day, sd.hour, sd.minute
93
+ ) # Naive datetime
94
+
95
+ tz = value.get("timezone")
96
+ if tz == "UTC" or tz == "":
97
+ tz = "UTC+00:00"
98
+
99
+ utc_offset_string = tz # String containing UTC offset
100
+ utc = convert_to_utc(naive_datetime, utc_offset_string)
101
+
102
+ # print(naive_datetime, utc, utc_offset_string)
103
+ context["year"] = utc.year
104
+ context["month"] = utc.month
105
+ context["day"] = utc.day
106
+ context["hour"] = utc.hour
107
+ context["minute"] = utc.minute
108
+ context["second"] = 0
109
+ return context
110
+
111
+ class Meta:
112
+ template = "cjkcms/blocks/countdown.html"
113
+ icon = "history"
114
+ label = "Countdown"
115
+ ordering = ["start_date"]
116
+ label_format = _("Countdown to {start_date}")
@@ -2,6 +2,7 @@
2
2
  Content blocks are for building complex, nested HTML structures that usually
3
3
  contain sub-blocks, and may require javascript to function properly.
4
4
  """
5
+
5
6
  from django.utils.translation import gettext_lazy as _
6
7
  from wagtail import blocks
7
8
  from wagtail.documents.blocks import DocumentChooserBlock
@@ -180,15 +181,6 @@ class NavBaseLinkBlock(BaseBlock):
180
181
  required=False,
181
182
  label=_("Image"),
182
183
  )
183
- visible_for = blocks.ChoiceBlock(
184
- choices=cms_settings.CJKCMS_AUTH_VISIBILITY_CHOICES,
185
- default=cms_settings.CJKCMS_AUTH_VISIBILITY_DEFAULT,
186
- required=False,
187
- label=_("Item visibility"),
188
- help_text=_(
189
- "DEPRECATED. Use the visibility options in the `Advanced Settings`. "
190
- ),
191
- )
192
184
 
193
185
 
194
186
  class NavExternalLinkBlock(NavBaseLinkBlock):
@@ -359,3 +351,48 @@ class ReusableContentBlock(BaseBlock):
359
351
  label = _("Reusable Content")
360
352
  template = "cjkcms/blocks/reusable_content_block.html"
361
353
  label_format = "{content} (Reusable Content)"
354
+
355
+
356
+ class HighlightBlock(BaseBlock):
357
+ """
358
+ Block that highlights a piece of text
359
+ """
360
+
361
+ text = blocks.RichTextBlock(
362
+ features=cms_settings.CJKCMS_RICHTEXT_FEATURES["default"],
363
+ label=_("Text"),
364
+ )
365
+
366
+ background_color = blocks.ChoiceBlock(
367
+ choices=cms_settings.CJKCMS_HIGHLIGHT_COLORS,
368
+ default=cms_settings.CJKCMS_HIGHLIGHT_DEFAULT_COLOR,
369
+ required=False,
370
+ label=_("Background Color"),
371
+ )
372
+
373
+ border_color = blocks.ChoiceBlock(
374
+ choices=cms_settings.CJKCMS_HIGHLIGHT_COLORS,
375
+ default=cms_settings.CJKCMS_HIGHLIGHT_DEFAULT_COLOR,
376
+ required=False,
377
+ label=_("Border Color"),
378
+ )
379
+
380
+ text_color = blocks.ChoiceBlock(
381
+ choices=cms_settings.CJKCMS_HIGHLIGHT_COLORS,
382
+ default="dark",
383
+ required=False,
384
+ label=_("Text Color"),
385
+ )
386
+
387
+ icon = blocks.ChoiceBlock(
388
+ choices=cms_settings.FONT_AWESOME_ICONS,
389
+ default=cms_settings.FONT_AWESOME_ICONS_DEFAULT,
390
+ required=False,
391
+ label=_("Icon"),
392
+ )
393
+
394
+ class Meta:
395
+ icon = "thumbtack"
396
+ label = _("Highlight")
397
+ label_format = _("Highlight")
398
+ template = "cjkcms/blocks/highlight_block.html"
@@ -4,15 +4,20 @@ from django.db import migrations, models
4
4
 
5
5
 
6
6
  class Migration(migrations.Migration):
7
-
8
7
  dependencies = [
9
- ('cjkcms', '0017_layoutsettings_default_seo_image'),
8
+ ("cjkcms", "0017_layoutsettings_default_seo_image"),
10
9
  ]
11
10
 
12
11
  operations = [
13
12
  migrations.AddField(
14
- model_name='layoutsettings',
15
- name='search_format',
16
- field=models.CharField(blank=True, choices=[], default='', max_length=50, verbose_name='Search format'),
13
+ model_name="layoutsettings",
14
+ name="search_format",
15
+ field=models.CharField(
16
+ blank=True,
17
+ choices=[],
18
+ default="",
19
+ max_length=50,
20
+ verbose_name="Search format",
21
+ ),
17
22
  ),
18
23
  ]
@@ -4,25 +4,42 @@ from django.db import migrations, models
4
4
 
5
5
 
6
6
  class Migration(migrations.Migration):
7
-
8
7
  dependencies = [
9
- ('cjkcms', '0018_layoutsettings_search_format'),
8
+ ("cjkcms", "0018_layoutsettings_search_format"),
10
9
  ]
11
10
 
12
11
  operations = [
13
12
  migrations.AddField(
14
- model_name='layoutsettings',
15
- name='searchbox_input_class',
16
- field=models.CharField(blank=True, default='border-secondary mb-0', help_text='Classes applied to searchbox input', max_length=255, verbose_name='Searchbox input class'),
13
+ model_name="layoutsettings",
14
+ name="searchbox_input_class",
15
+ field=models.CharField(
16
+ blank=True,
17
+ default="border-secondary mb-0",
18
+ help_text="Classes applied to searchbox input",
19
+ max_length=255,
20
+ verbose_name="Searchbox input class",
21
+ ),
17
22
  ),
18
23
  migrations.AddField(
19
- model_name='layoutsettings',
20
- name='searchbutton_class',
21
- field=models.CharField(blank=True, default='btn btn-outline-secondary', help_text='Classes applied to search button. Add e.g. `fas fa-search` for icon.', max_length=255, verbose_name='Search button (with text) class'),
24
+ model_name="layoutsettings",
25
+ name="searchbutton_class",
26
+ field=models.CharField(
27
+ blank=True,
28
+ default="btn btn-outline-secondary",
29
+ help_text="Classes applied to search button. Add e.g. `fas fa-search` for icon.",
30
+ max_length=255,
31
+ verbose_name="Search button (with text) class",
32
+ ),
22
33
  ),
23
34
  migrations.AddField(
24
- model_name='layoutsettings',
25
- name='searchbutton_label',
26
- field=models.CharField(blank=True, default='Search', help_text='Leave empty when using icon-only button. Otherwise, add button label.', max_length=255, verbose_name='Search button label'),
35
+ model_name="layoutsettings",
36
+ name="searchbutton_label",
37
+ field=models.CharField(
38
+ blank=True,
39
+ default="Search",
40
+ help_text="Leave empty when using icon-only button. Otherwise, add button label.",
41
+ max_length=255,
42
+ verbose_name="Search button label",
43
+ ),
27
44
  ),
28
45
  ]
@@ -0,0 +1,35 @@
1
+ # Generated by Django 5.0.2 on 2024-03-11 19:20
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("cjkcms", "0019_layoutsettings_searchbox_input_class_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="socialmediasettings",
14
+ name="github",
15
+ field=models.URLField(
16
+ blank=True, help_text="Your GitHub page URL", verbose_name="GitHub"
17
+ ),
18
+ ),
19
+ migrations.AddField(
20
+ model_name="socialmediasettings",
21
+ name="pinterset",
22
+ field=models.URLField(
23
+ blank=True,
24
+ help_text="Your Pinterest page URL",
25
+ verbose_name="Pinterest",
26
+ ),
27
+ ),
28
+ migrations.AddField(
29
+ model_name="socialmediasettings",
30
+ name="tiktok",
31
+ field=models.URLField(
32
+ blank=True, help_text="Your TikTok account URL", verbose_name="TikTok"
33
+ ),
34
+ ),
35
+ ]
@@ -0,0 +1,85 @@
1
+ # Generated by Django 5.0.2 on 2024-03-21 15:11
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("cjkcms", "0020_socialmediasettings_github_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.RemoveField(
13
+ model_name="layoutsettings",
14
+ name="navbar_color_scheme",
15
+ ),
16
+ migrations.AddField(
17
+ model_name="layoutsettings",
18
+ name="color_scheme",
19
+ field=models.CharField(
20
+ blank=True,
21
+ choices=[],
22
+ default="",
23
+ help_text="Default light/dark/custom theme. (MD/Bootstrap only)",
24
+ max_length=50,
25
+ verbose_name="Color scheme",
26
+ ),
27
+ ),
28
+ migrations.AddField(
29
+ model_name="layoutsettings",
30
+ name="light_dark_switch",
31
+ field=models.BooleanField(
32
+ default=False,
33
+ help_text="Show switch to toggle light/dark theme (MD/Bootstrap only)",
34
+ verbose_name="Light/Dark switch",
35
+ ),
36
+ ),
37
+ migrations.AlterField(
38
+ model_name="layoutsettings",
39
+ name="frontend_theme",
40
+ field=models.CharField(
41
+ blank=True,
42
+ choices=[],
43
+ default="",
44
+ help_text="Change the source of your Bootstrap theme.",
45
+ max_length=50,
46
+ verbose_name="Theme variant",
47
+ ),
48
+ ),
49
+ migrations.AlterField(
50
+ model_name="layoutsettings",
51
+ name="navbar_collapse_mode",
52
+ field=models.CharField(
53
+ blank=True,
54
+ choices=[],
55
+ default="",
56
+ help_text="Control on what screen sizes to show and collapse the navbar menu links.",
57
+ max_length=50,
58
+ verbose_name="Collapse navbar menu",
59
+ ),
60
+ ),
61
+ migrations.AlterField(
62
+ model_name="layoutsettings",
63
+ name="navbar_format",
64
+ field=models.CharField(
65
+ blank=True,
66
+ choices=[],
67
+ default="",
68
+ max_length=50,
69
+ verbose_name="Navbar format",
70
+ ),
71
+ ),
72
+ migrations.AlterField(
73
+ model_name="layoutsettings",
74
+ name="navbar_langselector",
75
+ field=models.CharField(
76
+ blank=True,
77
+ choices=[],
78
+ default=None,
79
+ help_text="Choose lang choice selector",
80
+ max_length=255,
81
+ null=True,
82
+ verbose_name="Language selector",
83
+ ),
84
+ ),
85
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 5.0.6 on 2024-09-02 12:44
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('cjkcms', '0021_remove_layoutsettings_navbar_color_scheme_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='cjkcmspage',
15
+ name='breadcrumb_label',
16
+ field=models.CharField(blank=True, help_text='If empty, page title will be used.', max_length=128, verbose_name='Breadcrumb label'),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='cjkcmspage',
20
+ name='breadcrumbs_visible',
21
+ field=models.BooleanField(default=True, help_text='Show breadcrumbs in this page header. For global change, see Settings->Layout', verbose_name='Breadcrumbs'),
22
+ ),
23
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 5.1.4 on 2024-12-31 20:17
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('cjkcms', '0022_cjkcmspage_breadcrumb_label_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='navbar',
15
+ name='language',
16
+ field=models.CharField(blank=True, choices=[], default='_all_', help_text='Select a language to limit display to specific locale.', max_length=10, verbose_name='Show in language'),
17
+ ),
18
+ ]
@@ -1,28 +0,0 @@
1
- from .snippet_models import Navbar, EventCalendar
2
- from wagtail.snippets.views.snippets import SnippetViewSet
3
-
4
-
5
- class NavbarSnippet(SnippetViewSet):
6
- model = Navbar
7
- menu_label = "Navigation"
8
- menu_icon = "link" # change as required
9
- add_to_admin_menu = True
10
- list_display = (
11
- "name",
12
- "custom_css_class",
13
- "custom_id",
14
- )
15
- search_fields = [
16
- "name",
17
- ]
18
-
19
-
20
- class EventCalendarSnippet(SnippetViewSet):
21
- model = EventCalendar
22
- menu_label = "Public Events"
23
- menu_icon = "calendar" # change as required
24
- # add_to_admin_menu = True
25
- list_display = ("name",)
26
- search_fields = [
27
- "name",
28
- ]
@@ -208,6 +208,25 @@ class CjkcmsPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CjkcmsPageMeta):
208
208
  blank=True, max_length=255, choices=None, verbose_name=_("Template") # type: ignore # noqa: E501
209
209
  )
210
210
 
211
+ ###############
212
+ # Breadcrumbs fields (on layout tab)
213
+ ###############
214
+
215
+ breadcrumbs_visible = models.BooleanField(
216
+ default=True,
217
+ verbose_name=_("Breadcrumbs"),
218
+ help_text=_(
219
+ "Show breadcrumbs in this page header. For global change, see Settings->Layout"
220
+ ),
221
+ )
222
+
223
+ breadcrumb_label = models.CharField(
224
+ blank=True,
225
+ max_length=128,
226
+ verbose_name=_("Breadcrumb label"),
227
+ help_text=_("If empty, page title will be used."),
228
+ )
229
+
211
230
  ###############
212
231
  # SEO overrides
213
232
  ###############
@@ -269,6 +288,13 @@ class CjkcmsPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CjkcmsPageMeta):
269
288
 
270
289
  layout_panels = [
271
290
  MultiFieldPanel([FieldPanel("custom_template")], heading=_("Visual Design")),
291
+ MultiFieldPanel(
292
+ [
293
+ FieldPanel("breadcrumbs_visible"),
294
+ FieldPanel("breadcrumb_label"),
295
+ ],
296
+ heading=_("Breadcrumbs settings"),
297
+ ),
272
298
  MultiFieldPanel(
273
299
  [
274
300
  FieldPanel("index_show_subpages"),
@@ -347,6 +373,13 @@ class CjkcmsPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CjkcmsPageMeta):
347
373
  edit_handler = TabbedInterface(panels)
348
374
  return edit_handler.bind_to_model(cls)
349
375
 
376
+ @property
377
+ def breadcrumb_title(self) -> str:
378
+ """
379
+ Gets breadcrumb title, or page title if not set.
380
+ """
381
+ return self.breadcrumb_label or self.title # type: ignore
382
+
350
383
  @property
351
384
  def default_seo_image(self) -> "Optional[AbstractImage]":
352
385
  """
@@ -25,6 +25,7 @@ from django.conf import settings
25
25
  from cjkcms.fields import CjkcmsStreamField
26
26
  from wagtail_color_panel.fields import ColorField
27
27
  from wagtail_color_panel.edit_handlers import NativeColorPanel
28
+ from django.forms import ModelForm
28
29
 
29
30
 
30
31
  @register_snippet
@@ -342,7 +343,7 @@ class Navbar(models.Model):
342
343
  language = models.CharField(
343
344
  blank=True,
344
345
  max_length=10,
345
- choices=[("_all_", _("All languages"))],
346
+ choices=[],
346
347
  default="_all_",
347
348
  verbose_name=_("Show in language"),
348
349
  help_text=_("Select a language to limit display to specific locale."),
@@ -371,24 +372,38 @@ class Navbar(models.Model):
371
372
  def __str__(self):
372
373
  return self.name
373
374
 
375
+ @staticmethod
376
+ def get_available_langs():
377
+ available_langs = [("_all_", _("All languages"))]
378
+ if hasattr(settings, "WAGTAIL_CONTENT_LANGUAGES"):
379
+ available_langs += settings.WAGTAIL_CONTENT_LANGUAGES
380
+ return available_langs
381
+
374
382
  def __init__(self, *args, **kwargs):
375
383
  """
376
384
  Inject custom choices and defaults into the form fields
377
385
  to enable customization of settings without causing migration issues.
378
386
  """
379
387
  super().__init__(*args, **kwargs)
380
- # Set choices dynamically.
381
-
382
- available_langs = [("_all_", _("All languages"))]
383
- if hasattr(settings, "WAGTAIL_CONTENT_LANGUAGES"):
384
- available_langs += settings.WAGTAIL_CONTENT_LANGUAGES
385
388
 
386
- self._meta.get_field("language").choices = available_langs # type: ignore
389
+ self._meta.get_field("language").choices = Navbar.get_available_langs() # type: ignore
387
390
  # Set default dynamically.
388
391
  if not self.id: # type: ignore
389
392
  self.language = "_all_"
390
393
 
391
394
 
395
+ class NavbarForm(ModelForm):
396
+ class Meta:
397
+ model = Navbar
398
+ fields = "__all__"
399
+
400
+ def __init__(self, *args, **kwargs):
401
+ super().__init__(*args, **kwargs)
402
+
403
+ # Set the dynamic choices
404
+ self.fields["language"].choices = Navbar.get_available_langs()
405
+
406
+
392
407
  @register_snippet
393
408
  class Footer(models.Model):
394
409
  """