khashashin
khashashin

Reputation: 1137

Wagtail Page title overwriting

enter image description here

I have this model above, and I would like to remove the title. Or rename it. Actually the match number itself is a title of this model. So I need one of the following options:

My model:

class Match(Page):
    match_number = models.PositiveSmallIntegerField(blank=True)
    team_1 = models.ForeignKey(
        TeamRooster,
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    team_1_color = ColorField(default='#ff0000', blank=True)
    team_1_score = models.PositiveSmallIntegerField(blank=True)
    team_2 = models.ForeignKey(
        TeamRooster,
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    team_2_color = ColorField(default='#0066ff', blank=True)
    team_2_score = models.PositiveSmallIntegerField(blank=True)
    match_starts_at = models.DateTimeField()


    parent_page_types = ['Matches']

    content_panels = [
        FieldPanel('title'),
        FieldPanel('match_number', classname="6"),
        FieldPanel('match_starts_at', classname="6"),
        MultiFieldPanel([
            FieldPanel('team_1', classname="12"),
            FieldPanel('team_1_color', classname="6"),
            FieldPanel('team_2_score', classname="6"),
        ], heading="Team 1"),
        MultiFieldPanel([
            FieldPanel('team_2', classname="12"),
            FieldPanel('team_2_color', classname="6"),
            FieldPanel('team_2_score', classname="6"),
        ], heading="Team 2"),
    ]

Upvotes: 3

Views: 5465

Answers (2)

Yannic Hamann
Yannic Hamann

Reputation: 5225

To tackle the issue spotted by @Mark Chackerian you could define a custom base_form_class for your wagtail page. Now, instead of overwriting the clean function of the model you could hook into the save method of your custom form. So you don't have to set a default slug for EVERY wagtail page:

forms.py:

from django import forms
from django.utils.translation import gettext_lazy as _
from django.utils.text import slugify
from django.utils.html import strip_tags
from wagtail.admin.forms import WagtailAdminPageForm


class NoTitleForm(WagtailAdminPageForm):
    title = forms.CharField(required=False, disabled=True, help_text=_('Title is auto-generated.'))
    slug = forms.SlugField(required=False, disabled=True, help_text=_('Slug is auto-generated.'))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.initial['title']:
            self.initial['title'] = _('auto-generated-title')
        if not self.initial['slug']:
            self.initial['slug'] = _('auto-generated-slug')

    def save(self, commit=True):
        page = super().save(commit=False)

        new_title = strip_tags(self.cleaned_data['rtf_title'])
        page.title = new_title
        page.slug = slugify(new_title)

        if commit:
            page.save()
        return page

models.py:

# ... imports omitted for brevity


class PageWithRtfTitle(Page):

    base_form_class = NoTitleForm
    rtf_title = RichTextField()

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel('rtf_title'),
            ],
            heading=_('Metadata')
    )]
    # ...

Still, this solution may not be ideal for some use cases. Both approaches overwrite the slug every time the title changes. You may not want that - but you could adapt the code to your needs.

Upvotes: 1

LB Ben Johnston
LB Ben Johnston

Reputation: 5196

A way to achieve this is to actually remove the title from the content_panels. Then manually create your title and slug fields inside your Page's clean method. You will also need to set a default slug value for your model.

Example

    from django.utils.text import slugify
    # all other imports (Page, models, panels etc)


    class MatchPage(Page):
        """A page that represents a single game match."""

        number = models.IntegerField(
            unique=True,  # must be unique for use in slug
            help_text="Add the unique number of this Match.")
        starts_at = models.DateTimeField(
            blank=True, # allows blank values (ie. not required)
            help_text="Date and time of when this Match starts.")

        content_panels = [
            # title not present, title should NOT be directly editable
            MultiFieldPanel([
                FieldRowPanel([
                    FieldPanel('number', classname="col6"),
                    FieldPanel('starts_at', classname="col6"),
                ]),
            ], 'Match'),
            # other field panels... teams etc
        ]

        def clean(self):
            """Override the values of title and slug before saving."""
            # super(MatchPage, self).clean() # Python 2.X syntax
            super().clean()
            new_title = 'Match %s' % self.number
            self.title = new_title
            self.slug = slugify(new_title)  # slug MUST be unique & slug-formatted


    # set a default blank slug for when the editing form renders
    # we set this after the model is declared
    MatchPage._meta.get_field('slug').default = 'default-blank-slug'

Details

This is can be a confusing issue with Wagtail as the Page model (separate from your MatchPage model) must have certain fields (title, slug). The editing interface also builds the slug field on the client side (in Javascript), and it relies on the title field being present.

So you will have to take care of both generating the title and the slug field yourself. It is important that the slug field is both unique and slugified.

In the example code there is no consideration of the editor manually setting the slug themselves (in the Promote panel), no matter what the user sets the slug to, it will always override to match-123.

The title field can be any string, but is required.

Remember that Wagtail does not dictate how you use the title field in any way in how you render your templates. It is only really the model restrictions and the admin interface that assumes the existence of a title.

There is an old and ongoing discussion on the Wagtail Github page about this item: https://github.com/wagtail/wagtail/issues/161

Code Nitpicks

  • It is a reasonable convention to name your Page models in the format MySomethingPage with Page at the end, this helps you and other developers know that this thing is a Page.
  • Your fields do not need to be prefixed with the page's model name (eg. match_something or match_other_thing). This makes it confusing down the road as you will have to be typing match_page.match_number when match_page.number makes more intuitive sense. Or if you do use this convention, use it on every single field like match_page.match_team_1.
  • In your code you are creating multiple teams manually, it might be nicer to explore doing this via a related model and InlinePanel, it will mean much less duplication of code. See docs about InlinePanels & Model Clusters

Upvotes: 13

Related Questions