Update Embedded Mongo Documents - Mongoengine - python

I've a model -
class Comment(EmbeddedDocument):
c_id = Integer()
content = StringField()
class Page(DynamicDocument):
comments = ListField(EmbeddedDocumentField(Comment))
I insert the following data
comment1 = Comment(c_id=1,content='Good work!')
comment2 = Comment(c_id=2,content='Nice article!')
page = Page(comments=[comment1, comment2])
Now I want to update the comment whose id was 1 to Great work!. How can I do it?
I read on some SO thread that it can be done in following way:-
p_obj = Page.objects.get(comments__c_id=1).update(set__comments__S__content='Great work')
However, the above update throws an error saying:-
Update failed (Cannot apply the positional operator without a corresponding query field containing an array.)
Following is the document structure:-
{
"comments": [{
"content": "Good Work",
"c_id": "1"
},
{
"content": "Nice article",
"c_id": "2"
}],
}

You need to modify your Comment model slightly. c_id field in the Comment model should be an mongoengine IntField() instead of Integer() you are using.
class Comment(EmbeddedDocument):
c_id = IntField() # use IntField here
content = StringField()
class Page(DynamicDocument):
comments = ListField(EmbeddedDocumentField(Comment))
Secondly, When performing an .update() operation using set, you need to use .filter() instead of the .get() you are using as .update() will try to update documents.
Updating embedded documents:
We will try to update embedded documents in the Python shell. We will first import the models and then create 2 comment instances comment1 and comment2. Then we create a Product instance and attach these comment instances to it.
To perform an update, we will first filter Product objects having c_id as 1 in the comments embedded document. After getting the filtered results we will call update() on it using set__.
For example:
In [1]: from my_app.models import Product, Comment # import models
In [2]: comment1 = Comment(c_id=1,content='Good work!') # create 1st comment instance
In [3]: comment2 = Comment(c_id=2,content='Nice article!') # create 2nd comment instance
In [4]: page = Page(comments=[comment1, comment2]) # attach coments to `Page` instance
In [5]: page.save() # save the page object
Out[5]: <Page: Page object>
# perform update operation
In [6]: Page.objects.filter(comments__c_id=1).update(set__comments__S__content='Great work')
Out[6]: 1 # Returns the no. of entries updated
Check that value has updated:
We can check that the value has updated by going inside the mongo shell and performing .find() on the page_collection.
> db.page_collection.find()
{ "_id" : ObjectId("5605aad6e8e4351af1191d3f"), "comments" : [ { "c_id" : 1, "content" : "Great work" }, { "c_id" : 2, "content" : "Nice article!" } ] }

Related

Trying to use Embedded Documents Fields in MongoDB

I'm following freecodecamp's video on MongoDB using mongoengine (as db). I'm trying to use the embedded document list field to add information to my main document. Also using a Streamlit webapp as my input source
My class's are:
class Contest(db.Document):
date_created = db.DateTimeField(default=datetime.today)
name = db.StringField(required=True)
format = db.EmbeddedDocumentField(Format)
class Format(db.EmbeddedDocument):
contest_id = db.ObjectIdField()
name = db.StringField()
Then I've tried a few different ways to to add the format to a specific contest instance.
Try #1
def set_format(active_contest):
format : Format = None
name = st.text_input('Name of Format:')
submit = st.button('Set Format Name')
if submit == True:
format.contest_id = active_contest.id
format.name = name
active_contest.save()
setting Format to None is the way the freecodecamp video shows... but i get this error: AttributeError: 'NoneType' object has no attribute 'contest_id'.
So I tried switching it to: format = Format()... this way it doesn't give me an error, but also doesn't update the Contest document to include the format information.
I also tried switching active_contest.save() to format.save() but then i get a: AttributeError: 'Format' object has no attribute 'save'
I've also tried the update function instead of save... but i get similar errors every-which way.
New to mongoDB and programming in general. Thanks in advance!
First of all, if you want to store Format as embedded document, the contest_id is not necessary in Format class. With this approach you will end with something like this in your MongoDB collection:
{
"date_created":ISODate(...),
"name": "...",
"format": {
"name": "..."
}
}
Another approach could be something like:
class Contest(db.Document):
date_created = db.DateTimeField(default=datetime.today)
name = db.StringField(required=True)
format = db.ReferenceField('Format') # <- Replaced by ReferenceField
class Format(db.Document): # <- EmbeddedDocument replaced by Document
name = db.StringField()
In that case each instance of "Format" will be stored in a separate collection. So you will end with something like this in MongoDB:
Collection Contest:
{
"date_created":ISODate(...),
"name": "...",
"format": :ObjectId("...") // <-- here's the relation field
}
Collection Format:
{
"_id":"...",
"name":"..",
}
Both approaches shares the same code:
def set_format(active_contest): # <-- here's the instance of 'Contest'
format : Format = Format() # <-- create a new Format instance
name = st.text_input('Name of Format:')
submit = st.button('Set Format Name')
if submit == True:
format.name = name
active_contest.format = format # <-- assigns the format to contest
active_contest.save() <- stores both because you are saving the 'parent' object

Custom function for annotate

