How to include related resource with Django Rest Framework JSON API? - python

I am using Django Rest Framework JSON API to create a REST API. I am trying quite simply to include a related resource (2nd degree relation) but Django keeps responding with the error:
This endpoint does not support the include parameter for path...
The structure is something like this:
# models:
class Household(models.Model):
...
class HouseholdMember(models.Model):
household = models.ForeignKey(Household)
...
class Subscription(models.Model):
subscriber = models.ForeignKey(HouseholdMember)
...
# serializers
from rest_framework_json_api import serializers
class SubscriptionSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
I would like to be able to make a request like this: http://example.com/api/subscriptions?include=subscriber.household to be able to group subscriptions by household. However, I simply cannot find out how to do this. I know I need to play around with ResourceRelatedField but I'm missing something or too much of a newbie to understand how this works. Any help?

Well, perhaps I was missing something obvious (because this wasn't mentioned in the documentation), but if you look at the serializers.pyfile in the example directory of the source of Django Rest Framework JSON API, it looks like you need to have a variable called included_serializers to do what I wanted. For my example, here's what you would need:
# models:
class Household(models.Model):
...
class HouseholdMember(models.Model):
household = models.ForeignKey(Household)
...
class Subscription(models.Model):
subscriber = models.ForeignKey(HouseholdMember)
...
# serializers
from rest_framework_json_api import serializers
class HouseholdSerializer(serializers.ModelSerializer):
class Meta:
model = Household
class HouseholdMemberSerializer(serializers.ModelSerializer):
included_serializers = {
'household': HouseholdSerializer
}
class Meta:
model = HouseholdMember
class SubscriptionSerializer(serializers.ModelSerializer):
included_serializers = {
'subscriber': SubscriberSerializer
}
class Meta:
model = Subscription

Related

Object has no attribute get in serializer

I created a serializer and an API endpoint so I can retrieve some data from a Django DB in my React app but getting this error message:
AttributeError: 'ProgrammingChallengesView' object has no attribute 'get'
Here is my models.py:
#creating programming challenges
class ProgrammingChallenges(models.Model):
challenge_id = models.AutoField(primary_key=True)
challenge_name = models.CharField(max_length=200)
challenge_description = models.TextField()
challenge_expectations = models.TextField()
my serializer:
from accounts.models import ProgrammingChallenges
...
class ProgrammingChallengesView(serializers.ModelSerializer):
class Meta:
model = ProgrammingChallenges
fields = '__all__'
and my urls.py:
path('api/programming_challenges/', ProgrammingChallengesView, name='programming_challenges'),
Thanks to the comments; I clearly didn't understand that a serializer only transforms my data to make it available through an API. I still had to create a view for my API's endpoint.
I opted to create a ReadOnlyModelView because I only want to GET data from this endpoint.
Here is what I wrote in my views:
class ProgrammingChallengesView(ReadOnlyModelViewSet):
serializer_class = ProgrammingChallengesSerializer
queryset = ProgrammingChallenges.objects.all()
#action(detail=False)
def get_list(self, request):
pass
and in my urls.py:
path('api/programming_challenges/', ProgrammingChallengesView.as_view({'get':'list'}), name='programming_challenges'),
I think you shouldn't hurry read the docs again. You are trying to use serializers as views.
Models - are representation of db tables as class.
Serializer serializes the data to json.
View accepts the reqeust from client and returns a Response.
Code shoudld be:
models.py
class ProgrammingChallenge(models.Model):
name = models.CharField(max_length=200)
description = models.TextField()
expectations = models.TextField()
Your model name should be ProgrammingChallenge(singular) not ProgrammingChallenges(plural).
You should't add prefix challenge before all field names. Because we already know that the fields are in a Model called ProgrammingChallenge. And it is easy to access them like ProgrammingChallenge.name than ProgrammingChallenge.challenge_name
You don't have to add field id manually. Django model automatically adds id field as primary_key
serializer.py
from accounts.models import ProgrammingChallenge
...
class ProgrammingChallengeSerializer(serializers.ModelSerializer):
class Meta:
model = ProgrammingChallenge
fields = '__all__'
No problem in serialize.
Now, main problem is you don't have any view. You definetly read docs. You can use APIView, generic views or viewset. In this example i'm going to use ViewSet that handles CRUD operations built in.
viewsets.py
from rest_framework.viewsets import ModelViewSet
from .models import ProgrammingChallenge
from .serializers import ProgrammingChallengSerializer
class ProgrammingChallengViewSet(ModelViewSet):
queryset = ProgrammingChallenge.objects.all()
serializer_class = ProgrammingChallengeSerializer
urls.py
from rest_framework.routers import SimpleRouter
from .viewsets import ProgrammingChallenge
router = SimpleRouter()
router.register('challengr', ProgrammingChallengeViewSet)
urlpatterns = router.urls
Another advantage of using viewset, it also generate all endpoint for it's CRUD methods automatically via routes.
It should help you to start your first project.
AGAIN, READ THE DOCS!

Django dynamically generated serializer

Is there a way how to dynamically generate a Django rest framework serializers?
Considering this:
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = models.Blog
fields = get_all_model_fields(models.Blog)
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = models.Post
fields = get_all_model_fields(models.Post)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = get_all_model_fields(models.User)
I am wondering if something like following example could be possible:
from django.apps import apps
models = [model for model in apps.get_models()]
for model in models:
type(model.__name__+'Serializer',(serializers.ModelSerializer,),{
type("Meta",(),{
"model":model,
"fields": get_all_model_fields(model)
})
})
Or is there any other way how to generate DRF serializers?
Here's a function to build a ModelSerializer for a given Model (tested with Django 3.2, DRF 3.12.4 and Python 3.8):
from functools import lru_cache
from typing import Type
from django.db import models
from rest_framework import serializers
#lru_cache(maxsize=0)
def model_serializer(model: Type[models.Model]) -> Type[serializers.ModelSerializer]:
meta_class = types.new_class("Meta")
setattr(meta_class, "model", model)
setattr(meta_class, "fields", "__all__")
result = types.new_class(
model.__name__ + "Serializer", (serializers.ModelSerializer,), {}
)
setattr(result, "Meta", meta_class)
return result
If you are certain that you will call this function only once for each serializer, you can omit the #lru_cache to preserve some memory.
Example usage:
class MyModel(models.Model):
some = models.CharField(max_length=123)
other = models.IntegerField()
MyModelSerializer = model_serializer(MyModel)
my_serializer = MyModelSerializer({"some": "abc", "other": 1234})
my_serializer.is_valid(True)
To add the serializers for all your models to the current module's namespace:
from django.apps import apps
for model in apps.get_models():
serializer_type = model_serializer(model)
globals()[serializer_type.__name__] = serializer_type
Your approach will work - but for Django to find about these serializers you have to assign them to the module namespace.
In your code, you just call type - the serializer class is created and "thrown away" immediately. Even if the base Django class serializers.ModelSerializer keep a reference to it in a registry, you would not be able to import your serializers and make use of them.
All you have to do is to add them to the dictionary returned by globals(). Likewise, the namespace you create for the class also has to be a dictionary -since you are calling "type" but not actually assigning its name as "Meta", you create a set, not a dictionary, and your call to type will fail as it was expecting a dictionary.
So, I did not check if the code actually will work, but the idea, based on yours is:
from django.apps import apps
models = [model for model in apps.get_models()]
for model in models:
name = f"{model.__name__}Serializer"
globals()[name] = type(name,(serializers.ModelSerializer,),{
"Meta": type("Meta",(),{
"model":model,
"fields": get_all_model_fields(model)
})
})
del models, model, name

Using Swagger with Django Rest Framework, can I see POST parameters in different fields instead of one body

I'm actually create APIs on a Django Website using Django Rest Framework.
I'm trying to document them using Swagger.
I'm using Django 2.1, django-rest-swagger 2.2 and djangorestframework 3.11
Everything is nearly working as expected except something :
Let me explain you :
I have this model (models.py)
class Technology(models.Model):
"""
This model defines the different technologies
"""
name = models.CharField(max_length=CHAR_SHORT)
path = models.CharField(max_length=CHAR_SHORT, validators=[validate_tech_path], help_text='this is only used to construct the url')
image = models.ImageField()
mailer = models.EmailField(blank=True)
external = models.BooleanField(default=False)
internal = models.BooleanField(default=False)
class Meta:
verbose_name_plural = "technologies"
ordering = ['name']
def __str__(self):
return self.name
Then I have the corresponding serializer class (serializer.py):
class TechnologySerializer(serializers.ModelSerializer):
"""
This model defines the different technologies
"""
class Meta:
model = Technology
fields = ('id', 'name', 'path', 'image', 'mailer', 'external', 'internal')
Finally I have my view with generated APIs (views.py):
class TechnologyViewSet(viewsets.ModelViewSet):
queryset = Technology.objects.all()
serializer_class = TechnologySerializer
http_method_names = ['get','post','delete','put']
Here's the result :
Api description 1
Api description 2
As you see on the picture above, the parameters are une the json body.
Is it possible to have something like this for all the parameters :
API parameter wanted
Thanks a lot.
Try using ListCreateAPIView instead of modelviewset you will be able to see your post https://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview.
from rest_framework.generics import ListCreateAPIView
class TechnologyViewSet(ListCreateAPIView):
queryset = Technology.objects.all()
serializer_class = TechnologySerializer
Hope it help.

Django Rest Framework - Django Nested serializer One to Many Relations

I have three models : User(django auth model), Consumer, Tasks.
User model and Consumer has one to one relationship and Consumer and Task has one to many relationship.
My model.py goes like this:
class Consumer(models.Model):
user=models.OneToOneField(User,on_delete=models.CASCADE)
average_rating=models.FloatField(default=5.0)
class Tasks(models.Model):
consumer=models.ForeignKey(Consumer,on_delete=models.CASCADE,related_name='consumer_obj')
title=models.CharField(max_length=50)
description=models.CharField(max_length=200)
added_on=models.DateTimeField(auto_now=True)
My serializers.py goes like this:
class TaskSerializer(serializers.ModelSerializer):
category=serializers.CharField(max_length=10)
class Meta:
model=Tasks
fields=['title','description']
class UserSerializer(serializers.ModelSerializer):
password=serializers.CharField(max_length=10,write_only=True)
class Meta:
model=User
fields=['username','password','email']
class ConsumerSerializer(serializers.ModelSerializer):
user=UserSerializer()
tasks=TaskSerializer(many=True,source='consumer_obj')
class Meta:
model=Consumer
fields=['user','average_rating','tasks']
Now what I want to do is, Whenever I save a post a task, it should get save the task and associate itself to the current Consumer.
What can be done.
Now, whenever I call serializer.save() in my views I can pass a user instance as an argument something like this
serializer.save(user=request.user)
And in my serializer create() function I can query the user and than query the consumer with the help of user to associate the task with that user.
I want to know if there is any better way to do this ? I'm new to DRF and having hard time learning it.
you can Use the same approach that DRF uses for User, write a Custom object like CurrentUserDefault that returns the consumer correspondent to the currently authenticated user:
from rest_framework.compat import unicode_to_repr
class CurrentConsumerDefault(object):
def set_context(self, serializer_field):
user = serializer_field.context['request'].user
self.consumer = user.consumer
def __call__(self):
return self.consumer
def __repr__(self):
return unicode_to_repr('%s()' % self.__class__.__name__)
note: you should use the generic view or provide the request for the serializer yourself.
then in your TaskSerializer, add this field:
class TaskSerializer(serializers.ModelSerializer):
consumer=serializers.HiddenField(default=CurrentConsumerDefault())
class Meta:
model=Tasks
fields=['title','description', 'consumer']
read my answer to a similar question for more information.

How to declare child resources in Tastypie?

I have this models.py:
from django.db import models
class Item(models.Model):
text = models.TextField()
class Note(models.Model):
text = models.TextField()
items = models.ManyToManyField(Item)
And this api.py:
import tastypie
from tastypie.resources import ModelResource
from tastypie.api import Api
from main.models import Item, Note
class ItemResource(ModelResource):
class Meta:
resource_name = 'items'
queryset = Item.objects.all()
class NoteResource(ModelResource):
items = tastypie.fields.ToManyField(ItemResource, 'items', full=True)
class Meta:
resource_name = 'notes'
queryset = Note.objects.all()
api = Api(api_name='v1')
api.register(NoteResource())
I want the only endpoint to items be:
/api/v1/notes/4/items
/api/v1/notes/4/items/2
And no /api/v1/items/?note=4
I've been reading Tastypie documentation and i didn't found any info on this.
This document recommends the URL form i post here.
How can i accomplish this?
Using Django REST Framework (posterity, see comments on OP), child resources are declared as follows (simple example):
class AddressSerializer(ModelSerializer):
"""
A serializer for ``Address``.
"""
class Meta(object):
model = Address
class OrderSerializer(ModelSerializer):
"""
A serializer for ``Order``.
"""
address = AddressSerializer()
class Meta(object):
model = Order
To get started, I highly recommend simply following this tutorial. It will get you 100% of what you need, in terms of customizing your URLs, customizing your serialized output, etc.
Tasty pie is a great project, and the creator, Daniel Lindsley, is a really smart guy (I worked with him for a short while), but just like every other great project, somebody came along and blew our socks off with something new that has learned from the good and bad parts of the existing framework.

Categories