This is an automated email from the ASF dual-hosted git repository.

yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git


The following commit(s) were added to refs/heads/main by this push:
     new a73355cec feat(cms): port Airavata page models, snippets, and blocks 
to Wagtail 7.4 (#114)
a73355cec is described below

commit a73355cecae3ca74b902fcdd84bbba7280681ced
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 04:51:56 2026 -0400

    feat(cms): port Airavata page models, snippets, and blocks to Wagtail 7.4 
(#114)
    
    Bring the portal's Wagtail content schema into the standalone CMS, 
modernized
    from Wagtail 2.13 to 7.4:
    
    - home/blocks.py: StreamField block library (paragraph, image, embed, 
heading,
      Bootstrap components, nav, CSS, code) ported with wagtail.core.blocks ->
      wagtail.blocks. CodeBlock via wagtailcodeblock (1.30, Wagtail 
7-compatible).
    - home/models.py: 9 snippets (Announcements, Navbar, GatewayIcon/Title,
      FooterText, NavExtra, CustomCss, CustomHeaderLinks, ExtraWebResources) 
and 3
      page types (HomePage, BlankPage, CybergatewayHomePage) with their 
orderables.
      Panels modernized (edit_handlers -> panels; StreamFieldPanel / 
ImageChooserPanel
      / PageChooserPanel -> FieldPanel); ParentalKey app label -> home.
    - Regenerate home/0001_initial for the full schema; rewrite the 
create-homepage
      data migration to run after wagtailcore (the rich HomePage FKs pull in 
late
      wagtailcore deps, so the old run_before=0053 trick cycles) and set the 
page
      locale explicitly.
    - Add wagtailcodeblock to requirements and INSTALLED_APPS.
    
    Validated on sqlite: migrate, makemigrations --check, manage.py check all 
green;
    root homepage + default site created. Page/block render templates and 
seagrid
    content import follow in later PRs.
---
 airavata-cms/airavata_cms/settings/base.py         |   1 +
 airavata-cms/home/blocks.py                        | 667 +++++++++++++++
 airavata-cms/home/migrations/0001_initial.py       | 250 +++++-
 .../home/migrations/0002_create_homepage.py        |  16 +-
 airavata-cms/home/models.py                        | 901 ++++++++++++++++++++-
 airavata-cms/requirements.txt                      |   1 +
 6 files changed, 1813 insertions(+), 23 deletions(-)

diff --git a/airavata-cms/airavata_cms/settings/base.py 
b/airavata-cms/airavata_cms/settings/base.py
index 72be1bd6e..1bf3683fd 100644
--- a/airavata-cms/airavata_cms/settings/base.py
+++ b/airavata-cms/airavata_cms/settings/base.py
@@ -37,6 +37,7 @@ INSTALLED_APPS = [
     "wagtail.search",
     "wagtail.admin",
     "wagtail",
+    "wagtailcodeblock",
     "modelcluster",
     "taggit",
     "django_filters",
diff --git a/airavata-cms/home/blocks.py b/airavata-cms/home/blocks.py
new file mode 100644
index 000000000..f3cad89c6
--- /dev/null
+++ b/airavata-cms/home/blocks.py
@@ -0,0 +1,667 @@
+from wagtail.blocks import (
+    BooleanBlock,
+    CharBlock,
+    ChoiceBlock,
+    IntegerBlock,
+    ListBlock,
+    RawHTMLBlock,
+    RichTextBlock,
+    StreamBlock,
+    StructBlock,
+    TextBlock
+)
+from wagtail.documents.blocks import DocumentChooserBlock
+from wagtail.embeds.blocks import EmbedBlock
+from wagtail.images.blocks import ImageChooserBlock
+from wagtailcodeblock.blocks import CodeBlock
+
+
+class ImageBlock(StructBlock):
+    """
+    Custom `StructBlock` for utilizing images with associated caption and
+    attribution data
+    """
+    image = ImageChooserBlock(required=True)
+    caption = CharBlock(required=False)
+    width = CharBlock(required=False)
+    height = IntegerBlock(required=False)
+    redirect_url = TextBlock(
+        required=False,
+        null=True,
+        blank=True,
+        help_text="Give a redirect link on clicking the image")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = 'image'
+        template = "blocks/image_block.html"
+
+
+class FontAwesomeIcon(StructBlock):
+    """
+    Custom 'StructBlock' for utilizing images with associated caption and
+    attribution django_airavata
+    """
+    icon_tag = TextBlock(required=True, blank=False,
+                         help_text="Provide a font awesome icon class text")
+    icon_size = IntegerBlock(
+        required=False,
+        default=2,
+        null=True,
+        blank=True,
+        help_text="Provide a icon size in number type")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = 'fa-flag'
+        template = "blocks/font_awesome_icon_block.html"
+
+
+class ParagraphBlock(StructBlock):
+    """
+    Custom 'StructBlock' for creating rich text content
+    """
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+    body = RichTextBlock()
+
+    class Meta:
+        icon = "fa-paragraph"
+        template = "blocks/paragraph_block.html"
+        help_text = "Create a free form paragraph"
+
+
+class IuFooter(StructBlock):
+    """
+    Custom 'StructBlock' for creating IU Footer
+    """
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+    footer_links = RichTextBlock()
+
+    class Meta:
+        icon = "fa-university"
+        template = "blocks/iu_footer.html"
+        help_text = "Create an IU Footer"
+
+
+class HeadingBlock(StructBlock):
+    """
+    Custom `StructBlock` that allows the user to select h1 - h4 sizes for
+    headers
+    """
+    heading_text = CharBlock(classname="title", required=True)
+    size = ChoiceBlock(choices=[
+        ('', 'Select a header size'),
+        ('h1', 'H1'),
+        ('h2', 'H2'),
+        ('h3', 'H3'),
+        ('h4', 'H4')
+    ], blank=True, required=False)
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "title"
+        template = "blocks/heading_block.html"
+
+
+class PlaceholderBlock(StructBlock):
+    """
+    Custom 'StructBlock' that acts as a placeholder
+    """
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-map-marker"
+        template = "blocks/placeholder_block.html"
+
+
+class CustomEmbedBlock(StructBlock):
+    """
+    Custom 'StructBlock' that allows you to embed videos
+    """
+    embed = EmbedBlock()
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-link"
+        template = "blocks/embed_block.html"
+        help_text = ("Insert a youtube URL e.g "
+                     "https://www.youtube.com/watch?v=SGJFWirQ3ks";)
+
+
+class CssCommentBlock(StructBlock):
+    """
+    CSS Comment block
+    """
+    message = TextBlock(
+        required=True, help_text="Write some comment to mark the css")
+
+    class Meta:
+        icon = "fa-comment"
+        template = "blocks/css_comment.html"
+        help_text = "Css Comment"
+
+
+class HorizontalRule(StructBlock):
+    """
+    HorizontalRule
+    """
+    thickness = IntegerBlock(
+        required=False,
+        blank=True,
+        help_text="Enter a thickness integer value. Eg(10)")
+    bg_color = TextBlock(
+        required=True,
+        help_text="Enter a hexcode color for the rule Eg(#000000)")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-long-arrow-right"
+        template = "blocks/hrule.html"
+        help_text = "Horizontal Rule"
+
+
+# BootStrap Components
+
+class BootstrapJumbotron(StructBlock):
+    """
+    Custom 'StructBlock' that allows the user to make a bootstrap jumbotron
+    """
+    title = TextBlock()
+    body = RichTextBlock()
+    button_text = TextBlock(required=False)
+    button_link = TextBlock(required=False)
+    button_color = ChoiceBlock(choices=[
+        ('btn-primary', 'DEFAULT'),
+        ('btn-danger', 'RED'),
+        ('btn-secondary', 'GREY'),
+        ('btn-success', 'GREEN'),
+        ('btn-warning', 'ORANGE')
+    ], blank=True, required=False, help_text="select a button color")
+    button_size = ChoiceBlock(choices=[
+        ('', 'DEFAULT'),
+        ('btn-lg', 'LARGE'),
+        ('btn-sm', 'SMALL')
+    ], blank=True, required=False, help_text="select a button size")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-indent"
+        template = "blocks/bootstrap/jumbotron.html"
+        help_text = "Create a bootstrap jumbotron"
+
+
+class BootstrapButton(StructBlock):
+    """
+    Custom 'StructBlock' that allows the user to make a bootstrap button
+    """
+    button_text = TextBlock()
+    button_link = TextBlock()
+    button_color = ChoiceBlock(choices=[
+        ('btn-primary', 'DEFAULT'),
+        ('btn-danger', 'RED'),
+        ('btn-secondary', 'GREY'),
+        ('btn-success', 'GREEN'),
+        ('btn-warning', 'ORANGE')
+    ], blank=True, required=False, help_text="select a button color")
+    button_size = ChoiceBlock(choices=[
+        ('', 'DEFAULT'),
+        ('btn-lg', 'LARGE'),
+        ('btn-sm', 'SMALL')
+    ], blank=True, required=False, help_text="select a button size")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-bold"
+        template = "blocks/bootstrap/button.html"
+        help_text = "Create a bootstrap button"
+
+
+class BootstrapButtonMore(StructBlock):
+    """
+    Custom 'StructBlock' that allows the user to make a bootstrap button that
+    toggles collapsible rich text block
+    """
+    button_text = TextBlock()
+    button_id = TextBlock(help_text="Unique name for this collapsible")
+    button_color = ChoiceBlock(choices=[
+        ('btn-primary', 'DEFAULT'),
+        ('btn-danger', 'RED'),
+        ('btn-secondary', 'GREY'),
+        ('btn-success', 'GREEN'),
+        ('btn-warning', 'ORANGE')
+    ], blank=True, required=False, help_text="select a button color")
+    button_size = ChoiceBlock(choices=[
+        ('', 'DEFAULT'),
+        ('btn-lg', 'LARGE'),
+        ('btn-sm', 'SMALL')
+    ], blank=True, required=False, help_text="select a button size")
+    button_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control the look of this button by giving unique class 
names "
+                  "separated by space and styling the class in css")
+    body_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control the look of this body by giving unique class names "
+                  "separated by space and styling the class in css")
+    body_inline_style = TextBlock(
+        required=False,
+        blank=True,
+        help_text="apply inline CSS styles to body")
+    body = RichTextBlock()
+
+    class Meta:
+        icon = "collapse-up"
+        template = "blocks/bootstrap/buttonmore.html"
+        help_text = "Create a button that will toggle the display of a rich 
text body of text"
+
+
+class BootstrapAlert(StructBlock):
+    """
+    Custom 'StructBlock' that allows the user to make a bootstrap alert
+    """
+    alert_text = TextBlock()
+    alert_color = ChoiceBlock(choices=[
+        ('alert-primary', 'DEFAULT'),
+        ('alert-secondary', 'GREY'),
+        ('alert-success', 'GREEN'),
+        ('alert-danger', 'RED'),
+        ('alert-warning', 'ORANGE'),
+        ('alert-dark', 'DARK'),
+        ('alert-light', 'LIGHT'),
+    ], blank=True, required=False, help_text="select a background color")
+    is_link = BooleanBlock(required=False)
+    alert_link = TextBlock(required=False)
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-bell"
+        template = "blocks/bootstrap/alert.html"
+        help_text = "Create a bootstrap alert"
+
+
+class BootstrapCard(StructBlock):
+    """
+    Custom 'StructBlock' that allows the user to make a bootstrap card
+    """
+
+    card_width = IntegerBlock(
+        help_text="18 works best for card", required=False, blank=True)
+    is_card_img = BooleanBlock(required=False)
+    is_card_img_overlay = BooleanBlock(
+        required=False,
+        default=False,
+        help_text="Use image as background for card",
+        label="Image Overlay?")
+    card_img = ImageChooserBlock(required=False)
+    card_img_width = IntegerBlock(
+        required=False, help_text="provide an image width")
+    card_img_height = IntegerBlock(
+        required=False, help_text="provide an image height")
+    card_title = TextBlock(required=False, null=True, blank=True)
+    card_text = RichTextBlock(required=False, null=True, blank=True)
+    card_bg_color = ChoiceBlock(choices=[
+        ('bg-primary', 'DEFAULT'),
+        ('bg-secondary', 'GREY'),
+        ('bg-success', 'GREEN'),
+        ('bg-danger', 'RED'),
+        ('bg-warning', 'ORANGE'),
+        ('bg-dark', 'DARK'),
+        ('bg-light', 'LIGHT'),
+    ], blank=True, required=False, help_text="select a background color")
+    card_text_color = ChoiceBlock(choices=[
+        ('text-primary', 'DEFAULT'),
+        ('text-secondary', 'GREY'),
+        ('text-success', 'GREEN'),
+        ('text-danger', 'RED'),
+        ('text-warning', 'ORANGE'),
+        ('text-dark', 'DARK'),
+        ('text-light', 'LIGHT'),
+    ], blank=True, required=False, help_text="select a text color")
+    buttons = ListBlock(BootstrapButton(required=False), default=[])
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-id-card"
+        template = "blocks/bootstrap/card.html"
+        help_text = "Create a bootstrap card"
+
+
+class BootstrapCarousel(StructBlock):
+    """
+    Custom 'StructBlock' that allows the user to make a bootstrap carousel
+    """
+    interval = IntegerBlock(default=2000, help_text="""
+    The amount of time to delay between automatically cycling an item. If
+    false, carousel will not automatically cycle.
+    """)
+    c_image1 = ImageChooserBlock(required=True)
+    c_image1_title = TextBlock(
+        required=False, blank=True, help_text="Give a title for image 1")
+    c_image1_body = TextBlock(
+        required=False, blank=True, help_text="Give a body for image 1")
+    c_image2 = ImageChooserBlock(required=False)
+    c_image2_title = TextBlock(
+        required=False, blank=True, help_text="Give a title for image 2")
+    c_image2_body = TextBlock(
+        required=False, blank=True, help_text="Give a body for image 2")
+    c_image3 = ImageChooserBlock(required=False)
+    c_image3_title = TextBlock(
+        required=False, blank=True, help_text="Give a title for image 3")
+    c_image3_body = TextBlock(
+        required=False, blank=True, help_text="Give a body for image 3")
+    c_image4 = ImageChooserBlock(required=False)
+    c_image4_title = TextBlock(
+        required=False, blank=True, help_text="Give a title for image 4")
+    c_image4_body = TextBlock(
+        required=False, blank=True, help_text="Give a body for image 4")
+    c_image5 = ImageChooserBlock(required=False)
+    c_image5_title = TextBlock(
+        required=False, blank=True, help_text="Give a title for image 5")
+    c_image5_body = TextBlock(
+        required=False, blank=True, help_text="Give a body for image 5")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-film"
+        template = "blocks/bootstrap/carousel.html"
+        help_text = ("Create a bootstrap carousel. Fill the images in order "
+                     "to get optimized display.")
+
+
+class BootstrapWell(StructBlock):
+    """
+    Custom 'StructBlock' that allows user to make a bootstrap well.
+    (optimized for bootstrap 4 using card)
+    """
+    message = RichTextBlock(help_text="Enter some message inside well")
+    well_bg_color = ChoiceBlock(choices=[
+        ('bg-primary', 'DEFAULT'),
+        ('bg-secondary', 'GREY'),
+        ('bg-success', 'GREEN'),
+        ('bg-danger', 'RED'),
+        ('bg-warning', 'ORANGE'),
+        ('bg-dark', 'DARK'),
+        ('bg-light', 'LIGHT'),
+    ], blank=True, required=False, help_text="select a background color")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-window-minimize"
+        template = "blocks/bootstrap/well.html"
+
+
+class BootstrapMediaObject(StructBlock):
+    """
+    Custom 'StructBlock' that allows user to make a bootstrap media object.
+    """
+    media_img = ImageChooserBlock(required=True)
+    media_img_alt = TextBlock(required=True)
+    media_img_width = IntegerBlock(
+        required=False,
+        blank=True,
+        help_text="Enter an image width as an integer value. Eg(50)")
+    media_img_height = IntegerBlock(
+        required=False,
+        blank=True,
+        help_text="Enter an image height as an integer value Eg(50)")
+    heading_text = TextBlock(
+        required=False,
+        blank=True,
+        help_text="enter some heading text for media object")
+    body_text = RichTextBlock(
+        required=True, help_text="Enter some message for the media object")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "fa-align-right"
+        template = "blocks/bootstrap/media.html"
+
+
+class BootstrapEmbedVideo(StructBlock):
+    """
+    Use Bootstrap's Embed component with video tag.
+    """
+    video = DocumentChooserBlock(required=True)
+    aspect_ratio = ChoiceBlock(choices=[
+        ('21by9', '21 x 9'),
+        ('16by9', '16 x 9'),
+        ('4by3', '4 x 3'),
+        ('1by1', '1 x 1'),
+    ], default='4by3', help_text="Aspect ratio")
+    custom_class = TextBlock(
+        required=False,
+        blank=True,
+        help_text="control this element by giving unique class names "
+                  "separated by space and styling the class in css")
+
+    class Meta:
+        icon = "media"
+        template = "blocks/bootstrap/embed-video.html"
+
+
+# StreamBlocks
+class BaseStreamBlock(StreamBlock):
+    """
+    Define the custom blocks that `StreamField` will utilize
+    """
+    paragraph_block = ParagraphBlock()
+    image_block = ImageBlock()
+    embed_block = CustomEmbedBlock()
+    heading_block = HeadingBlock()
+    bootstrap_jumbotron = BootstrapJumbotron()
+    bootstrap_alert = BootstrapAlert()
+    bootstrap_button = BootstrapButton()
+    bootstrap_card = BootstrapCard()
+    bootstrap_carousel = BootstrapCarousel()
+    bootstrap_well = BootstrapWell()
+    horizontal_rule = HorizontalRule()
+    bootstrap_media_object = BootstrapMediaObject()
+    placeholder_block = PlaceholderBlock()
+    font_awesome_icon_block = FontAwesomeIcon()
+    iu_footer_block = IuFooter()
+    bootstrap_embed_video = BootstrapEmbedVideo()
+    expandable_rich_text_block = BootstrapButtonMore()
+    HTML_code = RawHTMLBlock()
+    # Using 'snippet' icon for uniqueness (RawHTMLBlock already uses 'code' 
icon)
+    code_snippet = CodeBlock(icon="snippet")
+
+
+class CssStreamBlock(StreamBlock):
+    """
+    Define the custom blocks for css that 'StreamField' will utilize
+    """
+    css_block = RawHTMLBlock(required=True, help_text="Write Css Here")
+    css_comment = CssCommentBlock()
+
+
+class ContainerBlock(StructBlock):
+    inline_styles = TextBlock(
+        required=False,
+        blank=True,
+        help_text="Apply inline CSS styles to container wrapper.")
+    custom_classes = CharBlock(
+        required=False,
+        help_text="Apply custom CSS classes to container wrapper. You can "
+                  "define CSS classes in a Custom CSS snippet.")
+    background_image = ImageChooserBlock(
+        required=False,
+        help_text="Apply background image to container wrapper.")
+
+    def get_context(self, value, parent_context=None):
+        context = super().get_context(value, parent_context=parent_context)
+        context['container_class'] = self.container_class
+        return context
+
+    class Meta:
+        abstract = True
+        template = "blocks/bootstrap/container.html"
+
+
+class FullWidthContainer(ContainerBlock):
+    container_class = "container-fluid"
+
+    class Meta:
+        icon = "fa-arrows-h"
+
+
+class MaxWidthContainer(ContainerBlock):
+    container_class = "container"
+
+    class Meta:
+        icon = "fa-square-o"
+
+
+class ContainerChoiceBlock(StreamBlock):
+    full_width_container = FullWidthContainer()
+    max_width_container = MaxWidthContainer()
+
+    class Meta:
+        max_num = 1
+        required = False
+
+
+class NavItem(StructBlock):
+    link = CharBlock(help_text="Full URL or relative path (e.g., /auth/login)")
+    link_text = CharBlock(required=False)
+    image = ImageChooserBlock(required=False)
+    icon_class = CharBlock(required=False, help_text="Font awesome icon class")
+    show = ChoiceBlock(choices=[
+        ('not-logged-in', 'Only when not logged in'),
+        ('logged-in', 'Only when logged in'),
+        ('always', 'Always'),
+    ], default='not-logged-in')
+    horizontal_alignment = ChoiceBlock(choices=[
+        ('', 'Select a horizontal alignment'),
+        ('push-right', 'Push Right'),
+    ], required=False)
+    include_in_main_menu = ChoiceBlock(choices=[
+        ('', 'Choose if this link should also be added to main menu'),
+        ('yes', 'Add to main menu'),
+    ], required=False, help_text="The main menu is at the top of the dashboard 
when a user first logs in. Horizontal alignment does not apply when added to 
main menu.")
+
+    class Meta:
+        icon = "fa-minus"
+        template = "blocks/bootstrap/nav-item.html"
+
+
+class LoginNavItem(NavItem):
+
+    class Meta:
+        default = {
+            'link': '/auth/login',
+            'link_text': 'Log in',
+            'icon_class': 'fas fa-sign-in-alt',
+            'show': 'not-logged-in',
+        }
+
+
+class DashboardLinkNavItem(NavItem):
+
+    class Meta:
+        default = {
+            'link': '/workspace/dashboard',
+            'link_text': 'Go to Dashboard',
+            'icon_class': 'fas fa-arrow-circle-right',
+            'show': 'logged-in',
+            'horizontal_alignment': 'push-right',
+        }
+
+
+class LogoutNavItem(NavItem):
+
+    class Meta:
+        default = {
+            'link': '/auth/logout',
+            'link_text': 'Logout',
+            'icon_class': 'fas fa-sign-out-alt',
+            'show': 'logged-in',
+        }
+
+
+class CreateAccountNavItem(NavItem):
+
+    class Meta:
+        default = {
+            'link': '/auth/create-account',
+            'link_text': 'Create Account',
+            'icon_class': 'fas fa-user',
+            'show': 'not-logged-in',
+            'horizontal_alignment': 'push-right',
+        }
+
+
+class Nav(StructBlock):
+    custom_class = CharBlock(required=False)
+    nav_items = StreamBlock([
+        ('nav_item', NavItem()),
+        ('login_link', LoginNavItem()),
+        ('dashboard_link', DashboardLinkNavItem()),
+        ('logout_link', LogoutNavItem()),
+        ('create_account_link', CreateAccountNavItem()),
+    ])
+
+    class Meta:
+        icon = "fa-bars"
+        template = "blocks/bootstrap/nav.html"
diff --git a/airavata-cms/home/migrations/0001_initial.py 
b/airavata-cms/home/migrations/0001_initial.py
index 67f201d32..89f724929 100644
--- a/airavata-cms/home/migrations/0001_initial.py
+++ b/airavata-cms/home/migrations/0001_initial.py
@@ -1,31 +1,251 @@
+# Generated by Django 6.0.6 on 2026-06-08 05:29
+
+import django.db.models.deletion
+import modelcluster.fields
+import wagtail.fields
 from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
 
+    initial = True
+
     dependencies = [
-        ("wagtailcore", "0040_page_draft_title"),
+        ('wagtailcore', '0097_baselogentry_uuid_action_timestamp_indexes'),
+        ('wagtailimages', '0027_image_description'),
     ]
 
     operations = [
         migrations.CreateModel(
-            name="HomePage",
+            name='Announcements',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('announcement_text', models.CharField(default='Announcement 
Text', help_text='Provide an announcement text', max_length=255)),
+                ('announcement_link', models.CharField(default='Announcement 
Link', help_text='Give a redirect link for announcement', max_length=255)),
+            ],
+            options={
+                'verbose_name_plural': 'Announcement',
+            },
+        ),
+        migrations.CreateModel(
+            name='BlankPage',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, 
on_delete=django.db.models.deletion.CASCADE, parent_link=True, 
primary_key=True, serialize=False, to='wagtailcore.page')),
+                ('show_navbar', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want to display the 
navbar on home page and no if you don't want to.", max_length=5)),
+                ('show_nav_extra', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want the secondary 
navbar to show on home page or no if you don't want to", max_length=5)),
+                ('show_footer', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want the Footer to 
show on home page or no if you don't want to", max_length=5)),
+                ('show_announcements', models.CharField(choices=[('yes', 
'Yes'), ('no', 'No')], default='yes', help_text="Choose yes if you want the 
Announcements to show up on home page or no if you don't want to", 
max_length=5)),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='CustomCss',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('css', wagtail.fields.StreamField([('css_block', 0), 
('css_comment', 2)], blank=True, block_lookup={0: 
('wagtail.blocks.RawHTMLBlock', (), {'help_text': 'Write Css Here', 'required': 
True}), 1: ('wagtail.blocks.TextBlock', (), {'help_text': 'Write some comment 
to mark the css', 'required': True}), 2: ('wagtail.blocks.StructBlock', 
[[('message', 1)]], {})}, default='', help_text='Write custom css and give 
comments as necessary', null=True, verbose_name='CSS block')),
+            ],
+            options={
+                'verbose_name_plural': 'Custom CSS',
+            },
+        ),
+        migrations.CreateModel(
+            name='CustomHeaderLinks',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('header_link_text', models.CharField(help_text='Give a Link 
text', max_length=25)),
+                ('header_link', models.CharField(blank=True, 
help_text='Provide a redirect Link', max_length=255, null=True)),
+                ('header_sub_link_text1', models.CharField(blank=True, 
help_text='Give a Sub Link 1 text', max_length=25, null=True)),
+                ('header_sub_link_text2', models.CharField(blank=True, 
help_text='Give a Sub Link 2 text', max_length=25, null=True)),
+                ('header_sub_link_text3', models.CharField(blank=True, 
help_text='Give a Sub Link 3 text', max_length=25, null=True)),
+                ('header_sub_link_text4', models.CharField(blank=True, 
help_text='Give a Sub Link 4 text', max_length=25, null=True)),
+                ('header_sub_link1', models.CharField(blank=True, 
help_text='Provide a redirect Link for sublink 1', max_length=255, null=True)),
+                ('header_sub_link2', models.CharField(blank=True, 
help_text='Provide a redirect Link for sublink 2', max_length=255, null=True)),
+                ('header_sub_link3', models.CharField(blank=True, 
help_text='Provide a redirect Link for sublink 3', max_length=255, null=True)),
+                ('header_sub_link4', models.CharField(blank=True, 
help_text='Provide a redirect Link for sublink 4', max_length=255, null=True)),
+                ('body', models.CharField(blank=True, help_text='Give a title 
text', max_length=255, null=True)),
+            ],
+            options={
+                'verbose_name_plural': 'Header Custom Links',
+            },
+        ),
+        migrations.CreateModel(
+            name='ExtraWebResources',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+            ],
+            options={
+                'verbose_name_plural': 'Extra Web Resources',
+            },
+        ),
+        migrations.CreateModel(
+            name='FooterText',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('footer', wagtail.fields.StreamField([('paragraph_block', 2), 
('image_block', 7), ('embed_block', 9), ('heading_block', 12), 
('bootstrap_jumbotron', 17), ('bootstrap_alert', 20), ('bootstrap_button', 21), 
('bootstrap_card', 33), ('bootstrap_carousel', 45), ('bootstrap_well', 47), 
('horizontal_rule', 50), ('bootstrap_media_object', 56), ('placeholder_block', 
57), ('font_awesome_icon_block', 60), ('iu_footer_block', 61), 
('bootstrap_embed_video', 64), ('expandable_rich_tex [...]
+            ],
+            options={
+                'verbose_name_plural': 'Footer',
+            },
+        ),
+        migrations.CreateModel(
+            name='GatewayTitle',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('title_text', models.CharField(help_text='Title to display to 
logged in users.', max_length=100)),
+            ],
+            options={
+                'verbose_name_plural': 'Gateway Title',
+            },
+        ),
+        migrations.CreateModel(
+            name='NavExtra',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('nav', wagtail.fields.StreamField([('nav', 9)], 
block_lookup={0: ('wagtail.blocks.CharBlock', (), {'required': False}), 1: 
('wagtail.blocks.CharBlock', (), {'help_text': 'Full URL or relative path 
(e.g., /auth/login)'}), 2: ('wagtail.images.blocks.ImageChooserBlock', (), 
{'required': False}), 3: ('wagtail.blocks.CharBlock', (), {'help_text': 'Font 
awesome icon class', 'required': False}), 4: ('wagtail.blocks.ChoiceBlock', [], 
{'choices': [('not-logged-in', 'Only when not [...]
+            ],
+            options={
+                'verbose_name_plural': 'Nav extra',
+            },
+        ),
+        migrations.CreateModel(
+            name='CybergatewayHomePage',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, 
on_delete=django.db.models.deletion.CASCADE, parent_link=True, 
primary_key=True, serialize=False, to='wagtailcore.page')),
+                ('site_link', models.CharField(default='#', help_text='Give a 
site redirect link', max_length=255)),
+                ('site_text', models.CharField(default='#', help_text='Give a 
Site Name', max_length=50)),
+                ('site_header', models.CharField(default='#', help_text='Give 
a Site Header Name', max_length=70)),
+                ('site_link1', models.CharField(default='#', help_text='Give a 
Site Nav Link [1]', max_length=70)),
+                ('site_link_text1', models.CharField(help_text='Give a Site 
Nav Link Text [1]', max_length=70)),
+                ('site_link2', models.CharField(default='#', help_text='Give a 
Site Nav Link [2]', max_length=70)),
+                ('site_link_text2', models.CharField(help_text='Give a Site 
Nav Link Text [2]', max_length=70)),
+                ('site_link3', models.CharField(default='#', help_text='Give a 
Site Nav Link [3]', max_length=70)),
+                ('site_link_text3', models.CharField(help_text='Give a Site 
Nav Link Text [3]', max_length=70)),
+                ('contact', wagtail.fields.StreamField([('paragraph_block', 
2), ('image_block', 7), ('embed_block', 9), ('heading_block', 12), 
('bootstrap_jumbotron', 17), ('bootstrap_alert', 20), ('bootstrap_button', 21), 
('bootstrap_card', 33), ('bootstrap_carousel', 45), ('bootstrap_well', 47), 
('horizontal_rule', 50), ('bootstrap_media_object', 56), ('placeholder_block', 
57), ('font_awesome_icon_block', 60), ('iu_footer_block', 61), 
('bootstrap_embed_video', 64), ('expandable_rich_te [...]
+                ('footer', wagtail.fields.StreamField([('paragraph_block', 2), 
('image_block', 7), ('embed_block', 9), ('heading_block', 12), 
('bootstrap_jumbotron', 17), ('bootstrap_alert', 20), ('bootstrap_button', 21), 
('bootstrap_card', 33), ('bootstrap_carousel', 45), ('bootstrap_well', 47), 
('horizontal_rule', 50), ('bootstrap_media_object', 56), ('placeholder_block', 
57), ('font_awesome_icon_block', 60), ('iu_footer_block', 61), 
('bootstrap_embed_video', 64), ('expandable_rich_tex [...]
+                ('show_navbar', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want to display the 
navbar on home page and no if you don't want to.", max_length=5)),
+                ('show_nav_extra', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want the secondary 
navbar to show on home page or no if you don't want to", max_length=5)),
+                ('show_footer', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want the Footer to 
show on home page or no if you don't want to", max_length=5)),
+                ('site_logo', models.ForeignKey(blank=True, help_text='Site 
Logo Image', null=True, on_delete=django.db.models.deletion.SET_NULL, 
related_name='+', to='wagtailimages.image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='CssLink',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('sort_order', models.IntegerField(blank=True, editable=False, 
null=True)),
+                ('url', models.CharField(help_text='URL of CSS stylesheet.', 
max_length=255)),
+                ('extra_web_resources', 
modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='css_links', to='home.extrawebresources')),
+            ],
+            options={
+                'verbose_name': 'CSS Link',
+            },
+        ),
+        migrations.CreateModel(
+            name='GatewayIcon',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('background_color', models.CharField(default='#EEEEEE', 
help_text='Background color for icon (e.g. #FFFFFF)', max_length=10)),
+                ('icon', models.ForeignKey(blank=True, help_text='Choose 
Gateway Icon with dimensions 70x70', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+            ],
+            options={
+                'verbose_name_plural': 'Gateway Icon',
+            },
+        ),
+        migrations.CreateModel(
+            name='HomePage',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, 
on_delete=django.db.models.deletion.CASCADE, parent_link=True, 
primary_key=True, serialize=False, to='wagtailcore.page')),
+                ('hero_text', models.CharField(blank=True, help_text='Write an 
introduction for the bakery', max_length=255, null=True)),
+                ('hero_cta', models.CharField(blank=True, help_text='Text to 
display on Call to Action', max_length=255, null=True, verbose_name='Hero 
CTA')),
+                ('body', wagtail.fields.StreamField([('paragraph_block', 2), 
('image_block', 7), ('embed_block', 9), ('heading_block', 12), 
('bootstrap_jumbotron', 17), ('bootstrap_alert', 20), ('bootstrap_button', 21), 
('bootstrap_card', 33), ('bootstrap_carousel', 45), ('bootstrap_well', 47), 
('horizontal_rule', 50), ('bootstrap_media_object', 56), ('placeholder_block', 
57), ('font_awesome_icon_block', 60), ('iu_footer_block', 61), 
('bootstrap_embed_video', 64), ('expandable_rich_text_ [...]
+                ('features_text', wagtail.fields.RichTextField(blank=True, 
help_text='Write some feature description', null=True)),
+                ('feature_1_title', models.CharField(help_text='Feature Title 
1', max_length=255)),
+                ('feature_1_text', wagtail.fields.RichTextField(blank=True, 
help_text='Write some feature 1 text description', null=True)),
+                ('feature_2_title', models.CharField(help_text='Feature Title 
2', max_length=255)),
+                ('feature_2_text', wagtail.fields.RichTextField(blank=True, 
help_text='Write some feature 2 text description', null=True)),
+                ('feature_3_title', models.CharField(help_text='Feature Title 
3', max_length=255)),
+                ('feature_3_text', wagtail.fields.RichTextField(blank=True, 
help_text='Write some feature 3 text description', null=True)),
+                ('feature_4_title', models.CharField(help_text='Feature Title 
4', max_length=255)),
+                ('feature_4_text', wagtail.fields.RichTextField(blank=True, 
help_text='Write some feature 4 text description', null=True)),
+                ('custom_body_message', 
wagtail.fields.RichTextField(blank=True, help_text='Write some custom body 
message!', null=True)),
+                ('show_navbar', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default=True, help_text="Choose yes if you want to display the 
navbar on home page and no if you don't want to.", max_length=5)),
+                ('show_nav_extra', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default=True, help_text="Choose yes if you want the secondary 
navbar to show on home page or no if you don't want to", max_length=5)),
+                ('show_footer', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='yes', help_text="Choose yes if you want the Footer to 
show on home page or no if you don't want to", max_length=5)),
+                ('banner_image', models.ForeignKey(blank=True, 
help_text='Choose Banner Image', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+                ('feature_logo_1', models.ForeignKey(blank=True, 
help_text='Feature Logo 1', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+                ('feature_logo_2', models.ForeignKey(blank=True, 
help_text='Feature Logo 2', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+                ('feature_logo_3', models.ForeignKey(blank=True, 
help_text='Feature Logo 3', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+                ('feature_logo_4', models.ForeignKey(blank=True, 
help_text='Feature Logo 4', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+                ('hero_cta_link', models.ForeignKey(blank=True, 
help_text='Choose a page to link to for the Call to Action', null=True, 
on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailcore.page', verbose_name='Hero CTA link')),
+                ('image', models.ForeignKey(blank=True, help_text='Homepage 
image', null=True, on_delete=django.db.models.deletion.SET_NULL, 
related_name='+', to='wagtailimages.image')),
+                ('site_logo', models.ForeignKey(blank=True, help_text='Site 
Logo', null=True, on_delete=django.db.models.deletion.SET_NULL, 
related_name='+', to='wagtailimages.image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='JsLink',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('sort_order', models.IntegerField(blank=True, editable=False, 
null=True)),
+                ('url', models.CharField(help_text='URL of JavaScript 
script.', max_length=255)),
+                ('extra_web_resources', 
modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='js_links', to='home.extrawebresources')),
+            ],
+            options={
+                'verbose_name': 'JS Link',
+            },
+        ),
+        migrations.CreateModel(
+            name='Navbar',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('logo_redirect_link', models.CharField(blank=True, 
default='#', help_text='Provide a redirection link for the logo or logo text 
Eg. (https://www.google.com/)', max_length=255, null=True)),
+                ('logo_with_text', models.CharField(choices=[('yes', 'Yes'), 
('no', 'No')], default='no', help_text='Choose yes if you want to display the 
text next to the logo', max_length=5)),
+                ('logo_width', models.IntegerField(blank=True, default='144', 
help_text='Provide a width for the logo', null=True)),
+                ('logo_height', models.IntegerField(blank=True, default='43', 
help_text='Provide a height for the logo', null=True)),
+                ('logo_text', models.CharField(blank=True, help_text='Give a 
title text as an alternative to logo. Eg.(SEAGRID)', max_length=255, 
null=True)),
+                ('logo_text_color', models.CharField(blank=True, 
help_text='Give a color for logo text if you have a logo text Eg.(#FFFFFF)', 
max_length=100, null=True)),
+                ('logo_text_size', models.IntegerField(blank=True, 
help_text='Give a text size as number of pixels Eg.(30)', null=True)),
+                ('logo', models.ForeignKey(blank=True, help_text='Brand Logo', 
null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', 
to='wagtailimages.image')),
+            ],
+            options={
+                'verbose_name_plural': 'Navbar',
+            },
+        ),
+        migrations.CreateModel(
+            name='RowBlankPageRelation',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('sort_order', models.IntegerField(blank=True, editable=False, 
null=True)),
+                ('container', 
wagtail.fields.StreamField([('full_width_container', 3), 
('max_width_container', 3)], blank=True, block_lookup={0: 
('wagtail.blocks.TextBlock', (), {'blank': True, 'help_text': 'Apply inline CSS 
styles to container wrapper.', 'required': False}), 1: 
('wagtail.blocks.CharBlock', (), {'help_text': 'Apply custom CSS classes to 
container wrapper. You can define CSS classes in a Custom CSS snippet.', 
'required': False}), 2: ('wagtail.images.blocks.ImageChooserBlo [...]
+                ('body', wagtail.fields.StreamField([('paragraph_block', 2), 
('image_block', 7), ('embed_block', 9), ('heading_block', 12), 
('bootstrap_jumbotron', 17), ('bootstrap_alert', 20), ('bootstrap_button', 21), 
('bootstrap_card', 33), ('bootstrap_carousel', 45), ('bootstrap_well', 47), 
('horizontal_rule', 50), ('bootstrap_media_object', 56), ('placeholder_block', 
57), ('font_awesome_icon_block', 60), ('iu_footer_block', 61), 
('bootstrap_embed_video', 64), ('expandable_rich_text_ [...]
+                ('page', 
modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='row', to='home.blankpage')),
+            ],
+            options={
+                'ordering': ['sort_order'],
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='RowCybergatewayHomePageRelation',
             fields=[
-                (
-                    "page_ptr",
-                    models.OneToOneField(
-                        on_delete=models.CASCADE,
-                        parent_link=True,
-                        auto_created=True,
-                        primary_key=True,
-                        serialize=False,
-                        to="wagtailcore.Page",
-                    ),
-                ),
+                ('id', models.BigAutoField(auto_created=True, 
primary_key=True, serialize=False, verbose_name='ID')),
+                ('sort_order', models.IntegerField(blank=True, editable=False, 
null=True)),
+                ('body', wagtail.fields.StreamField([('paragraph_block', 2), 
('image_block', 7), ('embed_block', 9), ('heading_block', 12), 
('bootstrap_jumbotron', 17), ('bootstrap_alert', 20), ('bootstrap_button', 21), 
('bootstrap_card', 33), ('bootstrap_carousel', 45), ('bootstrap_well', 47), 
('horizontal_rule', 50), ('bootstrap_media_object', 56), ('placeholder_block', 
57), ('font_awesome_icon_block', 60), ('iu_footer_block', 61), 
('bootstrap_embed_video', 64), ('expandable_rich_text_ [...]
+                ('page', 
modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='row', to='home.cybergatewayhomepage')),
             ],
             options={
-                "abstract": False,
+                'ordering': ['sort_order'],
+                'abstract': False,
             },
-            bases=("wagtailcore.page",),
         ),
     ]
diff --git a/airavata-cms/home/migrations/0002_create_homepage.py 
b/airavata-cms/home/migrations/0002_create_homepage.py
index a3e918ec5..82ebc21b0 100644
--- a/airavata-cms/home/migrations/0002_create_homepage.py
+++ b/airavata-cms/home/migrations/0002_create_homepage.py
@@ -6,10 +6,14 @@ def create_homepage(apps, schema_editor):
     ContentType = apps.get_model("contenttypes.ContentType")
     Page = apps.get_model("wagtailcore.Page")
     Site = apps.get_model("wagtailcore.Site")
+    Locale = apps.get_model("wagtailcore.Locale")
     HomePage = apps.get_model("home.HomePage")
 
-    # Delete the default homepage (of type Page) as created by 
wagtailcore.0002_initial_data,
-    # if it exists
+    # This migration depends on the full HomePage schema (FKs to wagtailimages 
/
+    # wagtailcore.Page), so it runs after wagtailcore is fully migrated. Remove
+    # the default Site and welcome Page created by 
wagtailcore.0002_initial_data
+    # (delete the Site first; Page.delete would otherwise be blocked by the 
FK).
+    Site.objects.filter(hostname="localhost").delete()
     page_content_type = ContentType.objects.get(
         model="page", app_label="wagtailcore"
     )
@@ -22,6 +26,9 @@ def create_homepage(apps, schema_editor):
         model="homepage", app_label="home"
     )
 
+    # The default locale exists by now (wagtailcore.0054_initial_locale).
+    locale = Locale.objects.first()
+
     # Create a new homepage
     homepage = HomePage.objects.create(
         title="Home",
@@ -32,6 +39,7 @@ def create_homepage(apps, schema_editor):
         depth=2,
         numchild=0,
         url_path="/home/",
+        locale_id=locale.pk,
     )
 
     # Create a site with the new homepage set as the root
@@ -53,10 +61,6 @@ def remove_homepage(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    run_before = [
-        ("wagtailcore", "0053_locale_model"),
-    ]
-
     dependencies = [
         ("home", "0001_initial"),
     ]
diff --git a/airavata-cms/home/models.py b/airavata-cms/home/models.py
index 5076f57a1..1761dbcd3 100644
--- a/airavata-cms/home/models.py
+++ b/airavata-cms/home/models.py
@@ -1,7 +1,904 @@
+import os
+
 from django.db import models
+from modelcluster.fields import ParentalKey
+from modelcluster.models import ClusterableModel
+from wagtail.admin.panels import (
+    FieldPanel,
+    InlinePanel,
+    MultiFieldPanel,
+    ObjectList,
+    TabbedInterface
+)
+from wagtail.fields import RichTextField, StreamField
+from wagtail.models import Orderable, Page
+from wagtail.snippets.models import register_snippet
+
+from .blocks import BaseStreamBlock, ContainerChoiceBlock, CssStreamBlock, Nav
+
+
+@register_snippet
+class Announcements(models.Model):
+    """
+    This provides editable text for the site announcements. Again it uses the 
decorator
+    `register_snippet` to allow it to be accessible via the admin. It is made
+    accessible on the template via a template tag defined in base/templatetags/
+    navigation_tags.py
+    """
+    announcement_text = models.CharField(
+        max_length=255,
+        help_text='Provide an announcement text',
+        default='Announcement Text'
+    )
+    announcement_link = models.CharField(
+        max_length=255,
+        help_text='Give a redirect link for announcement',
+        default='Announcement Link'
+    )
+
+    panels = [
+        FieldPanel('announcement_text'),
+        FieldPanel('announcement_link'),
+    ]
+
+    def __str__(self):
+        return "Announcement"
+
+    class Meta:
+        verbose_name_plural = 'Announcement'
+
+
+@register_snippet
+class NavExtra(models.Model):
+    """
+    This provides editable text for the site extra navbar which comes below
+    the main navbar. Again it uses the decorator `register_snippet` to allow
+    it to be accessible via the admin. It is made accessible on the template
+    via a template tag defined in base/templatetags/navigation_tags.py
+    """
+    nav = StreamField([
+        ('nav', Nav(max_num=1)),
+    ])
+    panels = [
+        FieldPanel('nav'),
+    ]
+
+    def __str__(self):
+        return "Nav extra"
+
+    class Meta:
+        verbose_name_plural = 'Nav extra'
+
+
+@register_snippet
+class CustomCss(models.Model):
+    """
+    Custom CSS
+    """
+
+    css = StreamField(
+        CssStreamBlock(),
+        verbose_name="CSS block",
+        blank=True,
+        null=True,
+        help_text="Write custom css and give comments as necessary",
+        default="")
+
+    panels = [
+        FieldPanel('css'),
+    ]
+
+    def __str__(self):
+        return "Custom Css"
+
+    class Meta:
+        verbose_name_plural = 'Custom CSS'
+
+
+@register_snippet
+class FooterText(models.Model):
+    """
+    This provides editable text for the site footer. Again it uses the 
decorator
+    `register_snippet` to allow it to be accessible via the admin. It is made
+    accessible on the template via a template tag defined in base/templatetags/
+    navigation_tags.py
+    """
+    footer = StreamField(
+        BaseStreamBlock(),
+        verbose_name="Footer content block",
+        blank=True,
+        null=True)
+
+    panels = [
+        FieldPanel('footer'),
+    ]
+
+    def __str__(self):
+        return "Footer"
+
+    class Meta:
+        verbose_name_plural = 'Footer'
+
+
+@register_snippet
+class Navbar(models.Model):
+    """
+    This provides editable text for the site header title. Again it uses the 
decorator
+    `register_snippet` to allow it to be accessible via the admin. It is made
+    accessible on the template via a template tag defined in base/templatetags/
+    navigation_tags.py
+    """
+    logo = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Brand Logo'
+    )
+
+    logo_redirect_link = models.CharField(
+        max_length=255,
+        help_text='Provide a redirection link for the logo or logo text Eg. 
(https://www.google.com/)',
+        null=True,
+        blank=True,
+        default='#',
+    )
+
+    boolean_choices = (
+        ("yes", "Yes"),
+        ("no", "No")
+    )
+
+    logo_with_text = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want to display the text next to the 
logo",
+        default="no")
+
+    logo_width = models.IntegerField(
+        help_text='Provide a width for the logo',
+        null=True,
+        blank=True,
+        default='144',
+    )
+
+    logo_height = models.IntegerField(
+        help_text='Provide a height for the logo',
+        null=True,
+        blank=True,
+        default='43'
+    )
+
+    logo_text = models.CharField(
+        max_length=255,
+        help_text='Give a title text as an alternative to logo. Eg.(SEAGRID)',
+        null=True,
+        blank=True,
+    )
+
+    logo_text_color = models.CharField(
+        max_length=100,
+        help_text='Give a color for logo text if you have a logo text 
Eg.(#FFFFFF)',
+        null=True,
+        blank=True,
+    )
+
+    logo_text_size = models.IntegerField(
+        help_text='Give a text size as number of pixels Eg.(30)',
+        null=True,
+        blank=True,
+    )
+
+    panels = [
+        FieldPanel('logo'),
+        FieldPanel('logo_redirect_link'),
+        FieldPanel('logo_width'),
+        FieldPanel('logo_height'),
+        FieldPanel('logo_text'),
+        FieldPanel('logo_with_text'),
+        FieldPanel('logo_text_size'),
+        FieldPanel('logo_text_color'),
+    ]
+
+    def __str__(self):
+        return "Navbar"
+
+    class Meta:
+        verbose_name_plural = 'Navbar'
+
+
+@register_snippet
+class CustomHeaderLinks(models.Model):
+    """
+    This provides feasibility for custom links inside header. Otherwise 
headerlinks are generated dynamically when a new page is created. The sublinks 
are restricted to 4 per link
+    """
+    header_link_text = models.CharField(
+        max_length=25,
+        help_text='Give a Link text',
+    )
+
+    header_link = models.CharField(
+        max_length=255,
+        help_text='Provide a redirect Link',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link_text1 = models.CharField(
+        max_length=25,
+        help_text='Give a Sub Link 1 text',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link_text2 = models.CharField(
+        max_length=25,
+        help_text='Give a Sub Link 2 text',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link_text3 = models.CharField(
+        max_length=25,
+        help_text='Give a Sub Link 3 text',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link_text4 = models.CharField(
+        max_length=25,
+        help_text='Give a Sub Link 4 text',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link1 = models.CharField(
+        max_length=255,
+        help_text='Provide a redirect Link for sublink 1',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link2 = models.CharField(
+        max_length=255,
+        help_text='Provide a redirect Link for sublink 2',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link3 = models.CharField(
+        max_length=255,
+        help_text='Provide a redirect Link for sublink 3',
+        null=True,
+        blank=True,
+    )
+
+    header_sub_link4 = models.CharField(
+        max_length=255,
+        help_text='Provide a redirect Link for sublink 4',
+        null=True,
+        blank=True,
+    )
+
+    body = models.CharField(
+        max_length=255,
+        help_text='Give a title text',
+        null=True,
+        blank=True,
+    )
+
+    panels = [
+        FieldPanel('header_link_text'),
+        FieldPanel('header_link'),
+        MultiFieldPanel([
+            MultiFieldPanel([
+                FieldPanel('header_sub_link_text1'),
+                FieldPanel('header_sub_link1'),
+            ]),
+            MultiFieldPanel([
+                FieldPanel('header_sub_link_text2'),
+                FieldPanel('header_sub_link2'),
+            ]),
+            MultiFieldPanel([
+                FieldPanel('header_sub_link_text3'),
+                FieldPanel('header_sub_link3'),
+            ]),
+            MultiFieldPanel([
+                FieldPanel('header_sub_link_text4'),
+                FieldPanel('header_sub_link4'),
+            ])
+        ], heading="Sub Links section", classname="collapsible"),
+    ]
+
+    def __str__(self):
+        return "Header Custom Links"
+
+    class Meta:
+        verbose_name_plural = 'Header Custom Links'
+
+
+@register_snippet
+class GatewayIcon(models.Model):
+    """
+    Image icon displayed in the header for logged in users.
+    """
+
+    icon = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Choose Gateway Icon with dimensions 70x70'
+    )
+    background_color = models.CharField(
+        max_length=10,
+        default="#EEEEEE",
+        help_text='Background color for icon (e.g. #FFFFFF)',
+    )
+
+    panels = [
+        FieldPanel('icon'),
+        FieldPanel('background_color'),
+    ]
+
+    def __str__(self):
+        return "Gateway Icon"
+
+    class Meta:
+        verbose_name_plural = 'Gateway Icon'
+
+
+@register_snippet
+class GatewayTitle(models.Model):
+    """
+    Title displayed in the header for logged in users.
+    """
+
+    title_text = models.CharField(
+        max_length=100,
+        help_text='Title to display to logged in users.',
+    )
+
+    panels = [
+        FieldPanel('title_text'),
+    ]
+
+    def __str__(self):
+        return "Gateway Title: {}".format(self.title_text)
+
+    class Meta:
+        verbose_name_plural = 'Gateway Title'
+
+
+@register_snippet
+class ExtraWebResources(ClusterableModel):
+    """
+    Links to CSS and JavaScript to be included in all pages.
+    """
+
+    panels = [
+        InlinePanel('css_links', label="CSS Links"),
+        InlinePanel('js_links', label="JS Links"),
+    ]
+
+    def __str__(self):
+        try:
+            return "Extra Web Resources: {}".format(", ".join(
+                [os.path.basename(link.url) for link in self.css_links.all()] +
+                [os.path.basename(link.url) for link in self.js_links.all()]))
+        except Exception:
+            return "Extra Web Resources"
+
+    class Meta:
+        verbose_name_plural = 'Extra Web Resources'
+
+
+class CssLink(Orderable):
+    url = models.CharField(
+        max_length=255,
+        help_text='URL of CSS stylesheet.'
+    )
+    panels = [
+        FieldPanel('url'),
+    ]
+    extra_web_resources = ParentalKey(ExtraWebResources,
+                                      on_delete=models.CASCADE,
+                                      related_name="css_links")
+
+    class Meta:
+        verbose_name = 'CSS Link'
+
+
+class JsLink(Orderable):
+    url = models.CharField(
+        max_length=255,
+        help_text='URL of JavaScript script.'
+    )
+    panels = [
+        FieldPanel('url'),
+    ]
+    extra_web_resources = ParentalKey(ExtraWebResources,
+                                      on_delete=models.CASCADE,
+                                      related_name="js_links")
 
-from wagtail.models import Page
+    class Meta:
+        verbose_name = 'JS Link'
 
 
 class HomePage(Page):
-    pass
+    """
+    The Home Page. This looks slightly more complicated than it is. You can
+    see if you visit your site and edit the homepage that it is split between
+    a:
+    - Hero area
+    - Body area
+    - A promotional area
+    - Moveable featured site sections
+    """
+
+    # Hero section of HomePage
+    image = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Homepage image'
+    )
+    hero_text = models.CharField(
+        max_length=255,
+        help_text='Write an introduction for the bakery',
+        null=True,
+        blank=True,
+    )
+    hero_cta = models.CharField(
+        verbose_name='Hero CTA',
+        max_length=255,
+        help_text='Text to display on Call to Action',
+        null=True,
+        blank=True,
+    )
+    hero_cta_link = models.ForeignKey(
+        'wagtailcore.Page',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        verbose_name='Hero CTA link',
+        help_text='Choose a page to link to for the Call to Action'
+    )
+
+    # Body section of the HomePage
+    body = StreamField(
+        BaseStreamBlock(),
+        verbose_name="Home content block",
+        blank=True,
+        null=True)
+
+    # Promo section of the HomePage
+    site_logo = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Site Logo'
+    )
+
+    features_text = RichTextField(
+        null=True,
+        blank=True,
+        help_text='Write some feature description'
+    )
+
+    feature_logo_1 = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Feature Logo 1'
+    )
+
+    feature_1_title = models.CharField(
+        max_length=255,
+        help_text='Feature Title 1'
+    )
+
+    feature_1_text = RichTextField(
+        null=True,
+        blank=True,
+        help_text='Write some feature 1 text description'
+    )
+
+    feature_logo_2 = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Feature Logo 2'
+    )
+
+    feature_2_title = models.CharField(
+        max_length=255,
+        help_text='Feature Title 2'
+    )
+
+    feature_2_text = RichTextField(
+        null=True,
+        blank=True,
+        help_text='Write some feature 2 text description'
+    )
+
+    feature_logo_3 = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Feature Logo 3'
+    )
+
+    feature_3_title = models.CharField(
+        max_length=255,
+        help_text='Feature Title 3'
+    )
+
+    feature_3_text = RichTextField(
+        null=True,
+        blank=True,
+        help_text='Write some feature 3 text description'
+    )
+
+    feature_logo_4 = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Feature Logo 4'
+    )
+
+    feature_4_title = models.CharField(
+        max_length=255,
+        help_text='Feature Title 4'
+    )
+
+    feature_4_text = RichTextField(
+        null=True,
+        blank=True,
+        help_text='Write some feature 4 text description'
+    )
+
+    custom_body_message = RichTextField(
+        null=True,
+        blank=True,
+        help_text='Write some custom body message!'
+    )
+
+    banner_image = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Choose Banner Image'
+    )
+
+    boolean_choices = (
+        ("yes", "Yes"),
+        ("no", "No")
+    )
+
+    show_navbar = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want to display the navbar on home page 
and no if you don't want to.",
+        default=True)
+
+    show_nav_extra = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the secondary navbar to show on home 
page or no if you don't want to",
+        default=True)
+
+    show_footer = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the Footer to show on home page or 
no if you don't want to",
+        default="yes")
+
+    content_panels = Page.content_panels + [
+        MultiFieldPanel([
+            FieldPanel('image'),
+            FieldPanel('hero_text', classname="full"),
+            MultiFieldPanel([
+                FieldPanel('hero_cta'),
+                FieldPanel('hero_cta_link'),
+            ])
+        ], heading="Hero section"),
+        FieldPanel('body'),
+        MultiFieldPanel([
+            FieldPanel('site_logo'),
+            FieldPanel('features_text'),
+            MultiFieldPanel([
+                FieldPanel('feature_logo_1'),
+                FieldPanel('feature_1_title'),
+                FieldPanel('feature_1_text'),
+            ]),
+            MultiFieldPanel([
+                FieldPanel('feature_logo_2'),
+                FieldPanel('feature_2_title'),
+                FieldPanel('feature_2_text'),
+            ]),
+            MultiFieldPanel([
+                FieldPanel('feature_logo_3'),
+                FieldPanel('feature_3_title'),
+                FieldPanel('feature_3_text'),
+            ]),
+            MultiFieldPanel([
+                FieldPanel('feature_logo_4'),
+                FieldPanel('feature_4_title'),
+                FieldPanel('feature_4_text'),
+            ])
+        ], heading="Feature section", classname="collapsible"),
+        FieldPanel('custom_body_message'),
+        FieldPanel('banner_image')
+    ]
+
+    customization_panels = [
+        FieldPanel('show_navbar'),
+        FieldPanel('show_nav_extra'),
+        FieldPanel('show_footer')
+    ]
+
+    edit_handler = TabbedInterface([
+        ObjectList(content_panels, heading='Content'),
+        ObjectList(customization_panels, heading='Customization'),
+        ObjectList(Page.promote_panels, heading='Promote'),
+        ObjectList(Page.settings_panels, heading='Settings',
+                   classname="settings"),
+    ])
+
+    def __str__(self):
+        return self.title
+
+
+class Row(models.Model):
+    body = StreamField(
+        BaseStreamBlock(), verbose_name="Row Content", blank=True, null=True
+    )
+
+    panels = [
+        FieldPanel('body'),
+    ]
+
+    class Meta:
+        abstract = True
+
+
+class BootstrapRow(Row):
+    container = StreamField(
+        ContainerChoiceBlock(),
+        null=True,
+        blank=True,
+        help_text="(Optional) Create a new Bootstrap container for this "
+                  "and following rows.")
+    body = StreamField(
+        BaseStreamBlock(), verbose_name="Row Content", blank=True, null=True
+    )
+
+    panels = [
+        FieldPanel('container'),
+        FieldPanel('body'),
+    ]
+
+    class Meta:
+        abstract = True
+
+
+class RowBlankPageRelation(Orderable, BootstrapRow):
+    page = ParentalKey('home.BlankPage',
+                       on_delete=models.CASCADE, related_name='row')
+
+
+class BlankPage(Page):
+    """
+    The Blank Template Page. You can see if you visit your site and edit the 
blank page. Used to create free form content
+    """
+
+    boolean_choices = (
+        ("yes", "Yes"),
+        ("no", "No")
+    )
+
+    show_navbar = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want to display the navbar on home page 
and no if you don't want to.",
+        default="yes")
+
+    show_nav_extra = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the secondary navbar to show on home 
page or no if you don't want to",
+        default="yes")
+
+    show_footer = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the Footer to show on home page or 
no if you don't want to",
+        default="yes")
+
+    show_announcements = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the Announcements to show up on home 
page or no if you don't want to",
+        default="yes")
+
+    content_panels = Page.content_panels + [
+        InlinePanel("row", label="row")
+    ]
+
+    customization_panels = [
+        FieldPanel('show_navbar'),
+        FieldPanel('show_nav_extra'),
+        FieldPanel('show_footer'),
+        FieldPanel('show_announcements')
+    ]
+
+    edit_handler = TabbedInterface([
+        ObjectList(content_panels, heading='Content'),
+        ObjectList(customization_panels, heading='Customization'),
+        ObjectList(Page.promote_panels, heading='Promote'),
+        ObjectList(Page.settings_panels, heading='Settings',
+                   classname="settings"),
+    ])
+
+    def __str__(self):
+        return self.title
+
+
+class RowCybergatewayHomePageRelation(Orderable, Row):
+    page = ParentalKey('home.CybergatewayHomePage',
+                       on_delete=models.CASCADE, related_name='row')
+
+
+class CybergatewayHomePage(Page):
+    """
+    The Cybergateway themed template Page
+    """
+
+    # Hero section of HomePage
+    site_logo = models.ForeignKey(
+        'wagtailimages.Image',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name='+',
+        help_text='Site Logo Image'
+    )
+
+    site_link = models.CharField(
+        max_length=255,
+        default="#",
+        help_text='Give a site redirect link',
+    )
+
+    site_text = models.CharField(
+        max_length=50,
+        default="#",
+        help_text='Give a Site Name',
+    )
+
+    site_header = models.CharField(
+        max_length=70,
+        default="#",
+        help_text='Give a Site Header Name',
+    )
+
+    site_link1 = models.CharField(
+        max_length=70,
+        default="#",
+        help_text='Give a Site Nav Link [1]',
+    )
+
+    site_link_text1 = models.CharField(
+        max_length=70,
+        help_text='Give a Site Nav Link Text [1]',
+    )
+
+    site_link2 = models.CharField(
+        max_length=70,
+        default='#',
+        help_text='Give a Site Nav Link [2]',
+    )
+
+    site_link_text2 = models.CharField(
+        max_length=70,
+        help_text='Give a Site Nav Link Text [2]',
+    )
+
+    site_link3 = models.CharField(
+        max_length=70,
+        default="#",
+        help_text='Give a Site Nav Link [3]',
+    )
+
+    site_link_text3 = models.CharField(
+        max_length=70,
+        help_text='Give a Site Nav Link Text [3]',
+    )
+
+    contact = StreamField(
+        BaseStreamBlock(),
+        verbose_name="Contact Info Block",
+        blank=True,
+        null=True)
+
+    footer = StreamField(
+        BaseStreamBlock(),
+        verbose_name="Footer Content Block",
+        blank=True,
+        null=True)
+
+    boolean_choices = (
+        ("yes", "Yes"),
+        ("no", "No")
+    )
+
+    show_navbar = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want to display the navbar on home page 
and no if you don't want to.",
+        default="yes")
+
+    show_nav_extra = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the secondary navbar to show on home 
page or no if you don't want to",
+        default="yes")
+
+    show_footer = models.CharField(
+        choices=boolean_choices,
+        max_length=5,
+        help_text="Choose yes if you want the Footer to show on home page or 
no if you don't want to",
+        default="yes")
+
+    content_panels = Page.content_panels + [
+        MultiFieldPanel([
+            FieldPanel('site_logo'),
+            FieldPanel('site_link'),
+            FieldPanel('site_text'),
+            FieldPanel('site_header'),
+            FieldPanel('site_link1'),
+            FieldPanel('site_link_text1'),
+            FieldPanel('site_link2'),
+            FieldPanel('site_link_text2'),
+            FieldPanel('site_link3'),
+            FieldPanel('site_link_text3'),
+        ], heading="Navbar Section"),
+        InlinePanel("row", label="row"),
+        FieldPanel('contact'),
+        FieldPanel('footer'),
+    ]
+
+    customization_panels = [
+        FieldPanel('show_navbar'),
+        FieldPanel('show_nav_extra'),
+        FieldPanel('show_footer'),
+    ]
+
+    edit_handler = TabbedInterface([
+        ObjectList(content_panels, heading='Content'),
+        ObjectList(customization_panels, heading='Customization'),
+        ObjectList(Page.promote_panels, heading='Promote'),
+        ObjectList(Page.settings_panels, heading='Settings',
+                   classname="settings"),
+    ])
+
+    def __str__(self):
+        return self.title
diff --git a/airavata-cms/requirements.txt b/airavata-cms/requirements.txt
index d4cdd6f08..002ed5cee 100644
--- a/airavata-cms/requirements.txt
+++ b/airavata-cms/requirements.txt
@@ -1,2 +1,3 @@
 Django>=6,<6.1
 wagtail>=7.4,<7.5
+wagtailcodeblock>=1.30,<2

Reply via email to