marshmallow EmbeddedDocument doesn't work - mongoengine

I made a simple board api with flask-restplus and mongoengie.
Also use marshmallow for serialize data.
Below code is now I worked.
[model]
class Article(Document):
no = SequenceField()
subject = StringField(required=True)
content = StringField(required=True)
userid = StringField(required=True)
comments = ListField(EmbeddedDocumentField(Comment))
created_at = DateTimeField(default=datetime.datetime.now())
updated_at = DateTimeField(default=datetime.datetime.now())
class Comment(EmbeddedDocument):
content = StringField(required=True)
userid = StringField(required=True)
created_at = DateTimeField(default=datetime.datetime.now())
[serializer]
class CommentSchema(Schema):
content = fields.String()
userid = fields.String()
created_at = fields.DateTime()
class ArticleSchema(Schema):
comments = CommentSchema(many=True)
class Meta:
fields = ('no', 'subject', 'content', 'userid', 'comments', 'created_at', 'updated_at')
I defined schema follow to model.
In ArticleSchema, to show comments, I definded comments = CommentSchema(many=True) and insert it to fields.
And get article function is here.
def get_all_articles():
articles = Article.objects.all()
data, errors = ArticleListSchema(many=True).dump(articles)
return data
But when I access to it, it occur Internal error and throw error message like this.
TypeError: Object of type Comment is not JSON serializable
After searched in google, I found some interest function, Nested. (https://marshmallow.readthedocs.io/en/3.0/nesting.html)
So I modified schema.
class ArticleSchema(Schema):
no = fields.Integer()
subject = fields.String()
content = fields.String()
userid = fields.String()
comments = fields.Nested(CommentSchema())
created_at = fields.DateTime()
updated_at = fields.DateTime()
(comments = fields.Nested(CommentSchema())
But it doesn't work properly too.
[result]
{
"subject": "string",
"content": "string",
"userid": "string",
"updated_at": "2018-11-06T17:04:55.197000+00:00",
"no": 20,
"created_at": "2018-11-06T17:04:55.197000+00:00",
"comments": {}
}
I already insert 2 comments and mongodb result is,
> db.article.find()
{ "_id" : ObjectId("5be14bb61b48d9113e3d1413"), "no" : 20, "subject" : "string", "content" : "string", "userid" : "string", "comments" : [ { "content" : "cosdadas", "userid" : "123123", "created_at" : ISODate("2018-11-06T17:34:44.199Z") }, { "content" : "Second comment", "userid" : "john", "created_at" : ISODate("2018-11-06T17:34:44.199Z") } ], "created_at" : ISODate("2018-11-06T17:04:55.197Z"), "updated_at" : ISODate("2018-11-06T17:04:55.197Z") }
But in API, comments doesn't show. Just empty {}.
Is there any solution here?
Thanks.

[SOLVED]
Change
comments = fields.Nested(CommentSchema()) to
comments = fields.Nested(CommentSchema, many=True) and it works perfectly.

Related

Print the output in my desired format using nested serializers?

I am trying to send messages using django-rest framework.I have created serializers for both user and message.I am using django default user model for storing user's details.
I am pasting my model and serializers here:
class MessageModel(models.Model):
message = models.CharField(max_length=100)
created_at = models.DateTimeField(default=now)
updated_at = models.DateTimeField(default=now)
user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='user')
class UserSerializer(ModelSerializer):
class Meta:
model = get_user_model()
fields = ['id','username','email']
class MessageSerializer(ModelSerializer):
created_by = UserSerializer(read_only=True)
class Meta:
model = MessageModel
fields = ['id','message', 'created_at', 'updated_at','user','created_by']
View :
class MessageViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = MessageModel.objects.all()
serializer_class = MessageSerializer
After creating message,I am getting response like below:
{
"id": 6,
"message": "Lorem ipsum",
"created_at": "2022-11-07T09:21:19.492219Z",
"updated_at": "2022-11-07T09:21:19.492237Z",
"user": 2
}
but my output should looks like in below format,everytime when a new message is created :
{
"id": 102,
"message": "Lorem ipsum",
"created_at": "created time in UTC",
"updated_at": "last updated time in UTC",
"created_by": {
"id": 3,
"username": "testuser",
"email": "test#mail.com",
}
}
In case of any error, return the error message.
The name of the field is user, not created_by, so:
class MessageSerializer(ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = MessageModel
fields = ['id', 'message', 'created_at', 'updated_at', 'user']
or you can specify user as source:
class MessageSerializer(ModelSerializer):
created_by = UserSerializer(read_only=True, source='user')
class Meta:
model = MessageModel
fields = ['id', 'message', 'created_at', 'updated_at', 'created_by']
Note: Models normally have no …Model suffix. Therefore it might be better to rename MessageModel to Message.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the MessageModel
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the user relation to messages.

adding new documents not being show in ElasticSearch index

I am new to ElasticsSearch and was messing around with it today. I have a node running on my localhost and was creating/updating my cat index. As I was adding more documents into my cat indexes, I noticed that when I do a GET request to see all of the documents in Postman, the new cats I make are not being added. I started noticing the issue after I added my tenth cat. All code is below.
ElasticSearch Version: 6.4.0
Python Version: 3.7.4
my_cat_mapping = {
"mappings": {
"_doc": {
"properties": {
"breed": { "type": "text" },
"info" : {
"cat" : {"type" : "text"},
"name" : {"type" : "text"},
"age" : {"type" : "integer"},
"amount" : {"type" : "integer"}
},
"created" : {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
}
}
cat_body = {
"breed" : "Persian Cat",
"info":{
"cat":"Black Cat",
"name": " willy",
"age": 5,
"amount": 1
}
}
def document_add(index_name, doc_type, body, doc_id = None):
"""Funtion to add a document by providing index_name,
document type, document contents as doc and document id."""
resp = es.index(index=index_name, doc_type=doc_type, body=body, id=doc_id)
print(resp)
document_add("cat", "cat_v1", cat_body, 100 )
Since the document id is passed as 100 it just updates the same cat document. I'm assuming its not changed on every run !?
You have to change the document id doc_id with every time to add new cat instead of updating existing ones.
...
cat_id = 100
cat_body = {
"breed" : "Persian Cat",
"info":{
"cat":"Black Cat",
"name": " willy",
"age": 5,
"amount": 1
}
}
...
document_add("cat", "cat_v1", cat_body, cat_id )
With this you can change both cat_id and cat_body to get new cats.

flask-marshmallow custom fields

I use flask-marshmallow and mongoengine.
Also flask-restplus for my API server.
Here is my api.py
class BoardSchema(ma.Schema):
class Meta:
fields = ('no', 'title', 'body', 'tags', 'created_at', 'views')
board_schema = BoardSchema()
boards_schema = BoardSchema(many=True)
class ArticleList(Resource):
def get(self):
articles = Board.objects.all()
return boards_schema.jsonify(articles)
model.py
from datetime import datetime
from mongoengine import *
from config import DB_NAME
connect(DB_NAME)
class Board(Document):
d = datetime.now()
date = "{}-{}-{}".format(d.year, d.month, d.day)
no = SequenceField()
title = StringField(required=True)
body = StringField(required=True)
tags = ListField(StringField())
likes = ListField(StringField())
views = ListField(StringField())
password = StringField(required=True)
created_at = DateTimeField(default=date)
updated_at = DateTimeField(default=date)
When I access to /article, it's result like this ->
{
"body": "123",
"created_at": "2018-08-20T00:00:00+00:00",
"no": 1,
"tags": [
"MySQL",
"C"
],
"title": "\ud14c\uc2a4\ud2b8",
"views": [
"127.0.0.1"
]
}
in "views", ip will be added who read article.
But I want to count of all the list of views and include it to my result.
The result I wanted is here.
{
"body": "123",
"created_at": "2018-08-20T00:00:00+00:00",
"no": 1,
"tags": [
"MySQL",
"C"
],
"title": "\ud14c\uc2a4\ud2b8",
"views": 20
}
I'm new at flask-marshmallow so I'm so confused how can I solve this issue.
Thanks.
Maybe you can try like this:
class BoardSchemaCustom(ma.ModelSchema):
class Meta:
model = Board
views = ma.fields.method(deserialize="_custom_serializer")
def _custom_serializer(self, obj):
return len(obj.views)
Create instance of your custom schema:
custom_board_schema = BoardSchemaCustom()
and dump it:
dump, errors = custom_board_schema.schema.dump(Board.query.first())
>>> dump
i've got the same problem. and my code works after installing marshmallow-sqlalchemy
pip install marshmallow-sqlalchemy
see from offical documentation
https://flask-marshmallow.readthedocs.io/en/latest/
Below snippet would also work:
class BoardSchemaCustom(ma.ModelSchema):
class Meta:
model = Board
views = ma.fields.Function(lambda obj: len(obj.views))

How to append on existing field with elasticsearch python?

I am using kibana and Elasticsearch version 5.1.1 and python version 3.6.
I have created my index like this
put_books
The function to add a user is this one :
def add_user(first_name, last_name, age, mail):
doc = {"first_name": "" + first_name, "last_name": "" + last_name, "age": age, "email": "" + mail}
global id_user
res = es.index(index="books", doc_type="user", id=id_user, body=doc)
id_user += 1
print(res['result'])
and to add preferences :
def add_preferences(preferences, i):
doc = es.get(index="books", doc_type="user", id=id_book)
res = es.update(index="books", doc_type="user", id=i, body={'doc':{"keyword_preferences": preferences}})
My problem is here : when I want to add preferences, it success but if I want to add again preferences, it replace it :
id_user = 1
nom = "nom_1"
prenom = "prenom_1"
age = 45
email = "adresse_mail_1"
add_user(prenom, nom, age, email)
add_preferences("comique", 1)
add_preferences("horreur", 1)
get_user(1)
the result is :
updated
{'first_name': 'prenom_1', 'last_name': 'nom_1', 'age': 45, 'email': 'adresse_mail_1', 'keyword_preferences': 'horreur'}
Finally, the solution was :
POST /books/user/1/_update
{
"script" : {
"inline": "ctx._source.keyword_preferences += params.preference",
"lang": "painless",
"params" : {
"preference" : ["comique"]
}
}
}
The new function is :
def add_preferences(preferences, i):
doc = es.get(index="books", doc_type="user", id=i)
res = es.update(index="books", doc_type="user", id=i, body={'doc': {'keyword_preferences': [{"preferences": preferences}]}})
The mapping is done and now I've got the result :
{'first_name': 'prenom_1', 'last_name': 'nom_1', 'age': 45, 'email': 'mail_1', 'keyword_preferences': [{'preferences': 'horreur'}]}
So, it has replaced the first preference "comique" by "horreur"
Edited. example answer for your question.
Index a doc
POST /books/user/1
{
"keyword_preferences": ["comique"]
}
Now, update a doc to append horreur in keyword_preferences key.
POST /books/user/1/_update
{
"script": "ctx._source.keyword_preferences += keyword_preferences",
"params": {
"keyword_preferences": ["horreur"]
},
"lang": "groovy"
}
This will update keyword_preferences as ["comique", "horreur"].
If update API throws as exception {"type":"script_exception","reason":"scripts of type [inline], operation [update] and lang [groovy] are disabled"}, then you need to config elasticsearch.yml. Add script.engine.groovy.inline.update: on script.groovy.sandbox.enabled: true in elasticsearch.yml and restart you elasticsearch. I hope this helps.

Mongoose fetch array field which contain specific elements

I'm currently building objects in mongodb mongoose inside 1 collection.
Here's the mock data:
>[
> {_id: 1, name : "parent 1", parent: null},
> {_id:2, name = "child", parent : [1]},
> {_id:3, name = "grandchild", parent : [1,2]},
> {_id:4, name = "greatgrandchild 1", parent : [1,2,3]}
> {_id:5, name = "greatgrandchild 2", parent : [1,2,3]}
>]
so I was trying to query out all the children of "child" base on the array of parent id,
here's the mongoose method:
Model.find({"parent" : { $elemMatch: { $in : [1,2]}}});
my expected result is :
[
{_id:3, name = "grandchild", parent : [1,2]},
{_id:4, name = "greatgrandchild 1", parent : [1,2,3]}
{_id:5, name = "greatgrandchild 2", parent : [1,2,3]}
]
instead it gave me
[
{_id:2, name = "child", parent : [1]},
{_id:3, name = "grandchild", parent : [1,2]},
{_id:4, name = "greatgrandchild 1", parent : [1,2,3]}
{_id:5, name = "greatgrandchild 2", parent : [1,2,3]}
]
notice there's a document with id 2 included
I realized the mistake,
the method is equivalent to
select * from table where parent in(1,2)
is there a way to fetch properly?
cheers
Try this approach using $all which is similar to $and :
Model.find({"parent" : { $all:[1,2]}}, callbackfunction)
https://docs.mongodb.com/manual/reference/operator/query/all/
i found the answer, mongodb will trace into the sub document so normal and will work.
Model.find({$and : [{"parent" : 1}, {"parent" : 2}]});

Resources