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