how to use tastypie reverse relation - django

When I do something like this http://example.com/api/v1/nisit/?format=json. I will get this error "The model '' has an empty attribute 'page' and doesn't allow a null value."
I want to use reverse relation of tastypie. reverse to the "page" model form "nisit" model. the result that i want is to call 127.0.0.1:8000/api/v1/nisit/?format=json&friend_followingPage_id=1
The point of problem is "how can to set followingPage attribute in Nisit model is the reverse relation to the followingNisit attribute in Page model
this is my model
class Nisit(models.Model):
friends = models.ManyToManyField('self',null=True,blank=True)
class Page(models.Model):
followingNisit = models.ManyToManyField(Nisit,blank=True)
this is my resource
class NisitResource(ModelResource):
friend = fields.ManyToManyField('self','friend',null=True)
followingPage = fields.ToManyField('chula.api.resourse.PageResource','followingPage')
class Meta:
queryset = Nisit.objects.all()
resource_name = 'nisit'
filtering = {
'friend' : ALL_WITH_RELATIONS,
'id' : ALL,
}
In above code. I try to code >>>> page=................. according to this link http://django-tastypie.readthedocs.org/en/latest/resources.html#reverse-relationships
but it can't help
class PageResource(ModelResource):
followingNisit = fields.ManyToManyField(NisitResource, 'followingNisit',null=True)
class Meta:
queryset = Page.objects.all()
resource_name = 'page'
authorization= Authorization()
filtering = {
'followingNisit': ALL_WITH_RELATIONS,
'p_name': ALL,
}

Try adding:
class Page(models.Model):
followingNisit = models.ManyToManyField(Nisit,blank=True, related_name="followingPage")
Clear your cache and maybe remove all your null=True might suppress that error message.

Related

django swagger api returned object url instead of readable name

I have an model which is for mapping book(item) to categories(tag),
it shows like this in the django admin page.
id item_uid tag_uid
407 Food Recipe
but in django swagger page, when I try to GET this mapping api with ID 407, it returned like this:
"id": 407,
"item_uid": "http://127.0.0.1:8000/items/237/";
"tag_uid": "http://127.0.0.1:8000/tags/361/"
as you can see, it mapped together correctly, but the response body showed the object url and it's object id, which is not readable for human users. I wonder that if there is anyway to make them like this:
"id": 407,
"item_uid": "Food";
"tag_uid": "Recipe"
edit: codes,
#models.py
class Map_item_tag(models.Model):
item_uid = models.ForeignKey(items, on_delete=models.CASCADE, verbose_name='Item UID')
tag_uid = models.ForeignKey(tags, on_delete=models.CASCADE, verbose_name='Tag UID')
#admin.py
#admin.register(Map_item_tag)
class map_item_tag_admin(ImportExportModelAdmin):
resource_class = map_item_tag_Resource
readonly_fields = ('id',)
list_display = ['id','item_uid','tag_uid']
#serializers.py
class Map_item_tag_Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
#views.py
class Map_item_tag_ViewSet(viewsets.ModelViewSet):
queryset = Map_item_tag.objects.all().order_by('item_uid')
serializer_class = Map_item_tag_Serializer
parser_classes = (FormParser, MultiPartParser)
permission_classes = [permissions.IsAuthenticated]
thank you for answering!
It seems you are using a HyperlinkedModelSerializer instead of a regular ModelSerializer
Try changing the serializer class to a ModelSerializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [] # list of fields you want to include in your Item serializer
class Map_item_tag_Serializer(serializers.ModelSerializer):
item_uid = ItemSerializer()
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
In addition, I would advise you to use CamelCase notation for all your classes. For example: instead of using Map_item_tag_Serializer, change the name to MapItemTagSerializer. The same goes for all your other classes.
I would also avoid using using the _uuid suffix when using ForeignKey relationships. In the MapItemTag model, the ForeignKey relationship inherently means that the field will point to an object Item of Tag object. Hence, no need to specify the _uuid part again.
For example, the following changes would make the model a lot more readable:
class MapItemTag(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, verbose_name='map_item')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, verbose_name='map_tag')

How to make custom response in djangorestframework

So I like the idea of using class-based views and ModelSerializers but I have an issue with it for my particular use case. Maybe I am not using it as it's intended to be used.
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = CarModel
fields = ['car_name']
# A car can have build for multiple years
class MakelHistorySerializer(serializers.ModelSerializer):
car = CarSerializer(many=True, read_only=True)
class Meta:
model = MakeHistoryModel
fields = ['model_year', 'car']
The response is:
{
"car": {
"car_name": "Fiesta"
},
"model_year": "2020"
}
My two model classes, CarModel and MakeHistoryModel have ["id", "car_name", "manufacturer"] and ["id", "car_id", "model_year", "country_id"] fields respectively.
What kind of a response I really want is:
{
"car_name": "Fiesta",
"model_year": "2020"
}
How would I do this?
You don't need to first serializer (CarSerializer).Just this serializer which has SerializerMethodField enough for your output:
class MakelHistorySerializer(serializers.ModelSerializer):
car_name = serializers.SerializerMethodField()
class Meta:
model = MakeHistoryModel
fields = ['model_year', 'car_name']
def get_car_name(self,obj):
return obj.car.name if obj.car_id else ''
# I don't know your model so to avoid NoneType error, I added this check

