Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialising a QueryDict.fromkeys

Tags:

python

django

We can initialise a new dict instance from a list of keys:

>>> dict.fromkeys(['spam', 'spam', 'potato'])
{'potato': None, 'spam': None}

And a querydict is a dict, so this should work, right?

>>> QueryDict.fromkeys(['spam', 'spam', 'potato'])
TypeError: __init__() takes at least 2 arguments (1 given)

Of course I can do something lame like QueryDict('spam&spam&potato'), but my question: is the .fromkeys method usable at all, or completely broken?

If the former, how do you use it? If the latter, why wasn't it explicitly disabled in the subclass?

like image 360
wim Avatar asked Nov 10 '15 22:11

wim


2 Answers

The problem depends also on Django version. You use Django 1.7.x or older that requires the positional parameter query_string for QueryDict. It is fixed in Django 1.8 where this parameter is optional.

The second problem is that QueryDict creates by default an immutable instance and there is no way how to pass mutable=True through fromkeys. The keys can not be added to immutable and the method fails also in Django 1.8.

It can be fixed this way:

from django.http.request import QueryDict

class MyQueryDict(QueryDict):

    @classmethod
    def fromkeys(cls, seq, value='', encoding=None):
        result = cls('', mutable=True, encoding=encoding)
        for key in seq:
            result.appendlist(key, value)  # or result.update(key, value)
        return result

It is implemented more complicated in order to reflect repeated keys. The default value is the empty string because it doesn't make sense to convert it to string 'None' like 'potato=None&spam=None&spam=None' by urlencode() method. The default result QueryDict should be the same as QueryDict('potato&spam&spam').

The presented solution is so strange that raise NotImplemented() would be a more straightforward "implementation". (EDIT: I don't expect that anything more is useful enough to be accepted to Django code base) However I must agree with you, it is a bug with mysterious messages. Unimplemented functionalities are usually undocumented if they don't require a warning note because they can be found endless many.

Other solution would be to modify __init__ only:

class MyQueryDict(QueryDict):

    def __init__(self, query_string=None, mutable=True, encoding=None):
        super(MyQueryDict, self).__init__(query_string=query_string,
                                          mutable=mutable,
                                          encoding=encoding)

because immutable QueryDict instances are frequently impractical and even one half of internal use in Django is with mutable QueryDict instances.

like image 86
hynekcer Avatar answered Oct 21 '22 01:10

hynekcer


tl;dr QueryDict.fromkeys will start working in the Django 1.11 release.

At PyCon 2016 I had the opportunity to speak with some core Django developers, we agreed the inherited method is broken and violates the Liskov substitution principle. At one of the sprints I was able to scratch this itch and patch the QueryDict class.

Here's the relevant commit and issue, the fix is now merged into master and already in the docs. I used an implementation similar to the first proposal from hynekcer.

like image 20
wim Avatar answered Oct 21 '22 02:10

wim