Getting error "Need 2 values to unpack in for loop; got 1" in defaulttags.py

Hello,

As a result of some unusual web requests to my webserver I am sometimes getting the following error message from Django:

Need 2 values to unpack in for loop; got 1. 

It comes from the following place in the code in django/template/defaulttags.py inside the render() function in the ForNode class:

                if unpack:
                    # If there are multiple loop variables, unpack the item into
                    # them.
                    try:
                        len_item = len(item)
                    except TypeError:  # not an iterable
                        len_item = 1
                    # Check loop variable count before unpacking
                    if num_loopvars != len_item:
                        raise ValueError(
                            "Need {} values to unpack in for loop; got {}. ".format(
                                num_loopvars, len_item
                            ),
                        )
                    unpacked_vars = dict(zip(self.loopvars, item))

After adding a little extra debug output I figured out that the “item” in this case is of type <class ‘str’>, so I suppose then that len_item = len(item) will give the length of the string, which apparently in this case was only a single character so len_item got the value 1, while at the same time num_loopvars is 2 which is why the error message says “Need 2 values to unpack in for loop; got 1.”

I am not familiar with the django code and at the moment I don’t understand much what is going on here.

Is it expected that “item” can be a string of length 1, or does that indicate that something went wrong earlier?

Should the code be changed somehow to handle this case better?

Ideas on how to investigate further?

Hey Elias,

looks like you have a for loop like this somewhere in your template code:

{% for x, y in points %}
    There is a point at {{ x }},{{ y }}
{% endfor %}

I.e. you are unpacking the iterable (points) into two variables (x, y).

In the code you pasted, it is checked, if in this case points can be unpacked into the two variables x and y. Therefore points needs to be iterable (a list, tuple, string…) and it’s items need to be of length 2.

I would advise you to look for a for loop like this, where you unpack into two variables and then check whether it really makes sense to unpack the iterable into two variables under all possible conditions.

Since item is a single character in you case, I would guess that the iterable unexpectedly is a single string (an error message or something like that).

E.g. in the example above it would mean, that points usually is something like [(1,2), (3,4)], but under some condition Error: Could not find.. blah blah.

When iterating over string, we are iterating over it’s characters, so a situation like this would produce your error.

Hope that helps :slight_smile:

Cheers,
Mark

1 Like

Hi @maqnius and thanks for answering! Based on your suggestion I investigated some more and found the following:

The website in question uses a template with a for-loop like this:

{% for key, value in request.GET.items %}
    [... doing something with key and value here ...]
{% endfor %}

That seems to work most of the time, but there is a problem for certain request URLs, specifically the problem appears when the request URL contains “items=” as in the following example:

https://example.org/some/path/?items=x

I think what happens is that the “request.GET.items” (that the template tries to use) that normally exists, in this case gets confused with the “items” that comes in from the request URL.

Do you see what I mean, that things can go wrong in that way?

Is Django to blame here because it fails to distinguish between the different “items”, should this be reported as a bug in Django?

Or is the template to blame, and if so, is there a better way to write such a template that would avoid getting into this problem?

Welcome @eliasrudberg !

Absolutely.

This is not a bug in Django. This would be functioning in the manner that Django is designed and documented at The Django template language | Django documentation | Django.

The error here is trying to use a reference in a template without having the view ensure that the context being passed is correct.

Both the template and the view should be corrected here. The view should ensure the right data is put into the context, probably under a different name, and the template should render that data from that name.

I think the issue is that in a template the lookup request.GET.items will try request.GET["items"] before request.GET.items per the variables docs. So when the key ‘items’ isn’t present, the template works, but once it is, it returns that string instead.

The easiest fix would be to do as Ken suggests and pass an extra context variable, in this case I think something like:

return render(..., context={"query_params": request.GET.items()})

Alternatively, try making a custom template tag for whatever manipulation you’re doing with request.GET. Perhaps you need something like {% querystring %}, coming in Django 5.1? (Easily copy-pastable into your project on an older version.)

1 Like