Avoid nested objects when using nested serializers

I have two models, one contains the other in a foreignKey relationship, I wanted to make an API that would return the combine of these two models, so I attempted to use nested Serializers to add the related model as well, but the data are not all on the same level, the related models is a object inside the first.
Here are the Models
class ModelOne(models.Model):
last_counter = models.IntegerField()
class ModelTwo(models.Model):
model_one = models.ForeignKey(ModelOne, on_delete=models.CASCADE)
category = models.CharField(max_length=64)
counter_type = models.CharField(max_length=32)
Here are the serializers
class ModelOneSerializer(serializers.ModelSerializer):
class Meta:
model = ModelOne
fields = "__all__"
class ModelTwoSerializer(serializers.ModelSerializer):
model_one= ModelOneSerializer(read_only=True)
class Meta:
model = ModelTwo
fields = "__all__"
This would return from the API in the form of
{
"category" : ...,
"counter_type" : ...,
"model_one" : {
"last_counter" : ...
}
}
But I don't want the response to be like that, I want it more like this
{
"category" : ...,
"counter_type" : ...,
"last_counter" : ...,
}
Is there a way to achieve this through serializers?
Use SerializerMethodField
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = SerializerMethodField()
class Meta:
model = ModelTwo
fields = "__all__"
def get_last_counter(self, obj):
return ModelOneSerializer(obj.model_one).data['last_counter']
When creating custom fields(field_one for example) with SerializerMethodField, you have to create a method called get_field_one, for this method to be automatically detected by the serializer.
You can achieve what you want to do using SerializerMethodField from drf fields:
SerializerMethodField is a read-only field that computes its value at request processing time, by calling a method on the serializer class it is attached to. For example for your case it will look like this. Notice that the computed last_counter is added on the serialized model fields.
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = serializers.SerializerMethodField()
class Meta:
model = ModelTwo
fields = ["category", "counter_type", "last_counter"]
def get_last_counter(self, obj):
return int(obj.model_one.last_counter)
SerializerMethodField accepts method_name, but it’s usually more convenient to use the default pattern for naming those methods, which is get_. Just make sure you‘re not overburdening your method fields with any heavy-lifting operations.
You can read more on the official documentation:enter link description here

Serialize relation both ways with Django rest_framework

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.

Tastypie Reverse Relation

I am trying to get my api to give me the reverse relationship data with tastypie.
I have two models, DocumentContainer, and DocumentEvent, they are related as:
DocumentContainer has many DocumentEvents
Here's my code:
class DocumentContainerResource(ModelResource):
pod_events = fields.ToManyField('portal.api.resources.DocumentEventResource', 'pod_events')
class Meta:
queryset = DocumentContainer.objects.all()
resource_name = 'pod'
authorization = Authorization()
allowed_methods = ['get']
def dehydrate_doc(self, bundle):
return bundle.data['doc'] or ''
class DocumentEventResource(ModelResource):
pod = fields.ForeignKey(DocumentContainerResource, 'pod')
class Meta:
queryset = DocumentEvent.objects.all()
resource_name = 'pod_event'
allowed_methods = ['get']
When I hit my api url, I get the following error:
DocumentContainer' object has no attribute 'pod_events
Can anyone help?
Thanks.
I made a blog entry about this here: http://djangoandlove.blogspot.com/2012/11/tastypie-following-reverse-relationship.html.
Here is the basic formula:
API.py
class [top]Resource(ModelResource):
[bottom]s = fields.ToManyField([bottom]Resource, '[bottom]s', full=True, null=True)
class Meta:
queryset = [top].objects.all()
class [bottom]Resource(ModelResource):
class Meta:
queryset = [bottom].objects.all()
Models.py
class [top](models.Model):
pass
class [bottom](models.Model):
[top] = models.ForeignKey([top],related_name="[bottom]s")
It requires
a models.ForeignKey relationship from the child to the parent in this case
the use of a related_name
the top resource definition to use the related_name as the attribute.
Change your line in class DocumentContainerResource(...), from
pod_events = fields.ToManyField('portal.api.resources.DocumentEventResource',
'pod_events')
to
pod_events = fields.ToManyField('portal.api.resources.DocumentEventResource',
'pod_event_set')
The suffix for pod_event in this case should be _set, but depending on the situation, the suffix could be one of the following:
_set
_s
(no suffix)
If each event can only be associated with up to one container, also consider changing:
pod = fields.ForeignKey(DocumentContainerResource, 'pod')
to:
pod = fields.ToOneField(DocumentContainerResource, 'pod')