Why Django adds entered values in custom filter into quotes and square brackets?

Hi all!
I have custom filters:

class InputFilter(admin.SimpleListFilter):
    template = 'admin/input_filter.html'

    def lookups(self, request, model_admin):
        # Dummy, required to show the filter.
        return ((),)

    def choices(self, changelist):
        # Grab only the "all" option.
        all_choice = next(super().choices(changelist))
        all_choice['query_parts'] = (
            (k, v)
            for k, v in changelist.get_filters_params().items()
            if k != self.parameter_name
        )
        yield all_choice

class HostnameFilter(InputFilter):
    parameter_name = 'hostname'
    title = 'Hostname'

    def queryset(self, request, queryset):
        if self.value() is not None:
            switch_obj = Switch.objects.get(hostname=self.value().strip())
            return queryset.filter(switch=switch_obj)

When I fill in multiple filters it looks like this after enter in vlan-filter (previously the hostname was entered without ‘’):

Before updating Django from 4.2 to 5.2 there was no such problem. What can be done to avoid writing logic for each filter to trim added quotes and square brackets?

The square brackets and quotes suggests that the value is a list, not a string. It’s a list containing one string.

I’m not very familiar with writing custom list filters, so don’t know where you’ve gone wrong, but maybe that will help point you in the right direction (or until someone else comes along).

In python syntax this is true. But in browser and admin.py debug you can see that this is something like str(list(‘<entered_value>’)):


However, if you fill in only one filter, Django understands the entered values ​​as strings. Wrapping values ​​entered into filters in quotes and brackets is performed when several filters are filled. In Django 4.2 values ​​entered into several filters were treated as strings.

In browser:

But in templates lists are output exactly as in your example.

e.g. if I add a list to the context in my view:

context["test_list"] = ["tester"]

Then in my template I put:

{{ test_list }}

I see this in the browser:

[‘tester’]

So I would again suggest that something in your filter is returning a list when the template is expecting a string.

Yes, that’s right. That’s what I wanted to understand, what exactly returns a list, not a string, and at the same time also modifies the original input data in the filter field (in admin view after enter value). This is a Django SimpleListFilter. I was hoping someone could help here.

Just to be sure, what do you want the output to look like?

I want to use the values ​​entered in the field in the filter without transformations. I found out that when entering the first value (By Hostname filter) a

?hostname=Access_FL3_1

is append to the URL. Then, after specifying the value in the second filter (By Vlan filter), the previously appended part of the URL is converted again to:

?hostname=%5B%27Access_FL3_1%27%5D&vlan=1

%5B%27Access_FL3_1%27%5D - this is url encoding for the string:

[‘Access_FL3_1’]

That is, the value of the hostname parameter when specifying values ​​in several filters written as subclasses of SimpleListFilter is a list of one element, as philgyford noted earlier. Because in the URL string:

“[‘Access_FL3_1’]”

and when calling request.GET.get(‘hostname’) or inside the value() method, this is exactly the same string as shown above in the debug.
Does this encoding depend on Django? Perhaps it was different in version 4.2, since there was no such problem with filters. I hope I didn’t tire you out)

I’m slightly puzzled by this line in your choices() method:

all_choice['query_parts'] = (

Looking at SimpleListFilter, it doesn’t specify a “query_parts” in the dict that it yields:

This dictionary is used in input_filter.html. Is this not correct?

By rewriting the value() method for filters, I achieved that after sending the form (filling in the field in the desired filter and entering) the entered data does not change. But according to the encoding in the URL, I see that Django still wraps the entered values ​​in lists. I am sure that there must be a more elegant way to implement filters like mine. After all, the changes in SimpleListFilter from version Django 4.2 were made for the convenience of developers, but in my case everything only became more complicated). Maybe you know what exactly in Django is responsible for forming URLs (encoding) when adding filters? Or what in SimpleListFilter wraps the entered data in a list? It seems that I am not using the SimpleListFilter class correctly to implement my filters in the context of Django 5.2.

I don’t know how to say it another way. The template is expecting the value it outputs to be a string. It is receiving a list containing one element. You need to look at your code and see where the value is a list containing one element, and not a string.

Presumably the v in your list comprehension is a list? Have you checked this yet? What do you see in the console if you print(all_choice['query_parts']) from inside the choices() method?

Side note from the moderator: Please do not continue to post images of code. When you need to post code, copy/paste the code into the body of your post, marked as preformatted text.
(All textual data should be posted as text. This would also include templates, html, and error messages.)

request.GET

<QueryDict: {'hostname': ["['Access_FL3_1']"], 'vlan': ['1']}>

special variables

function variables

encoding:'utf-8'

_assert_mutable:<bound method QueryDict._assert_mutable of <QueryDict: {'hostname': ["['Access_FL3_1']"], 'vlan': ['1']}>>

_encoding:'utf-8'

_getlist:<bound method MultiValueDict._getlist of <QueryDict: {'hostname': ["['Access_FL3_1']"], 'vlan': ['1']}>>

_mutable:False

'hostname':"['Access_FL3_1']"

'vlan':'1'

len():2

request.GET is an immutable Django object, and here you can see that after I entered a value into the second filter (By Vlan), in request.GET it immediately turns into a list. So it’s not my code modifies the values ​​entered into the filters, right?

Sorry, accepted. Is it correct to show debug on screenshot?

No. It’s all text data. Copy/paste the text of the debug session just like you would code.

(Agreeing with Ken; screenshots are hard to read on small devices and impossible for screen readers. And they make it impossible for anyone to copy and paste code/errors into replies so they can help you.)

As you can see, you’re setting the value of the “hostname” to ["['Access_FL3_1']"] which seems wrong doesn’t it?

I don’t know how you came up with this method, but googling it looks the same as the one in this 8 year old gist. If you scroll down that page you’ll see someone added a “Django 5 compatible version” of which a later commenter said, “This fixed the problem with extra square brackets and quotes for me, thanks.”

Oh, everything ingenious is simple. Awesome! Thank you so much!