velis
velis

Reputation: 10025

How to copy page-privacy to Block in Wagtail?

Pages have this privacy setting where one can set who is allowed to see a particular page.

My documentation requires a bit more granular lever where for a specific block one could also set to restrict its visibility.

I would be quite content with a groups chooser, but obviously I can't place a ManyToManyRelation in the Page model. Rather I would do it in the Block I need specially handled.

So I tried to define the block like so:

class MyBlock(StructBlock):
    visible_groups = ListBlock(ChooserBlock(
        label=_('Limit view to groups'), target_model='django.contrib.auth.groups', 
        required=False, blank=True
    ))

But Wagtail complains that ChooserBlock had been declared without target_model attribute. I guess lack of constructor doing so is an indicator enough that ChooserBlock itself is not meant for operations like this.

How could I properly declare my Block such that I could select the groups and then later - while rendering - correctly identify them and match them against user's groups?

Upvotes: 0

Views: 309

Answers (1)

LB Ben Johnston
LB Ben Johnston

Reputation: 5186

ChooserBlock is not meant to be used stand-alone, instead it is a base class that is meant to be used to build up other types of chooser.

Option 0 - Use a related model

  • While I understand your desire not to use a model relationship, you might end up creating a lot of complexity by attempting to use StreamFields here.
  • It might be worthwhile rethinking the problem in terms of basic relationships, your page as a list of content items, each content item has a one to many relationship to an auth group AND has some content (rich text, etc).
  • You could do this with a normal model that relates to your page, and using Wagtail's InlinePanel allow the user to create many of these content items each with its own auth group set AND content. Remember the inner content on other models can still be StreamField based but the main connections in this method would be normal Django relationships.
  • Remember that StreamField links are not as robust (e.g. if auth groups get deleted, the links may become stale in your StreamField and there are no easy ways to block this), plus migrations become quite complicated if you want to move things around more in the future.

Option 1 - Use SnippetChooser

The quickest way to get this working would be to use the SnippetChooserBlock and register the Django auth groups as a snippet. One caveat to this approach is that the model will be visible in your snippets menu (if enabled) in the admin UI.

Firstly, register the model as a snippet somewhere central (e.g. wagtail_hooks.py)

from django.contrib.auth.models import Group
from wagtail.snippets.models import register_snippet

# ... other hooks things
register_snippet(Group)

Then build your custom StructBlock using the SnippetChooser wherever you keep your Blocks.

from django.contrib.auth.models import Group
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.core.blocks import ListBlock

class PrivacyBlock(StructBlock):
    visible_groups = ListBlock(
        SnippetChooserBlock(
            Group,
            label='Limit view to groups',
            required=False,
            blank=True
        )
    )

    class Meta:
        icon = "user"

Finally, you can use this in your PageModel like any other StructBlock.

from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, StreamFieldPanel
from wagtail.core.fields import StreamField

from myapp.blocks import PrivacyBlock


class BlogPage(Page):
    # ... other fields

    privacy_content = StreamField([
        ('privacy', PrivacyBlock(blank=True))
    ])

    content_panels = Page.content_panels + [
        # ... other panels
        StreamFieldPanel('privacy_content'),
    ]

Option 2 - Build or use a generic model choose

You can extend the ChooserBlock to build your own chooser, but this gets complicated quickly as you need to also build a custom model chooser. The code below is NOT fully function but gives you an idea of what might be needed.

If you can use additional libraries, it might be worth looking into adding a Wagtail Generic Chooser, here are some quick results from Google (I have not used these).

Remember that a chooser block is specific to StreamField implementations and chooser akin to a specific type of Django widget. You will need to build or get both if you want to use StreamFields for this implementation.

If you need to build your own I recommend to dig into the Wagtail internals to get an idea of how some other choosers are built.

POC (non functional) code example


from wagtail.admin.widgets import AdminChooser
from django.utils.functional import cached_property

class ModelChooser(AdminChooser):
    # ... implement __init__ etc


class AuthGroupBlock(ChooserBlock):

    @cached_property
    def target_model(self):
        from django.contrib.auth.models import Group
        return Group

    @cached_property
    def widget(self):
        return ModelChooser(self.target_model)


class PrivacyBlock(StructBlock):
    visible_groups = ListBlock(
        AuthGroupBlock(
            label='Limit view to groups',
            required=False,
            blank=True
        )
    )

    class Meta:
        icon = "user"


class PrivacyBlock(StructBlock):
    visible_groups = ListBlock(
        SnippetChooserBlock(
            Group,
            label='Limit view to groups',
            required=False,
            blank=True
        )
    )

    class Meta:
        icon = "user"

Upvotes: 2

Related Questions