Django Rest Framework - Serializer error when trying to add model from json with 1 attribute as array - rest

Im trying to create an API with Django RestFramework to save some info of computers.
I have encountered a problem when the json has an attribute that is an array of IPv4 field.
I generated the following code
Model
class Computer(models.Model):
hostname = models.CharField(max_length=32)
os_system = models.CharField(max_length=60)
class ComputerIPAddress(models.Model):
computer = models.ForeignKey(Computer,on_delete=models.CASCADE)
ip_address = models.GenericIPAddressField()
Serializer
class ComputerIPAddressSerializer(serializers.ModelSerializer):
class Meta:
model = ComputerIPAddress
fields = ('__all__')
class ComputerSerializer(serializers.ModelSerializer):
ip_address = ComputerIPAddressSerializer(many=True)
class Meta:
model = Computer
fields = ('__all__')
Viewset
class ComputerViewSet(viewsets.ModelViewSet):
queryset = Computer.objects.all()
serializer_class = ComputerSerializer
class ComputerIPAddressViewSet(viewsets.ModelViewSet):
queryset = ComputerIPAddress.objects.all()
serializer_class = ComputerIPAddressSerializer
The idea is that the IP belongs to the computer (if the computer is deleted, I am not interested in having the IP) and a computer can have several IP assigned to it.
The json that is sent is the following:
{'hostname':'PC-01','os_system':'Windows 10','ip_address':['192.168.1.10','192.168.2.10']}

I would approach this problem by overriding the create method the ComputerSerializer
class ComputerSerializer(serializer.ModelSerializer):
...
def create(self, validated_data):
ip_address = validated_data.pop("ip_address", None)
computer = Computer.objects.create(**validated_data)
if ip_address:
for ip in ip_address:
computer.computeripaddress_set.create(ip)
return computer

Related

How to extend django's default User in mongodb?

I'm using mongodb as database and trying to extend the django's inbuilt user model.
here's the error I'm getting:
django.core.exceptions.ValidationError: ['Field "auth.User.id" of model container:"<class \'django.contrib.auth.models.User\'>" cannot be of type "<class \'django.db.models.fields.AutoField\'>"']
Here's my models.py:
from djongo import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.EmbeddedField(model_container=User)
mobile = models.PositiveIntegerField()
address = models.CharField(max_length=200)
pincode = models.PositiveIntegerField()
Using EmbeddedField is not a good idea, because it will duplicate user data in the database. You will have some user in the Users collection and the same data will be embedded in the Profile collection elements.
Just keep the user id in the model and query separately:
class Profile(models.Model):
user_id = models.CharField() #or models.TextField()
mobile = models.PositiveIntegerField()
address = models.CharField(max_length=200)
pincode = models.PositiveIntegerField()
It is simple as defined in the documentation.
So, first, use djongo models as the model_container, and I suppose the User model is the Django model, not the djongo model.
And the second thing, make your model_cotainer model abstract by defining in the Meta class as given below.
from djongo import models
class Blog(models.Model):
name = models.CharField(max_length=100)
class Meta:
abstract = True
class Entry(models.Model):
blog = models.EmbeddedField(
model_container=Blog
)
headline = models.CharField(max_length=255)
Ref: https://www.djongomapper.com/get-started/#embeddedfield

How to custom the table name in peewee?

I want to define a table which the table name is gobang_server,i write code as follow:
class BaseModel(Model):
class Meta:
database = database
class GobangServer(BaseModel):
time = DateField(default=datetime.datetime.now())
name = CharField(max_length=64)
host = CharField(max_length=30)
port = IntegerField()
pid = IntegerField()
but i look at PostgreSQL the table name is "gobangserver"?
How can i define with the table name is gobang_server and the class name is not be modified.
class GobangServer(BaseModel):
...
class Meta:
db_table = 'gobang_server'
In peewee 3.0 it changes from "db_table" to "table_name".

EmbeddedDocumentSerializer runs query for every ReferenceField