I am trying to extract records with my Django ORM Model:
users = User.objects.values( "first_name", "last_name" ).annotate( user_level=custom_levels( F("user_level" ) ) );
The model is represented by:
first_name
last_name
user_level: it may be [1] or [1,13] (it comes from saving a MultipleSelect throught forms.py)
Goal:
My goal is to process user_level in the query in order to transform those ids in text (I have a dict key:value with those ids). For that I was thinking of writing a function, custom_level, to process the pairing job.
I am using annotate() but I am not really sure of its correct use.
How would you process the pairing between ids and text in order to get a single text line?
Any clue?
Thank you
Don't see a reason why you should do it on ORM level. Why don't you just make it with model's property, which would transform this field value onto desired? Like this:
class User(AbstractUser):
# ...
#property
def user_level_description(self):
level_descriptions = {
1: "Level One",
2: "Level Two",
# ...
}
return ", ".join(level_descriptions[level] for level in self.user_level)

Pymongo - How can I return a specific field?

I need the ObjectId of documents so I can put it in a list. Visually I need
'_id':'ObjectId(x)'
Out of a bunch of documents. How can I return the Id of a document?
Updating the question for clarity.
Suppose you have a document:
doc1 = {"_id": ObjectId('123456789'),
"name" : "John Doe",
"shoe_size" : "7"
}
And you need a specific attribute such as "name"
>>> name = doc1.get("name")
>>> print(name)
John Doe
Now, suppose you have only the Id = 123456789 and you need to find the document attached to that Id out of a database and return the size:
import pymongo
from pymongo import MongoClient
from bson import ObjectId
>>> URI = "URI" #link that gives you access to your database
>>> client = MongoClient(URI)
>>> db = client.get_database()
>>> print(db.collection.find_one({"_id":ObjectId("123456789")}).get("size))
7
To search all of the documents in a collection and store the Id's in a list:
lst_ids = []
collection = db.collection.find({})
for doc in collection:
lst_ids.append(doc.get("_id))
You always get the _id field unless you specifically ask to not get it.
So it will be in the returned document(s) of your find() or find_one() statement. For example:
record = db.mycollection.find_one({})
print (record.get('_id'))

Mongoengine, after dictionary key field.. Mongoengine cannot convert field names to db_fields

If you try this code.. you can see the problem I have..
class Embedded(EmbeddedDocument):
boxfluxInt = IntField(default=0, db_field='i')
meta = {'allow_inheritance': False}
class Test(Document):
boxflux = MapField(field=EmbeddedDocumentField(Embedded), db_field='x')
meta = {'collection': 'test',
'allow_inheritance': False}
Test.drop_collection()
newTestDoc = Test()
newTestDoc.boxflux['DICTIONARY_KEY'] = Embedded(boxfluxInt=1)
newTestDoc.save()
Test.objects.update_one(inc__boxflux__DICTIONARY_KEY__boxfluxInt=1)
The result in Mongodb is like..
> db.test.findOne()
{
"_id" : ObjectId("4fbdbbc8c450190a50000001"),
"x" : {
"DICTIONARY_KEY" : {
"boxfluxInt" : 1,
"i" : 1
}
}
}
>
As you can see, I intended to increase 'x.DICTIONARY_KEY.i' by 1
but the result is that a new key (boxfluxInt) is created even though I set 'boxfluxInt' 's db_field as 'i'
Is it bug? or am I wrong?
I think the dictionary key ('DICTIONARY_KEY') makes conversion to mongo style db fields impossible.. if I'm correct..
OK this looks like a bug, best place to report them is in github: http://github.com/mongoengine/mongoengine
This wont get fixed until 0.7 as it will break existing users in production. So I'll have to write up migration notes as part of the fix.

Mongoengine, retriving only some of a MapField

For Example.. In Mongodb..
> db.test.findOne({}, {'mapField.FREE':1})
{
"_id" : ObjectId("4fb7b248c450190a2000006a"),
"mapField" : {
"BOXFLUX" : {
"a" : "f",
}
}
}
The 'mapField' field is made of MapField of Mongoengine.
and 'mapField' field has a log of key and data.. but I just retrieved only 'BOXFLUX'..
this query is not working in MongoEngine....
for example..
BoxfluxDocument.objects( ~~ querying ~~ ).only('mapField.BOXFLUX')
AS you can see..
only('mapField.BOXFLUX') or only only('mapField__BOXFLUX') does not work.
it retrieves all 'mapField' data, including 'BOXFLUX' one..
How can I retrieve only a field of MapField???
I see there is a ticket for this: https://github.com/hmarr/mongoengine/issues/508
Works for me heres an example test case:
def test_only_with_mapfields(self):
class BlogPost(Document):
content = StringField()
author = MapField(field=StringField())
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...',
author={'name': "Ross", "age": "20"}).save()
obj = BlogPost.objects.only('author__name',).get()
self.assertEquals(obj.author['name'], "Ross")
self.assertEquals(obj.author.get("age", None), None)
Try this:
query = BlogPost.objects({your: query})
if name:
query = query.only('author__'+name)
else:
query = query.only('author')
I found my fault! I used only twice.
For example:
BlogPost.objects.only('author').only('author__name')
I spent a whole day finding out what is wrong with Mongoengine.
So my wrong conclusion was:
BlogPost.objects()._collection.find_one(~~ filtering query ~~, {'author.'+ name:1})
But as you know it's a just raw data not a mongoengine query.
After this code, I cannot run any mongoengine methods.
In my case, I should have to query depending on some conditions.
so it will be great that 'only' method overwrites 'only' methods written before.. In my humble opinion.
I hope this feature would be integrated with next version. Right now, I have to code duplicate code:
not this code:
query = BlogPost.objects()
query( query~~).only('author')
if name:
query = query.only('author__'+name)
This code:
query = BlogPost.objects()
query( query~~).only('author')
if name:
query = BlogPost.objects().only('author__'+name)
So I think the second one looks dirtier than first one.
of course, the first code shows you all the data
using only('author') not only('author__name')

Categories