JaredOzzy
JaredOzzy

Reputation: 221

Filter Queryset in Wagtail ModelAdmin not working

I have a menu item that contains 4 resources, each language, if the user goes to EnResources i would like it to only display the Resources where the language field contains 'en' and the same with the other languages. So the issue is it is only ever getting the en items, no matter which menu item i choose its always the en items, not the FrResources or anything.

I am following the docs http://docs.wagtail.io/en/v2.5.1/reference/contrib/modeladmin/indexview.html#modeladmin-get-queryset

Models.py

class Resource(models.Model):
    language = models.CharField(max_length=255, choices=constants.LANGUAGES)
    title = models.CharField(blank=True, max_length=255)
    resource_type = models.CharField(
        choices=constants.RESOURCE_TYPES,
        max_length=255
    )
    description =  models.TextField()
    link = StreamField(
        blocks.BasicLinkBlock(max_num=1),
        blank=True,
    )


    panels = [
        FieldPanel('language'),
        FieldPanel('title'),
        FieldPanel('resource_type'),
        FieldPanel('description'),
        StreamFieldPanel('link'),
    ]

constants.py

RESOURCE_TYPES = (
    ('Documentation', 'Documentation'),
    ('Whitepaper', 'Whitepaper'),
    ('Webinar', 'Webinar'),
    ('Video', 'Video'),
    ('Testimonial', 'Testimonial'),
    ('ProductSheet', 'ProductSheet'),
)

LANGUAGES = (
    ('en', 'English'),
    ('fr', 'French'),
    ('be-fr', 'Belgique'),
    ('be-nl', 'Nederlands'),
)

WagtailHooks.py

class ResourceAdmin(ModelAdmin):
    model = models.Resource
    menu_label = 'Resources'
    menu_icon = 'snippet'  # change as required
    list_display = (
        'resource_type',
        'title',
    )
    list_filter = (
        'resource_type',
    )
    search_fields = (
        'title',
        'business_email',
    )

class EnResourceAdmin(ResourceAdmin):
    menu_label = 'English Resources'

    def get_queryset(self, request):

        qs = super().get_queryset(request)
        return qs.filter(language='en')


class FrResourceAdmin(ResourceAdmin):
    menu_label = 'French Resources'

    def get_queryset(self, request):
        qs = super().get_queryset(request)

        return qs.filter(language='fr')


class BeResourceAdmin(ResourceAdmin):
    menu_label = 'Belgium Resources'
    def get_queryset(self, request):
        qs = super().get_queryset(request)

        return qs.filter(language='be-fr')


class NlResourceAdmin(ResourceAdmin):
    menu_label = 'Nederlands Resources'

    def get_queryset(self, request):
        qs = super().get_queryset(request)

        return qs.filter(language='be-nl')


class ResourceAdminGroup(ModelAdminGroup):
    menu_label = 'Resources'
    menu_icon = 'snippet'  # change as required
    menu_order = 1000  # (000 being 1st, 100 2nd)
    items = (
        EnResourceAdmin,
        FrResourceAdmin,
        BeResourceAdmin,
        NlResourceAdmin,
    )

modeladmin_register(ResourceAdminGroup)

EDIT: I started doing a little more research and i found that according to the Django docs on default_manager. https://docs.djangoproject.com/en/2.2/topics/db/managers/#django.db.models.Model._default_manager

If you use custom Manager objects, take note that the first Manager Django encounters (in the order in which they’re defined in the model) has a special status. Django interprets the first Manager defined in a class as the “default” Manager, and several parts of Django (including dumpdata) will use that Manager exclusively for that model. As a result, it’s a good idea to be careful in your choice of default manager in order to avoid a situation where overriding get_queryset() results in an inability to retrieve objects you’d like to work with.

You can specify a custom default manager using Meta.default_manager_name.

If you’re writing some code that must handle an unknown model, for example, in a third-party app that implements a generic view, use this manager (or _base_manager) rather than assuming the model has an objects manager.

Note the last part of the first paragraph. I think that is exactly what is happening here.

Upvotes: 2

Views: 1141

Answers (2)

Ramesh-X
Ramesh-X

Reputation: 5055

Your approach is correct. After you run your code, you will see 4 menu items with the names you have given, but as you said, all those pages will show the same data.

If you closely look at the URL that your browser has gone to, you will see that no matter which menu item you click, browser redirects to the same URL. You need to fix that.

To do that you have to make use of url_helper_class variable. That accepts an instance of AdminURLHelper class. You can find the source code of that class here.

If you read the source code, you will see that this class is generating urls using the app_label and model_name. But as those two are same in this instance, no matter which menu item you click, this generates the same URL.

You can override all these methods to perform a custom URL generation as you like. But there is a simpler hack.

If you looked at the source code, you will see that the app_label and the model_name is taken from the model's meta data. As those are the only two things that this class is getting from the meta data, you can simply override it with a custom class.

class MyOpts:
    def __init__(self, model_name, app_label) -> None:
        self.model_name = model_name
        self.app_label = app_label

class MyUrlHelper(AdminURLHelper):

    def __init__(self, model):
        super().__init__(model)
        self.opts = MyOpts(XXXX, model._meta.app_label)

Instead of passing the model_name from meta data, you have to pass something else (stated as XXXX in the code) to make URLs unique for each menu item.

def get_helper(name):
    class MyUrlHelper(AdminURLHelper):

        def __init__(self, model):
            super().__init__(model)
            model_name = f"{model._meta.model_name}-{name}"
            self.opts = MyOpts(model_name, model._meta.app_label)

    return MyUrlHelper


class EnResourceAdmin(ResourceAdmin):
    menu_label = 'English Resources'
    url_helper_class = get_helper('en')

Now you will see that the menu items redirects to different pages and your filter have applied correctly. Hope this helps!!

Upvotes: 1

gsundberg
gsundberg

Reputation: 547

This is a great question and I had a similar issue recently. You are correct that the first manager will be used as the default manager. A good way to get the outcome you are looking for is to define proxy models for each case of Resource, and add custom managers for each of the proxy models. Then you can modify get_queryset to return only instances where language = 'some language'.

Upvotes: 0

Related Questions