67

I am using Flask and MongoDB. I am trying to convert the content of request.form into something suitable for saving via PyMongo. It seems like something that should come up often enough to have a ready-made solution.

So what Flask give me is something like:

ImmutableMultiDict([('default', u''), ('required': u'on'), ('name', u'short_text'), ('name', u'another'), ('submit', u'Submit')])

And what I am looking to get is something close to this:

{
  'default': '',
  'name': ['short_text', 'another'],
  'required': true
}
Ivan P
  • 1,920
  • 2
  • 15
  • 19
  • Why you need that? you can access the values as you access in dictionary without converting it. like d['required'] gives you true in return. – Abdul Majeed Oct 02 '15 at 06:30
  • @Abdul, if you want to loop through the ImmutableNutiDict(), then you can just do like normally dictionary, but if you want use it as a whole as a dict, for example, merge dictionary using z={**x,**y}, then it does not work, the value will have squares around it, like {...,'name':['short_text'],}. – zhihong Sep 22 '17 at 09:06

6 Answers6

86
>>> from werkzeug.datastructures import ImmutableMultiDict
>>> imd = ImmutableMultiDict([('default', u''), ('required', u'on'), ('name', u'short_text'), ('name', u'another'), ('submit', u'Submit')])
>>> imd.to_dict(flat=False)
>>> {'default': [''], 
'name': ['short_text', 'another'],
'required': ['on'],
'submit': ['Submit']}

.to_dict(flat=False) is the thing to keep in mind. See the relevant documentation

Dagrooms
  • 1,507
  • 2
  • 16
  • 42
Vb407
  • 1,577
  • 2
  • 15
  • 27
  • 12
    Just use the `.to_dict()` method like `imd.to_dict()` instead. That way every value isn't a list, like in your solution. Also you won't need to import ImmutableMultiDict – wordsforthewise Jan 20 '17 at 23:49
  • 1
    I would probably update this answer to include what the comment suggests doing. The usage of `.to_dict()` would be the way to go here to remove those values as lists. I would keep your answer as is, but add to it and demonstrate the difference in using `to_dict()`. – idjaw May 30 '17 at 21:27
  • imd.to_dict() isn't appropriate as it loses the duplicate entry – lod Oct 17 '17 at 07:08
  • 2
    `imd.to_dict(flat=False) ` is exactly what OP is looking for. See the edited answer above. – chhantyal Nov 09 '17 at 15:09
  • 1
    use `imd.to_dict(flat=True)` for get as key/value pair – skpaik Jun 19 '19 at 11:45
  • `flat=False` is important to get a list of values for a key, instead of a single value. Very important. – jdhao Jan 21 '20 at 15:09
33

The Flask ImmutableMultiDict data structure has a built in to_dict method.

This knowledge in addition to the Flask request object form property being an ImmutableMultiDict allows for simple handling of a form POST request to MongoDB.

See below for a naive example:

from flask import request

@app.route('/api/v1/account', methods=['POST'])
def create_account():
    """Create user account"""
    account_dict = request.form.to_dict()

    db.account.insert_one(account_dict)
Joel Colucci
  • 471
  • 4
  • 9
19

You can use werkzeug's getlist to write code like this

data = dict((key, request.form.getlist(key)) for key in request.form.keys())

Now each key of data would be a list which would contain 1 more element. To get results exactly in your format do this

data = dict((key, request.form.getlist(key) if len(request.form.getlist(key)) > 1 else request.form.getlist(key)[0]) for key in request.form.keys())

Now this is inefficient because for each key there are 3 calls to request.form.getlist(key). You can write a loop and get around it.

lovesh
  • 5,235
  • 9
  • 62
  • 93
17

request.form.to_dict() would yield what you need

Arjun
  • 1,259
  • 1
  • 13
  • 25
4

Comparison of dict() and .to_dict() method before and after python version 3.6.

from werkzeug.datastructures import ImmutableMultiDict
imd = ImmutableMultiDict([('default', u''), ('required', u'on'), ('name', u'short_text'), ('name', u'another'), ('submit', u'Submit')])

Till python3.5

dict(imd)
#output: {'default': [''], 'required': ['on'], 'name': ['short_text', 'another'], 'submit': ['Submit']}

imd.to_dict(flat=false)
#output: {'default': [''], 'required': ['on'], 'name': ['short_text', 'another'], 'submit': ['Submit']}

imd.to_dict(flat=True) # or imd.to_dict() 
#output: {'default': '', 'required': 'on', 'name': 'short_text', 'submit': 'Submit'}

Thus,

dict(imd) == imd.to_dict(flat=False)
#output: True

From python3.6 onwards

dict(imd)
#output: {'default': '', 'required': 'on', 'name': 'short_text', 'submit': 'Submit'}

imd.to_dict(flat=false)
#output: {'default': [''], 'required': ['on'], 'name': ['short_text', 'another'], 'submit': ['Submit']}

imd.to_dict(flat=True) # or imd.to_dict() 
#output: {'default': '', 'required': 'on', 'name': 'short_text', 'submit': 'Submit'}

Thus,

dict(imd) == imd.to_dict(flat=False)
#output: False

Using .to_dict() with flat=True/False is a safer option.

Rakesh Sharma
  • 608
  • 6
  • 9
  • This was sooo helpful. I resurrected a 2-year old project and a bunch of tests were broken and was wondering how the heck is the site even running.... turns out the running site is using `dict()` and 3.5. Switching to 3.6 and `flat=False`. – Ray Toal May 23 '19 at 08:08
  • Running ```dict(ImmutableMultiDict)``` on Python 3.7 windows 10 worked fine for me. When i deployed the project to Windows Server 2016 on Python 3.7 ```dict(ImmutableMultiDict)``` returned dict values as a list. I had to use ```.to_dict()```. – Schäfer Sep 03 '20 at 13:55
3
>>> from werkzeug.datastructures import ImmutableMultiDict
>>> so = ImmutableMultiDict([('default', u''), ('required', u'on'), ('name', u'short_text'), ('name', u'another'), ('submit', u'Submit')])

# Most earlier answers have comments suggesting so.to_dict()
# It doesn't work, duplicates are lost like in a normal dict
>>> so.to_dict()
{'default': '', 'required': 'on', 'name': 'short_text', 'submit': 'Submit'}

# The response by Vb407 is better but litters lists everywhere
>>> dso = dict(so)
{'default': [''], 'required': ['on'], 'name': ['short_text', 'another'], 'submit': ['Submit']}

# We can achieve the requested state by cleaning this up
>>> { k: dso[k][0] if len(dso[k]) <= 1 else dso[k] for k in dso }
{'default': '', 'required': 'on', 'name': ['short_text', 'another'], 'submit': 'Submit'}
lod
  • 1,098
  • 10
  • 13
  • This is best answer. Especially the last method that differentiates between list and strings is perfect. – bonney Nov 27 '19 at 06:50