I am using MongoDB as the back-end database for Python web application (PyMongo + Bottle). Users can upload files and optionally 'tag' these files during upload. The tags are stored as a list within the document, per below:

{
    "_id" : ObjectId("561c199e038e42b10956e3fc"),
    "tags" : [ "tag1", "tag2", "tag3" ],
    "ref" : "4780"
}

I am trying to allow users to append new tags to any document. I came up with something like this:

def update_tags(ref, new_tag)
    # fetch desired document by ref key as dict
    document = dict(coll.find_one({'ref': ref}))
    # append new tag
    document['tags'].append(new_tag)
    # re-insert the document back into mongo
    coll.update(document)

(fyi; ref key is always unique. this could easily be _id as well.) It seems like there should be a way to just update the 'tags' value directly without pulling back the entire document and re-inserting. Am I missing something here?

Any thoughts are greatly appreciated :)

Solution 1

You don't need to use to retrieve the document first just use the .update method with the $push operator.

def update_tags(ref, new_tag):
    coll.update({'ref': ref}, {'$push': {'tags': new_tag}})

Since update is deprecated you should use the find_one_and_update or the update_one method if you are using pymongo 2.9 or newer

Solution 2

Just to add to @ssytvane answer,and to answer @Guarav: you can add "upsert = True" if it does not exist:

def update_tags(ref, new_tag):
    coll.update({'ref': ref}, {'$push': {'tags': new_tag}}, upsert = True)

or

def update_tags(ref, new_tag):
    coll.update_one({'ref': ref}, {'$push': {'tags': new_tag}}, upsert = True)

Solution 3

You can simply do

1) If you want to append single entry

def update_tags(ref, new_tag):
    coll.update({'ref': ref}, {'$push': {'tags': new_tag}})

eg:

{
    "_id" : ObjectId("561c199e038e42b10956e3fc"),
    "tags" : [ "tag1", "tag2", "tag3" ],
    "ref" : "4780"
}
>> update_tags("4780", "tag4")
{'updatedExisting': True, u'nModified': 1, u'ok': 1, u'n': 1}
>> coll.find_one({"ref":"4780"})
{
    "_id" : ObjectId("561c199e038e42b10956e3fc"),
    "tags" : [ "tag1", "tag2", "tag3" , "tag4" ],
    "ref" : "4780"
}

2) If you want to append multiple entries

def update_tags(ref, new_tag):
    coll.update({'ref': ref}, {'$pushAll': {'tags': new_tag}}) #type of new_tag is list

eg:

{
    "_id" : ObjectId("561c199e038e42b10956e3fc"),
    "tags" : [ "tag1", "tag2", "tag3" ],
    "ref" : "4780"
}
>> update_tags("4780", ["tag5", "tag6", "tag7"])
{'updatedExisting': True, u'nModified': 1, u'ok': 1, u'n': 1}
>> coll.find_one({"ref":"4780"})
{
    "_id" : ObjectId("561c199e038e42b10956e3fc"),
    "tags" : [ "tag1", "tag2", "tag3" , "tag4" , "tag5", "tag6", "tag7" ],
    "ref" : "4780"
}

Note: If the key is not already present, then mongo will create new key.

Solution 4

There had been some good answers that are correct but in my opinion writing update_tags this way is better and more usable:

def update_tags(ref, *args):
    coll.update_one(ref, {'$push': {'tags': {'$each': args}}})

this way you can do both appending one tag or appending many tags:

>> update_tags(ref, 'tag5')
>> update_tags(ref, 'tag5', 'tag6')
>> list_of_new_tags = do_something_that_returns_list_of_tags()
>> update_tags(ref, *list_of_new_tags)

Solution 5

You can use $push

collection_name.update_one({'ref': ref}, {'$push': {'tags': new_tag}})

You can update multiple arrays in a single query

collection_name.update_one({'ref': ref}, {'$push': {'field1': value1, 'filed2': value2}})

Values can be pushed to push like below.

{ $push: { <field1>: <value1>, ... } }