Search code examples
djangodjango-admin

Django Admin: How to Set a Default Filter Without Conflicting with User-Selected Filters?


I'm working with the Django Admin panel and need to set a default filter for a model's changelist view. When the user opens the changelist page for the first time, I want it to automatically filter by a specific value (e.g., status="A"). However, when a user selects a different filter (e.g., status="B"), the default filter (status="A") should not be applied.

What I Have Tried: I've overridden the changelist_view method in my ModelAdmin class to check if any status filter is present in the request. If not, I add the default filter (status=A). Here’s what my code looks like:

from django.contrib import admin
from django.shortcuts import redirect
from django.urls import reverse
from .models import Person  # Replace with your actual model

class PersonAdmin(admin.ModelAdmin):
    list_display = ('name', 'status')  # Adjust fields to match your model
    list_filter = ('status',)  # Adjust filters to match your model

    def get_queryset(self, request):
        # Get the original queryset without any default filtering
        qs = super().get_queryset(request)
        return qs

    def changelist_view(self, request, extra_context=None):
        # Check if any filters related to 'status' are already applied by looking at request.GET
        if 'status' not in request.GET and 'status__exact' not in request.GET:
            # Redirect to the same changelist URL with the default filter applied
            query = request.GET.copy()  # Make a mutable copy of GET parameters
            query['status'] = 'A'  # Set the default filter value for 'status' to 'A'
            
            # Redirect to the changelist URL with the correct query string
            return redirect(
                f"{reverse('admin:Human_Resource_Management_person_changelist')}?{query.urlencode()}"
            )

        # Call the original changelist_view method
        return super().changelist_view(request, extra_context=extra_context)

# Register your admin class with the associated model
admin.site.register(Person, PersonAdmin)

The code works initially: when the changelist view loads without any filters, it defaults to status=A. However, if a user selects another filter, such as status=B, the URL ends up looking like this: ?status=A&status__exact=B This results in both filters being applied simultaneously, which is incorrect. I want to only have the user-selected filter (e.g., status=B) applied and not keep the default filter (status=A) in the URL.

What I've Tried: I tried modifying request.GET directly, but that caused an AttributeError since request.GET is immutable. I also tried more complex condition checks, but I can't seem to prevent the status=A filter from staying in the URL when another filter is selected. What I'm Looking For: I'm looking for a solution that:

Applies the default filter (status=A) only when no other status filter is present. Removes the default filter when the user selects a different filter (status=B) to avoid conflicting filters in the URL. Any insights or suggestions on how to achieve this would be greatly appreciated!

Thank you!


Solution

  • The filter throws out the previous one with the same name. But you use status, not status__exact, hence the problem. Use status__exact instead:

    class PersonAdmin(admin.ModelAdmin):
        # …
    
        def changelist_view(self, request, extra_context=None):
            if 'status' not in request.GET and 'status__exact' not in request.GET:
                # Redirect to the same changelist URL with the default filter applied
                query = request.GET.copy()  # Make a mutable copy of GET parameters
                query['status__exact'] = 'A'  # Set the default filter value for 'status' to 'A'
    
                # Redirect to the changelist URL with the correct query string
                return redirect(
                    f"{reverse('admin:Human_Resource_Management_person_changelist')}?{query.urlencode()}"
                )
    
            # Call the original changelist_view method
            return super().changelist_view(request, extra_context=extra_context)