I'm working on a API for a project and I have a relationship Order/Products through OrderProducts like this:
In catalog/models.py
class Product(models.Model):
...
In order/models.py
class Order(models.Model):
products = models.ManyToManyField(Product, verbose_name='Products', through='OrderProducts')
...
class OrderProducts(models.Model):
order = models.ForeignKey(Order)
product = models.ForeignKey(Product)
...
Now, when I load an Order through the API I'd like to get the related Products as well, so I tried this (with django-tastypie):
In order/api.py
class OrderResource(ModelResource):
products = fields.ToManyField('order.api.OrderProductsResource', products, full=True)
class Meta:
queryset = Order.objects.all()
resource_name = 'order'
class OrderProductsRessource(ModelResource):
order = fields.ToOneField(OrderResource, 'order')
class Meta:
queryset = OrderProducts.objects.all()
resource_name = 'order/products'
which give me this error message: "'Product' object has no attribute 'order'". So I'm not sure what's wrong or missing, it probably requires something in my Product resource as well but I tried several way without success. Any help would be welcome :)
The problem is with this line:
order = fields.ToOneField(OrderResource, 'order')
The error is pretty straight-forward. Product really doesn't have an attribute named order. Your OrderProduct join table does, but your M2M doesn't return OrderProducts it returns Products.
Related
I have an entity model, and i want to list all the instances of Entity based on the id of a category instance in it. So basically i'd like to list all the Entity instances with category id of 1 with /specialists/category/1 but for some reason i get
Not Found: /api/v1/specialists/categories/1
Even though the code looks alright.
class Entity(models.Model):
entity_category = models.ManyToManyField(Category)
class SpecialistSerializer(serializers.ModelSerializer):
entity_category = SpecialistCategorySerializer(read_only=True, many=True)
class Meta:
model = Entity
fields = (....., entity_category)
class SpecialistsPerCategory(generics.ListAPIView):
serializer_class = SpecialistSerializer
def get_queryset(self):
category_id = self.kwargs['pk']
return Entity.objects.filter(entity_category=category_id, lookup_type='in')
path('specialists/category/<int:pk>', SpecialistsPerCategory.as_view()),
Do you have any idea what's wrong?
You have category in path, but plural categories in url.
Also I think that will not work, because SpecialistSerializer have model of Entity, but you want to get pk of Category in SpecialistsPerCategory that is based on that serializer.
I've got an Area model allowing sub areas (you might think of it as categories with subcategories). I reached this by nesting one field to self as foreign key.
class Area(models.Model):
area = models.CharField(max_length=120)
parent = models.ForeignKey('self', models.CASCADE, blank=True, null=True, related_name='subarea')
def __str__(self):
return self.area
With the django rest framwork I've manages to get the correct output. The problem is that when I analyze the request with django-toolbar multiple duplicated requests are made (N*Area(parent=None)). I've solved similar issues by using prefetch_related or select_related. But never done it with a nested model. Is there any way to solve this? Or is this design of the model bad?
I manage to serialize the correct output with the following view and
class ListArea(generics.ListCreateAPIView):
serializer_class = AreaSerializer
queryset = Area.objects.prefetch_related('parent').filter(parent=None)
and serializers
class SubAreaSerializer(serializers.ModelSerializer):
class Meta:
model = Area
fields = ('area','id')
class AreaSerializer(serializers.ModelSerializer):
subarea=SubAreaSerializer(many=True)
class Meta:
model = Area
fields = ('area','id','subarea')
Or might those extra calls be due to the browsable API?
Solution
I solved this with help of the following thread Django: Does prefetch_related() follow reverse relationship lookup?
Instead of
queryset = Area.objects.prefetch_related('parent').filter(parent=None)
I should use
queryset = Area.objects.prefetch_related('parent').prefetch_related('subarea')
I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.
Is there a way to reference the same ForeignKey model/resource more than once in Tastypie?
Assume the models:
class Case(models.Model):
name = models.CharField(max_length=10)
class Interaction(models.Model):
case = models.ForeignKey(Case, related_name="interaction_cases")
type = models.CharField(max_length=2, choices=TYPE_CHOICES)
Assume the TastyPie resources:
class CaseResource(ModelResource):
type_one_interactions = fields.ManyToManyField('TypeOneInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
type_two_interactions = fields.ManyToManyField('TypeTwoInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
class Meta:
queryset = Case.objects.all()
class TypeOneInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeOneInteractionResource, self).get_object_list(request).filter(type='A')
class TypeTwoInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeTwoInteractionResource, self).get_object_list(request).filter(type='B')
Basically I am trying to create a single resource with two reverse resources to the same model with different data. When I access the CaseResource I see both TypeOneInteractionResource and TypeTwoInteractionResource in the result, but the data is not being filtered correctly.
I assume it has something to do with the "related_name" being the same and the way TastyPie does model joining internally. Has anybody been successful doing this? Is it even possible?
The reason is because get_object_list is not called at all when dehydrating the ToManyField for related resources (see https://github.com/toastdriven/django-tastypie/blob/master/tastypie/fields.py#L780).
Instead, you'd want to use the dehydrate_type_one_interactions and dehydrate_type_two_interactions methods on the CaseResource.
On the other hand, you can provide properties on the Case model that would return desired QuerySets and use those properties for attribute names in ManyToManyFields.
Given two related Django models A and B in a OneToMany relationship:
models.py
class A(models.Model):
name = models.CharField(max_length=5)
class B(models.Model):
name = models.CharField(max_length=5)
a = models.ForeignKey(A)
And given (potentially non-optimal) Tastypie resources:
api.py
class AResource(ModelResource):
bs = fields.ToManyField( 'projectname.api.BResource', 'bs', full = True)
class Meta:
queryset = A.objects.all()
class BResource(ModelResource):
a = fields.ToOneField( AResource, 'a', full = True)
class Meta:
queryset = B.objects.all()
Let's assume the database is empty so far. Now I have related external data, and would like to crowd the database it with both an instance of A and several instances of B.
What is the prettiest Tastypionic way to approach this problem? Is it possible to crowd both A and the Bs at once? Or do I need to crowd first A, and then crowd B supplying A's ID as the ForeignKey?
It would be great if someone could come up with an post example (using e.g. a python dictionary and httplib2, or curl).
Thanks a million.
The solution is here . Use the related name for tastypie fields which automatically populate the reverse relationship while creating multiple objects at once.
http://django-tastypie.readthedocs.org/en/v0.10.0/fields.html#tastypie.fields.RelatedField.related_name
RelatedField.related_name
Used to help automatically populate reverse relations when creating data. Defaults to None.
In order for this option to work correctly, there must be a field on the other Resource with this as an attribute/instance_name. Usually this just means adding a reflecting ToOneField pointing back.
Example:
class EntryResource(ModelResource):
authors = fields.ToManyField('path.to.api.resources.AuthorResource', 'author_set', related_name='entry')
class Meta:
queryset = Entry.objects.all()
resource_name = 'entry'
class AuthorResource(ModelResource):
entry = fields.ToOneField(EntryResource, 'entry')
class Meta:
queryset = Author.objects.all()
resource_name = 'author'
Use of related_name do the task. it maps the objects of related fields and automatically populates the relations when creating data.
as you did full=True on both side of your resources it will generate maximum recursion depth exceeded exception because both resources are full in each others.
Here is one solution involving ManyToMany instead of OneToMany relationships:
models.py
class B(models.Model):
name = models.CharField(max_length=5)
class A(models.Model):
name = models.CharField(max_length=5)
bs = models.ManyToManyField(B)
api.py
class BResource(ModelResource):
class Meta:
queryset = B.objects.all()
resource_name = 'b'
class AResource(ModelResource):
bs = fields.ToManyField( BResource, 'bs', related_name = 'a', full = True, null=True)
class Meta:
queryset = A.objects.all()
resource_name = 'a'
curl
curl -v -H "Content-Type: application/json" -X POST --data '{"name":"a_name1", "bs":[{"name":"b_name1"}, {"name": "b_name2"}]}' http:<my_path>/api/a/
httplib2.py
A working example to post data via a python script using the httplib2 package is based on a neat and simple solution posted by warren-runk:
post_dict(
url='http:<my_path>/api/a/',
dictionary={
'name' : 'a_name1',
'bs' : [
{'name' : 'b_name1'},
{'name' : 'b_name1'},
]
}
)
However, now an additional table to relate A and B is created in the database. There might be better solutions based on the OneToMany relationship of A and B?