0

I'm trying to post data to mongodb using postman but I don't know the proper convention for uploading the reference to a image file in the fs.files bucket. Basically, the file is already in the database, I'm just trying to post a new user with the reference to the image.

Here is my model:

class Users(db.Document):
    _id = db.StringField()
    name = db.StringField()
    picture = db.FileField()
    email = db.StringField()
    password = db.StringField()
    meta = {'collection': 'Users'}

In postman, I try to post data like so:

{
"_id" : "1",
"name" : "John Doe",
"picture": [{"$id": "5e6a...f9q102"}], #This is the reference id for the image already in the database, in fs.files
"password" : "<hashed pw>",
"email" : "example@example.com"
}

I'm using flask restful api so in the python script, the post function is defined like so:

def post(self):
    body = request.get_json()
    print (body)
    user = Users()
    user = Users(**body).save()
    return 'Successful Upload', 200

I get the error when I try with the above convention:

mongoengine.errors.ValidationError: ValidationError (Users:None) ('list' object has no attribute
    'grid_id': ['picture'])

How do I post a new user in postman? Your help is appreciated

2 Answers2

0

You need to change a bit your code

Add these imports:

from mongoengine.fields import ImageGridFsProxy
from mongoengine import ReferenceField, DynamicDocument
from bson.dbref import DBRef
from bson import ObjectId

Modify your class picture field definition + add extra class fs

class Fs(DynamicDocument):
    #Add 'db_alias':'default' to meta
    meta = {'collection': 'fs.files'}

class Users(Document):
    ...
    picture = ReferenceField('Fs', dbref=True)
    ...

Now, you need to create new instance for DBRef this way:

def post(self):
    body = request.get_json()
    body["picture"] = DBRef('fs.files', ObjectId(body["picture"]))

    #mongoengine assumes `ObjectId('xxx')` already exists in `fs.files`. 
    #If you want to check, run below code:
    #if Fs.objects(_id=body["picture"].id).first() is None:
    #    return 'Picture ' + str(body["picture"].id) + ' not found', 400

    user = Users(**body).save()
    return 'Successful Upload', 200

At the end, if you need to read picture content:

image = ImageGridFsProxy(grid_id=ObjectId('xxx'))
f = open("image.png", "wb")
f.write(image.read())
f.close()
Valijon
  • 12,667
  • 4
  • 34
  • 67
0

It was a Validation error. The database was accepting JSON in a particular format than what I was posting. And the way I was processing the post request was also incorrect. This is the format it expected:

{
    ...,
    "picture" = {"$ref": "fs.files", 
                 "$id": ObjectId("5e6a...f9q102")},
    ...
}

Postman cannot accept the above format, instead, it accepted this:

{
    "_id" : "1",
    "name" : "John Doe",
    "picture": {"$ref": "fs.files", "$id": {"$oid": "5e6a...f9q102"}}, 
    "password" : "<hashed pw>",
    "email" : "example@example.com"
}

To make this work I changed the model to look this so in my flask app:

class Users(db.Document):
    _id = db.StringField()
    name = db.StringField()
    picture = db.ReferenceField('fs.files') #I changed this to a reference field because it holds the reference for the file and not the actual file in the database
    upload_picture = db.FileField() #I added this field so I can still upload pics via flask and via this document
    email = db.StringField()
    password = db.StringField()
    meta = {'collection': 'Users'}

Then I had to add this import and change the code so that it would read the input as JSON and transfer the reference value of picture to ObjectId(id) so that it matches the format the database was expecting.

from bson.json_util import loads

def post(self):
        body = str(request.get_json())
        x = body.replace("'", '"') #replace single quotes as double quotes to match JSON format
        data = loads(x)
        officer = Officers(**data).save()
        return 'Successful Upload', 200

Then voila, it works!