I have following models and serializer the target is when serializer runs to have only one query:
Models:
class Assignee(EmbeddedDocument):
id = ObjectIdField(primary_key=True)
assignee_email = EmailField(required=True)
assignee_first_name = StringField(required=True)
assignee_last_name = StringField()
assignee_time = DateTimeField(required=True, default=datetime.datetime.utcnow)
user = ReferenceField('MongoUser', required=True)
user_id = ObjectIdField(required=True)
class MongoUser(Document):
email = EmailField(required=True, unique=True)
password = StringField(required=True)
first_name = StringField(required=True)
last_name = StringField()
assignees= EmbeddedDocumentListField(Assignee)
Serializers:
class MongoUserSerializer(DocumentSerializer):
assignees = AssigneeSerializer(many=True)
class Meta:
model = MongoUser
fields = ('id', 'email', 'first_name', 'last_name', 'assignees')
depth = 2
class AssigneeSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Assignee
fields = ('assignee_first_name', 'assignee_last_name', 'user')
depth = 0
When checking the mongo profiler I have 2 queries for the MongoUser Document. If I remove the assignees field from the MongoUserSerializer then there is only one query.
As a workaround I've tried to use user_id field to store only ObjectId and changed AssigneeSerializer to:
class AssigneeSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Assignee
fields = ('assignee_first_name', 'assignee_last_name', 'user_id')
depth = 0
But again there are 2 queries. I think that the serializer EmbeddedDocumentSerializer fetches all the fields and queries for ReferenceField and
fields = ('assignee_first_name', 'assignee_last_name', 'user_id')
works after the queries are made.
How to use ReferenceField and not run a separate query for each reference when serializing?
I ended up with a workaround and not using ReferenceField. Instead I am using ObjectIdField:
#user = ReferenceField("MongoUser", required=True) # Removed now
user = ObjectIdField(required=True)
And changed value assignment as follows:
- if assignee.user == MongoUser:
+ if assignee.user == MongoUser.id:
It is not the best way - we are not using ReferenceField functionality but it is better than creating 30 queries in the serializer.
Best Regards,
Kristian
It's a very interesting question and I think it is related to Mongoengine's DeReference policy: https://github.com/MongoEngine/mongoengine/blob/master/mongoengine/dereference.py.
Namely, your mongoengine Documents have a method MongoUser.objects.select_related() with max_depth argument that should be large enough that Mongoengine traversed 3 levels of depth: MongoUser->assignees->Assignee->user and cached all the related MongoUser objects for current MongoUser instance. Probably, we should call this method somewhere in our DocumentSerializers in DRF-Mongoengine to prefetch the relations, but currently we don't.
See this post about classical DRF + Django ORM that explains, how to fight N+1 requests problem by doing prefetching in classical DRF. Basically, you need to override the get_queryset() method of your ModelViewSet to use select_related() method:
from rest_framework_mongoengine.viewsets import ModelViewSet
class MongoUserViewSet(ModelViewSet):
def get_queryset(self):
queryset = MongoUser.objects.all()
# Set up eager loading to avoid N+1 selects
queryset.select_related(max_depth=3)
return queryset
Unfortunately, I don't think that current implementation of ReferenceField in DRF-Mongoengine is smart enough to handle these querysets appropriately. May be ComboReferenceField will work?
Still, I've never used this feature yet and didn't have enough time to play with these settings myself, so I'd be grateful to you, if you shared your findings.

Grails MongoDB Embedded Domain Objects Saving

When attempting to save a Grails Domain object that is backed by Mongo all of my embedded objects are having save() called on them as well. In some instances this is causing some tremendous affect on performance.
Some example domain objects are as follows.
class Bird {
Object Id
String name
}
class Nest {
static embedded = ['birds']
String name
Set<Bird> birds
}
class Tree {
static embedded = ['nests']
Object Id
String name
}
class TreeState {
static embedded = ['tree']
Object Id
Set<Nest> nests
Tree tree
Date dateCreated
}
Now let's say I wanted to save the state of a tree at a given time.
def tree = Tree.findByName("Sleepwood")
def nestA = new Nest()
nestA.birds = new LinkedHashSet()
nestA.name = "Sleepwood-A"
def nestB = new Nest()
nestB.birds = new LinkedHashSet()
nestB.name = "Sleepwood-B"
def blueJay = Bird.findByName('Blue Jay')
def cardinal = Bird.findByName('Cardinal')
def oriole = Bird.findByName('Oriole')
def robin = Bird.findByName('Robin')
def treeState = new TreeState()
treeState.nests = new LinkedHashSet()
treeState.tree = tree
nestA.add(blueJay)
nestA.add(cardinal)
nestA.add(oriole)
nestB.add(oriole)
nestB.add(blueJay)
nestB.add(robin)
treeState.nests.add(nestA)
treeState.nests.add(nestB)
treeState.safe(failOnError: true)
This sort of action seems to cause the embedded entities, Bird and Tree to be saved as values but also seem to be saved as well to update their associated domain models.
Is there a way I can prohibit these references from being updated and just save the embedded fields in the TreeState object?

How to store domain reference as a dynamic attribute of another domain in Grails with MongoDB?

I would like to be able to store a reference to a domain object as a dynamic attribute of another domain.
Unfortunately, when I try to save such domain object I get:
| Error 2013-02-18 14:03:09,352 [localhost-startStop-1] ERROR context.GrailsContextLoader - Error initializing the application: can't serialize class Employee
Message: can't serialize class Employee
Here's the code [Machine & Employee are domains]:
def m = new Machine(name: "Machine 01")
def e = Employee.findByName("employee name")
m['operator'] = e
m.save(failOnError: true)
I'm wondering if this is possible? I think it should, there are no limitations listed on mongodb plugin [I'm using 1.1.0GA with Grails 2.2.0].
class Machine {
String name
}
class Employee {
String name
}