How can I implement a global, implicit filter in Django admin?
Asked Answered
M

2

6

A lot of my models have a foreign key to a "Company" model. Every logged in user can be part of one or more companies (User m2m Company, not null).

I would like the current admin user to have "Company goggles" on, i.e. a select list, on the admin index page or maybe the base header, where they can switch their "current" Company. Doing that should automatically apply a "company equals" filter - for models that have a foreign key to Company - in addition to any other filters.

What's the best way to achieve this?

NB: This is meant as a comfort function for the admin interface, actual protection of models is not necessary at this stage (client views do need that but I can just use a custom Manager and lookup via request.user there).

My current idea is:

  1. Store current company in session.

  2. Use middleware to look up current company from session, and append the company to all relevant links:

    a) change_list: (?/&)"company__eq=42"

    b) change_view "add?company=42" for models that have a foreign key to Company.

    This may require to reverse or pattern match the URLs to find out their model and check it for presence of the foreign key (or I might prepare that list beforehand to improve performance).

  3. Include in each ModelAdmin form the foreign key field, but hide it via CSS, so that change_view add ("new") includes the preset foreign key value from the link without mentioning it.

Do you find this a viable approach?

If http://code.djangoproject.com/ticket/10761 was implemented I guess I could just specify a custom queryset which reads the current company from request.session and be done with it. Maybe better to fast-track (=make and submit patch) that ticket instead?

EDIT: or maybe just redefine the queryset() method on every ModelAdmin that needs it / has the foreign key?

Merous answered 17/3, 2011 at 20:2 Comment(0)
S
5

My vote is is for overriding ModelAdmin.queryset, since you conveniently have access to the request there. Override save_model for point 3.

 class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.session.get('company_goggles'):
             return qs.filter(company=request.session['company_goggles'])
        return qs

If you have many models, I'd subclass ModelAdmin as something like GogglesAdmin and define a field / default to pull the fieldname from and also the pre-save auto injecting of company.

class CompanyGogglesAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.session.get('company_goggles'):
            return qs.filter(**{ getattr(self, 'company_field', 'company') : 
                          request.session['company_goggles'] })

By the way, I really like this "company goggles" terminology.

Streeto answered 17/3, 2011 at 20:41 Comment(4)
Thanks for this. Almost identical to lazerscience's answer, plus thank you for the idea with the base class and field name parameter. I think I'm all set now. Back to coding. :-)Merous
NB: It should be "request.session.get('company_goggles', None)", not getattr() - request.session is dictionary-like.Merous
Thanks Danny, I dunno what I was thinking. That stream of consciousness style coding!Gaff
The queryset method is now called get_queryset.Unpleasant
C
1

To answer the last question: If you just want to display certain items of your queryset you can override the ModelAdmin's queryset() method withoud problems. For example, if you'd set a company in the current session. You can furthermore overwrite the save_model() method to have the company ForeignKey always point to the user's company when saving the form:

class MyAdmin(admin.ModelAdmin):
    def queryset(self, request):
        company = request.session.get('company', None)
        qs = self.model._default_manager.get_query_set()
        if not company is None:
            qs = qs.filter(company=company)
        return qs

    def save_model(self, request, obj, form, change):
        company = request.session.get('company', None):
        instance = form.save(commit=False)
        if not change or not instance.company:
            instance.company = company
        instance.save()
        form.save_m2m()
        return instance
Caruthers answered 17/3, 2011 at 20:42 Comment(2)
Ah, save_model(), of course! Thank you, I think all boxes are ticked on this now. :-)Merous
And if you have many classes (and are too lazy to trivially find their base), how do you globally register that - say - for all forms in a project?Sisak

© 2022 - 2024 — McMap. All rights reserved.