0

I need to create a reusable element (cta button) that I can include in many places throughout the page.

These cta buttons are used ~8 times throughout the design. How can I do this without copy-pasting?

Similar to this: Ways to create reusable sets of fields in Wagtail? Except I must be able to use the set several times on a single page.

This is what I am trying to do:

class HomePage(Page):
    
    template = "home/home_page.html"

    hero_heading = models.CharField(max_length=50)
    hero_subheading = models.CharField(max_length=255)


    hero_cta1 = HeroCTA1() # abstract reusable model
    hero_cta2 = HeroCTA2()

    content_panels = Page.content_panels + [
        FieldPanel("hero_heading"),
        FieldPanel("hero_subheading"),
        hero_cta1.panels,
        hero_cta2.panels,
    ]

My attempt at a reusable CTAButton class:

class CTAButton(models.Model):
    text = RichTextField(max_length=25, features=["bold"])
    url = models.URLField(null=True, blank=True)
    page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="%(app_label)s_%(class)s_page",
    )

    panels = MultiFieldPanel(
            [
                FieldPanel("text"),
                FieldPanel("url"),
                PageChooserPanel("page"),
            ],
            heading="CTA Button Fields",
            classname="collapsible",
        )

    class Meta:
        abstract = True


class HeroCTA1(CTAButton):
    pass

class HeroCTA2(CTAButton):
    pass

Except this doesn't work :/ I am encountering "HomePage has no field named 'page'"

Shouldn't this break on 'text' since it's before 'page'?

Any advice on how to proceed?

Jehy
  • 119
  • 2
  • 12

3 Answers3

1

checkout django documentation for model inheritance there are three method you can achieve model inheritance in django wagtail is made from djanog so you can also use abstract model inheritance in that for more information checkout this documentation https://docs.djangoproject.com/en/3.0/topics/db/models/#model-inheritance

Kishan Parmar
  • 819
  • 6
  • 8
  • I used your documentation link to try and make a proper inheritance. My abstract class seems to be working, but I am still having troubles. – Jehy Jul 29 '20 at 02:48
1

If your page design has 8 places where a CTA button can go, then perhaps it's more useful to treat it as a flexible sequence of elements where CTA buttons can be freely mixed in with other types of content chosen by the page author, rather than a fixed layout with specific elements at specific points. Wagtail's StreamField provides that kind of flexible layout: https://docs.wagtail.io/en/stable/topics/streamfield.html

gasman
  • 23,691
  • 1
  • 38
  • 56
  • In many cases your answer would be correct. But I don't actually want the page author to use this. I would like to reuse these elements in the blocks and pages themselves, then the authors can use those blocks and pages in StreamFields. – Jehy Jul 28 '20 at 15:33
0

I followed the suggestion of @gasman and broke the template out into blocks and used a mixin to keep it DRY.

This solution extracts the href function and allows it to be used in multiple CTA blocks, but it hardcodes the naming convention. Future readers can probably find a smarter way to do this. To use multiple elements, such as TwoCTAMixin, I just extend the base mixin and add cta1_text, etc.

CTAStructValue is required to access the value as you would expect with an @property variable. More info here.

def get_cta_href_value(cta, varname="cta"):
    """Function that returns the href link with a value provided.
       The value is returned in this order: (url > page > #)."""

    url_name = f"{varname}_url"
    page_name = f"{varname}_page"

    if cta[url_name] and cta[url_name] != "":
        return cta[url_name]
    elif cta[page_name] and cta[url_name] != "":
        return cta[page_name]
    return "#" # broken link, return something non-volatile


class CTAStructValue(blocks.StructValue):
    """Calculated properties for CTAMixin."""

    @property
    def cta_href(self):
        return get_cta_href_value(self)


class CTAMixin(blocks.StructBlock):
    """Mixin that includes a single CTA element."""

    cta_text = blocks.CharBlock(required=True, help_text="Text to display on the button")
    cta_url = blocks.URLBlock(required=False, help_text="URL the button directs to")
    cta_page = blocks.PageChooserBlock(required=False, help_text="Page the button directs to")

    class Meta:
        abstract = True


class SomeSectionBlock(CTAMixin):
    section_heading = blocks.CharBlock()

    class Meta:
        template = "blocks/some_section_block.html"

some_section_block.html

<section>
    <div class="wrapper some-section">
        <h2>{{ self.section_heading }}</h2>        

        <a href="{{ self.cta_href }}">
        <button>{{ self.cta_text }}</button>
        </a>
    </div>
</section>
Jehy
  • 119
  • 2
  • 12