How to use nested marshmallow schemas with Mongoengines lazyReferenceFields? - flask

I have the following marshmallow schemas and mongoengine classes defined:
class DataReport(Document):
id = ObjectIdField(required=True)
firmware_statistics_report_reference_id = LazyReferenceField(FirmwareReport, required=True)
...
class DataReportSchema(Schema):
id = fields.Str()
firmware_statistics_report_reference_id = fields.Nested(FirmwareReportSchema)
...
class FirmwareReport(Document):
id = ObjectIdField(required=True)
firmware_id_list = ListField(required=True))
...
class FirmwareReportSchema(Schema):
id = fields.Str()
firmware_id_list = fields.List(fields.Str())
...
report = DataReport().objects.get(pk=...)
DataReportSchema().dump(report)
Is it possible to dump the nested schema with the LazyReferenceField from mongoengine? What I want is to get the all fields defined in the child schema, but I did not find a way to get the full schema data when it is referenced with the LazyReferenceField.
If I dump the DataReportSchema I will only get the referenced Object-ID and not the fields from the FirmwareReportSchema, which makes sense since it is a LazyReference until I call mongoengines fetch() function. I assume there must be a way to fetch the LazyReference before it is dumped, but I do not understand how.
Is it possible to fetch() the LazyReference before the marshmallow dump() in order to get the full dump from the child schema and not only an Object-ID?

Ah I found the solution myself. Instead of using the fields.Nested method it is possible to use the fields.Method method. Then it is possible to fetch() the id within a custom method:
class DataReportSchema(Schema):
id = fields.Str()
report_date = fields.DateTime()
firmware_statistics_report_reference_id = fields.Method("get_firmware_schema")
def get_firmware_schema(self, data_statistics_report):
firmware_report = data_statistics_report.firmware_statistics_report_reference_id.fetch()
return FirmwareStatisticsReportSchema().dump(firmware_report)

Related

Create django model with as many fields as an integer in other field

I have the following model in django
class params(models.Model):
name = models.CharField(max_length=30, default = 'no_name')
cs_n = models.IntegerField(default=16)
alt_n = models.IntegerField(default=2)
opt_out = models.BooleanField(default=1)
at_n = models.IntegerField(default=4)
I want to create a new model with as many fields as at_n. For example, if the user enter "4" in at_n, I want this to create automatically:
class params(models.Model):
at_1 = models.IntegerField(default=2)
at_2 = models.IntegerField(default=2)
at_3 = models.IntegerField(default=2)
at_4 = models.IntegerField(default=2)
Thanks
This probably isn't a good data model to follow as Django models are intended to closely mirror database tables. For example, you wouldn't want to dynamically update the DDL of a table in a database because doing so places you at risk of messing up data that already exists in said table.
Instead, I think it would be a better approach for you to re-evaluate your data model.
For example, if there was a main object you were trying to tie these attributes to, then make a model for that main object and then make a separate model for main object attributes/values.
From there, you could use view logic to actually validate that the appropriate number of attributes assigned to a particular main object.
I'm thinking something kind of like this:
class MainModel(models.Model):
....
{Your main model attributes}
at_n = models.IntegerField(default=4)
....
class MainModelAttributes(model.Model):
main_model = models.ForeignKey(MainModel)
attr_value = models.IntegerField()
Then in your views.py file, you could use logic to make sure that the number of attributes on the MainModelAttributes model match the number stored in MainModel.at_n.

Mongoengine EmbeddedDocumentListField // I can only access the get menthod

