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

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.

Related

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.

Django validate data when updating model with primary key

I am having trouble with updating fields of a model instance. The model is as follows:
class commonInfo(models.Model):
mothers_id = models.IntegerField(primary_key=True)
date = models.DateField()
data_collector = models.CharField(max_length=50)
Essentially, I just want to do this, but it won't work because commonInfo has a user defined primary key
commonInfo_form(request.POST or None).is_valid()
Since I am updating, I am overriding date and data_collector, but not mothers_id. So I would want to do something like this, but this specific code is not working
obj = commonInfo.objects.get(pk=commonInfo_id)
form = commonInfo_form(request.POST)
date = form.cleaned_data['data_collector'] #this line is not working
data_collector = form.cleaned_data['data_collector'] #this line is not working
obj.update(**{'date':date, 'data_collector':data_collector})
any ideas? I feel like it is just those two lines that I need to fix. Or if there is a more pythonic way or built method in Django?
Just validate with isinstance. so like,
if isinstance(request.POST['date'], datetime.date) and isinstance(request.POST['data_collector'], str):
# you might have to use getattr for request.POST here, I'm not sure
# and request.POST['date'] would have to be converted from a string to datetime.date I think
date = request.POST['date']
data_collector = request.POST['data_collector']
obj.update(**{'date':date, 'data_collector':data_collector})
The process for adding a record from a form is different from updating an existing instance. All you need to do differently is indicate which instance to bind the form to when you create it, ex:
obj = commonInfo.objects.get(pk=commonInfo_id)
form = commonInfo_form(request.POST, instance=obj)

Tastypie + Django: How to filter on sub-documents?

I am trying to implement filtering in one of my data APIs built on Django(non-rel, mongodb-engine) + Tastypie(non-rel). I want to implement filtering on a sub-document inside my main document. The main documents in mongo looks like
{"user_name": "abc", "email": "abc#domain.com", "safety" :{"intra": True, "inter": False, "external": ['a', 'b']}}
The sub-document contains 2 boolean fields and list field. In mongo I can easily query on sub-documents and their fields however I am not able to implement it at the API level.The django model looks like this
from djangotoolbox.fields import DictField, EmbeddedModelField
class UserSafety(models.Model):
user_name = models.CharField()
email = models.EmailField()
safety = DictField() <------- What to use here!
The corresponding Tasty-pie resource looks like
from tastypie_nonrel.fields import DictField, EmbeddedModelField
class UserSafetyResource(ModelResource):
user_name = field.CharField(attribute='user_name')
email = fields.CharField(attribute='email')
safety = DictField(attribute='safety') <----What to use here!
# This part i want to add
class Meta:
filtering = ['safety.intra': ALL, 'safety.inter': ALL]
Eventually I would like it to work this like in an HTTP request
GET http://<server>/my_endpoint/usersafety?safety.intra=true
Any ideas how this can be achieved? May be using either the embedded type fields?

Django error when following relationships backward

I have two models:
Base_Activity:
topics = models.ManyToManyField(Topic)
... some others
User_Activity:
user = models.ForeignKey(settings.AUTH_USER_MODEL)
activity = models.ForeignKey(Base_Activity)
is_archived = models.BooleanField(default=False)
Now I want to query Base_activity to select all rows with topic X and exclude any rows that have a matching row in User_Activity for user=*current_user* and is_archived=True.
I have read the Django docs on how to follow relationships backward, since I query Base_Activity, but need information from User_Activity which has a ForeignKey to the former. However, even testing this method in the Django console doesn't work:
a = Base_Activity.objects.filter(topics__slug = topic)
a.user_activity_set.all()
AttributeError: 'InheritanceQuerySet' object has no attribute 'user_activity_set'
Question: What is the best way to do my query? If this is indeed by following the ForeignKey backwards, then what am I doing wrong?
a = Base_Activity.objects.filter(topics__slug = topic)
This returns a QuerySet instance, not a model instance. You should iterate through it or just get one from the list:
activities = Base_Activity.objects.filter(topics__slug=topic)
activities[0].user_activity_set.all()
In your case you can do entire work in one query:
activities = Base_Activity.objects.filter(topics__slug=topic).exclude(user_activity__user=user, user_activity__is_archived=True)
I'm not sure this will solve your problem, but anyway please don't use underscores in your class names in Python.

django save model ForeignKey relation

in my model.py
class Layer(models.Model):
user = models.IntegerField()
name = models
...
class Point(models.Model):
layers = models.ForeignKey(Layer)
meta = models.TextField()
...
in my view.py
def datasave(request, id):
mid = request.POST.get("layerid",default = "")
metas = request.POST.get("meta",default = "")
cs = Point()
cs.layers = mid
cs.meta = metas
cs.save()
but it gives an error in my django debug..in my project i use geodjango,openlayers and extjs... i didnt find any solution about saving my post
i didnt make any relation with my foreignkey.. basically i want to make a layer than when i want to add a point in my layer , i want save my point with layer id....
A more efficient way is to specify the foreign key by adding an "_id" at the end, like this:
cs = Point(layers_id = mid, meta = metas)
cs.save()
DO NOT do layers=XXX.objects.get(id=###) because that's doubling the number of database queries.
ALSO ... you're not cleaning your POST data here, which is pretty dangerous. Get it like this:
from django import forms
id_field = forms.IntegerField()
layer_id = id_field.clean(request.POST.get("layerId", "")
It's helpful to post the traceback to help understand your problem (for future questions).
It looks to me like you are passing in a number to your Point instance's layers attribute which would cause a ValueError. The only place you can pass a number to a foreign key field is in a django form which does the lookup for you.
You need to assign an actual Layer instance instead.
cs = Point()
cs.layers = Layer.objects.get(id=mid)
cs.meta = metas
cs.save()