Tastypie ordering of nested relationships - tastypie

I am using a nested relationship to return grouped messages and was wondering how i can order the nested messages so that they are ordered chronologically.
class MessageGroupResource(ModelResource):
messages = fields.ToManyField('lookup.api.MessageResource', 'message_set', full=True);
class Meta:
queryset = MessageGroup.objects.all();
resource_name = 'message'
authorization= UserAuthorization()
ordering = [
'sendTime',
]
class MessageResource(ModelResource):
messageGroup = fields.ForeignKey(MessageGroupResource, 'messageGroup')
class Meta:
queryset = Message.objects.all()
resource_name = 'submessage'
authorization= UserAuthorization()

This is one way to do it; however, I don't know if there's a better/more API standard way of doing this.
class MessageGroupResource(ModelResource):
messages = fields.ToManyField(MessageResource,
attribute=lambda bundle: bundle.obj.MessageResource.all().order_by("sendTime"))

Related

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

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

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

JPA and "anonymous" classes in scala

I'm a bit stuck and don't understand what's going on.
This one doesn't work
#Entity
#DynamicInsert
#DynamicUpdate
#SelectBeforeUpdate
#Table
class Entity {
#Column(nullable = false)
var owner: String = _
}
val myEntity = new Entity() {
owner = "some owner 1"
}
session.persist(myEntity)
Hibernate throws exception:
java.lang.IllegalArgumentException: Unknown entity:persistence.dao.EntityDaoTest$$anonfun$13$$anonfun$14$$anon$5
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777)
This one works:
val myEntity = new Entity()
entity.owner = "some owner 1"
session.persist(myEntity)
Why? Why does hibernate don't recognize my Entity instance?
UPD:
#Sheinbergon, thanks, it's clear. I completely forgot that annotations are lost. Is there any possibility to set entity fields with some shortcut?
writing
val myEntity = new MyEntity()
myEntity.owner = "some owner"
myEntity.someOtherProperty = "value"
is super boring
One more question
This one works:
val parent = new Parent
parent.owner = "Our parent"
parent.addChild(new Child() {
name = "First parent's child"
addGrandChild(new GrandChild() {
name = "Grand child name"
addGrandGrandChild(new GrandGrandChild() {
name = "Grand Grand child name"
address = new Address() {
id = 1L
}
})
})
})
Why? Child, GrandChild, GrandGrandChild also created anonymously.
addChild, addGrandChild, addGrandGrandChild are just list mutators.
def addChild(child: Child): Unit = {
if (children == null) {
children = new util.ArrayList[Child]()
}
if (Option(child.parent).isEmpty) {
child.parent = this
}
children.add(child)
}
What you are doing here is instantiating a class anonymously in Scala , and well... that creates an anonymous implementation of your class Entity ( like instantiating an interface anonymously in Java).
you can see it by printing the class name - println(myEntity.getClass) in both cases
Annotations applied to the original class do not apply to the anonymous one (reflection can still find them in the super class, but that's up to the code scanning them) and I guess that's why you're getting the various JPA exceptions
In response to your added sub-questions
Regarding a shortcut - why don't you use companion objects for factories or turn this class into a case class (with defaults), allowing for nicer, more flexible initialization.
Regarding the second object graph(and assuming eachof your classes are annotated) - again it depends on how the reflective code treats the objects it scans. it's possible ( and more likely, given that it won't scan each member of the collection for annotations ) it takes annotation definitions from the erased type ( possible to get it's FQDN class name as ParameterizedType in Java's reflection API) of the collection and not from the actual members of the collection and that's why it works.
I'm not really sure what it does about field definitions though (they are only present in the "super" class), but there's no "magic" here, just plain old reflection scans.

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.

tastypie - List related resources keys instead of urls

When I have a related Resource, I would like to list foreign keys, instead of a url to that resource. How is that possible aside from dehydrating it?
I'm not sure that it's possible without dehydrating the field. I usually have utility functions that handle conversion the dehydration of foreign key and many-to-many relationships, something like this:
#api_utils.py
def many_to_many_to_ids(bundle, field_name):
field_ids = getattr(bundle.obj, field_name).values_list('id', flat=True)
field_ids = map(int, field_ids)
return field_ids
def foreign_key_to_id(bundle, field_name):
field = getattr(bundle.obj, field_name)
field_id = getattr(field, 'id', None)
return field_id
And apply them to the fields like so:
#api.py
from functools import partial
class CompanyResource(CommonModelResource):
categories = fields.ManyToManyField(CompanyCategoryResource, 'categories')
class Meta(CommonModelResource.Meta):
queryset = Company.objects.all()
dehydrate_categories = partial(many_to_many_to_ids, field_name='categories')
class HotDealResource(CommonModelResource):
company = fields.ForeignKey(CompanyResource, 'company')
class Meta(CommonModelResource.Meta):
queryset = HotDeal.objects.all()
dehydrate_company = partial(foreign_key_to_id, field_name='company')