I am creating a database for my school. There is a Student Document that has an EmbeddedDocumentListField of Adults. I am trying to update existing EmbeddedDocuments using the EmbeddedDocumentListField methods but both save() and update() give me errors. get() seems to work though.
class Adult(EmbeddedDocument):
relation = StringField() # Adult, Mother, Father, Grandmother...
...
notes = StringField()
class Student(Document):
afname = StringField()
alname = StringField()
...
adults = EmbeddedDocumentListField('Adult')
I am able to successfully use the get method
#app.route('/editadult/<aeriesid>/<adultoid>')
def editadult(aeriesid,adultoid):
editUser = User.objects.get(aeriesid=aeriesid)
editAdult = editUser.adults.get(oid=adultoid)
This returns the object named editAdult with the attributes as expected but not the methods. I now want to update the values in that Object. I am able to see the methods I want to call by doing.
dir(editUser.adults)
But can't see the methods with
dir(editAdult)
From my reading of the docs I should be able to do this.
editAdult.update(fname="Juanita", lname="Sanchez")
This gives the error: AttributeError: 'Adult' object has no attribute 'update'.
But can't figure out how to use the methods on that context.
I tried
editAdult.fname = "Juanita"
editAdult.lname = "Sanchez"
editAdult.save()
But that gives the error: AttributeError: 'Adult' object has no attribute 'save'
The documentation is sparse. Mongoengine tells me what the methods are but no examples.
http://docs.mongoengine.org/apireference.html#embedded-document-querying
And GitHub gives good examples but I couldn't get them to work.
https://github.com/MongoEngine/mongoengine/pull/826
I am using Mongoengine 0.20.0
After a lot of trial and error I figured this out. With this data structure:
class Adult(EmbeddedDocument):
relation = StringField() # Adult, Mother, Father, Grandmother...
fname = StringField()
lname = StringField()
...
notes = StringField()
class Student(Document):
afname = StringField()
alname = StringField()
...
adults = EmbeddedDocumentListField('Adult')
I then created this Flask Route
#app.route('/editadult/<aeriesid>/<adultoid>')
def editadult(aeriesid,adultoid):
editUser = User.objects.get(aeriesid=aeriesid)
editAdult = editUser.adults.get(oid=adultoid)
adultoid is the unique identifier for the adult and aeriesid is the unique identifier for the student so I know that both of these will retrieve exactly one record.
The part that I was missing is that while the editAdult Object contains exactly the values that I want, it is NOT an EmbeddedDocumentListObject so it does not contain the methods. So, that get() command above is the basic MongoEngine get() and NOT the get() method from the EmbeddedDocumentFieldList. (I show the EmbeddedDocumentListField get() below)
Here is what I was missing. This is how you use the update() method in the EmbeddedDocumentListField.
editUser.adults.filter(oid=adultoid).update(
relation = form.relation.data,
fname = form.fname.data,
lname = form.lname.data,
...
notes = form.notes.data
)
I am not sure if this update command would update all of the filtered records. In my case, it is only possible that one record is returned because I am filtering on a uniqueid. Next, it turns out the EmbeddedDocumentListField() update() does NOT save like it does in the base update() method so you then have to do this. This fact is actually well documented in the MongoEngine docs.
http://docs.mongoengine.org/apireference.html?highlight=embedded%20documents#embedded-document-querying
editUser.adults.filter(oid=adultoid).save()
Finally, there is another way to do the original get() command:
editAdult2 = editUser.adults.filter(oid=adultoid).get()
For the sake of completeness, here is the Flask route to delete an EmbeddedDocument record
#app.route('/deleteadult/<aeriesid>/<adultoid>')
def deleteadult(aeriesid,adultoid):
editUser = User.objects.get(aeriesid=aeriesid)
editAdult = editUser.adults.get(oid=adultoid)
editUser.adults.filter(oid=adultoid).delete()
editUser.adults.filter(oid=adultoid).save()
I hope this is helpful to others. The process to learn this was crazy hard and mostly just trial and error. I considering going back to SQLAlchemy. :(

How to save a model-instance using serializers in django?

I would like to save a class instance in my database using serializers, provided by DRF.
Serializer:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
here I try to save an instance:
p = Person(Name = "Test")
_srlz = PersonSerializer(data = p)
The real case is little bit more complicated, but sanse the same.
Unfortunately the data - _srlz.data is empty. So if I try to save data via _srlz.save() method then nothing change.
Otherwise if I request an existed object
p = Person.objects.filter(id = 1)
_srlz = PersonSerializer(data = p)
Then my _srlz.data will not be empty.
How should I save this object if datasource is not JSON-object, but model-class instance?Should I first serialize it and then deserialize again?
I already tried to save instance calling a .save() method, provided by model's default Manager, but I would like to separate my model and service methods so all database operations performed by serializers.
Or is it OK in this case, because I deal not with pure JSON, but with model instance? But how can I then validate data?

Mongoengine : How to update specific fields of an existing document?

I have an existing mongo document which has been exposed over a REST API. The API request will contain certain fields from the document which either needs to be updated with new values or insert new values in them if the field is null. How to perform the update on fields of an existing mongoengine document? I'm using marshmallow-mongoengine for serialization on flask.
The problem that I'm facing is that if a certain field is missing in the request payload, on calling update with the remaining fields as kwargs leads to setting the missing fields as None. How can update or insert only the fields given in the payload?
Joseph's answer is OK. But another answer wont hurt eh!
Here's how i updated my document using flask-mongoengine
Actual code :
Game.objects(id = _id).update(
set__kickoff = request_json.get('kickoff'),
set__gameid = request_json.get('gameid'),
set__home_team = request_json.get('home_team'),
set__away_team = request_json.get('away_team'),
set__home_win = request_json.get('home_win'),
set__draw = request_json.get('draw'),
set__away_win = request_json.get('away_win'),
set__sport = request_json.get('sport')
)
Game class :
import datetime
flask_mongoengine import BaseQuerySet, MongoEngine
db = MongoEngine()
class Game(db.Document):
kickoff = db.DateTimeField(required=True)
added_on = db.DateTimeField(default=datetime.datetime.utcnow)
gameid = db.FloatField(required=True)
home_team = db.StringField(required=True)
home_win = db.FloatField(required=True)
draw = db.FloatField(required=True)
away_win = db.FloatField(required=True)
away_team = db.StringField(required=True)
sport = db.StringField(required=True)
meta = {
'collection':'games',
'queryset_class': BaseQuerySet
}
PS : Remember to indent the code in python
Additionally I noticed you tagged Marshmallow in your question. Here, a sample derived from their official git repo here
First we need a Mongoengine Document:
import mongoengine as me
class Task(me.EmbeddedDocument):
content = me.StringField(required=True)
priority = me.IntField(default=1)
class User(me.Document):
name = me.StringField()
password = me.StringField(required=True)
email = me.StringField()
tasks = me.ListField(me.EmbeddedDocumentField(Task))
Great ! Now it's time for the Marshmallow Schema. To keep things DRY, we use marshmallow-mongoengine to do the mapping:
import marshmallow_mongoengine as ma
class UserSchema(ma.ModelSchema):
class Meta:
model = User
Finally it's time to use our schema to load/dump documents:First let's create a document
user_schema = UserSchema()
u, errors = user_schema.load({"name": "John Doe", "email":
"jdoe#example.com", "password": "123456","tasks": [{"content": "Find a
proper password"}]})
u.save()
If the document already exists, we can update it using update
u
u2, errors = user_schema.update(u, {"name": "Jacques Faite"})
>>> u2.name
"Jacques Faite"
If you only want to update one single document you can use the save method. That's what I do. If a document already exists, it updates fields instead of creating a new document.
car = Car.objects(pk=car_id) # return a queryset
if car:
car = car.get(pk=car_id) # return an object from queryset
car.make = requestData['make']
car.model = requestData['model']
car.mileage = requestData['mileage']
car.save()
If you want to update many documents then, I recommend checking out the atomic updates section of the user guide.
Something like~
Car.objects(param="param to filter by").update(set__param=newParam)
"set" followed by two underscores is a modifier. There are more modifiers available in the guide I linked above.

MapField is not displayed in Django Rest Framework Mongoengine

I have a model with following attributes.
class File(DynamicDocument):
country = fields.StringField(max_length=100, unique=True)
languages = fields.MapField(fields.MapField(
fields.EmbeddedDocumentField(AudioImage)))
I am trying to use Django Rest Framework Mongoengine as follows:
from rest_framework_mongoengine.serializers import DocumentSerializer
class TestSerializer(DocumentSerializer):
class Meta:
model = File
It simply gives the following output:
But I wanted it to address the tree like structure with all the fields from AudioImage class as well.
Did I miss anything? or There is another way for MapField ?
Sijan, is it correct that you want your File documents to have the following structure:
{
"country": "UK",
"languages": {
"hindi": AudioImageJSON,
"russian": AudioImageJSON,
"cockney": AudioImageJSON
}
}
where the structure of AudioImageJSON is described by corresponding EmbeddedDocument?
In that case, your DocumentSerializer is correct and your specify your model as follows:
class AudioImage(EmbeddedDocument):
content = fields.FileField()
class File(DynamicDocument):
country = fields.StringField(max_length=100, unique=True)
languages = fields.MapField(fields.EmbeddedDocumentField(AudioImage))
Note that Browsable API won't be able to display nested form inputs for EmbeddedDocument fields. But you may still use raw data view.