I have two models: ModelA and ModelB, with their corresponding serializers ModelASerializer and ModelBSerializer
In a specific viewset, called MyViewSet i have the follwing structure:
class MyViewSetRoot(viewsets.ModelViewSet):
http_method_names = ["get"]
# The returned values are of type "ModelA", so I need it to use that serializer
serializer_class = ModelASerializer
queryset = ""
Finally, in my actual view, I do something like this:
class MyViewSet(MyViewSetRoot):
get(self, request: HttpRequest, *args, **kwargs) -> Response:
ModelA_queryset = ModelA.objects.all()
return Response(
data=ModelA_queryset,
status=status.HTTP_200_OK,
)
I would expect in that case for the queryset to be serialized using the ModelASerializer that I specified in the serializer_class field. However, I get the error
Object of type ModelA is not JSON serializable
If I do this instead:
class MyViewSet(MyViewSetRoot):
get(self, request: HttpRequest, *args, **kwargs) -> Response:
ModelA_queryset = ModelA.objects.all()
serialized_queryset = ModelASerializer(ModelA_queryset, many=True)
return Response(
data=serialized_queryset.data,
status=status.HTTP_200_OK,
)
It works just fine, but I want to avoid serializing explicitly in the view.
Any ideas on what could be actually going on? Am I forced to serialize explicitly in this case?
I think you don't need to customize the get function. In ModelViewSet, the function for the GET API, is list or retrieve. But you don't need to redefine it.
class MyViewSetRoot(viewsets.ModelViewSet):
http_method_names = ["get"]
serializer_class = ModelASerializer
queryset = ModelA.objects.all()
class MyViewSet(MyViewSetRoot):
pass
Related
In DRF the serializer validate() method is not calling by default.
i am using the serializer like this:
class SampleListView(ListAPIView):
queryset = Sample.objects.all()
serializer_class = SampleSerializer
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response(sorted_result)
class SampleSerializer(serializers.ModelSerializer):
custom_data = serializers.SerializerMethodField()
class Meta:
model = SampleModel
fields = ('field_1', 'field_2')
def validate(self, data):
return data
Execution not enter into the validate() method in serializer.
Anyone have an idea about this?
Shijo Validate() only be called when you are using save method of serializer hence only be used for create and update method of API.
As refer in Validation in DRF documentation
you always need to call is_valid() before attempting to access the validated data, or save an object instance.
This is my models.py
class Grade(models.Model):
grade = models.CharField(max_length=255, primary_key=True)
This is my views to perform get(post is not required, I can run if post methood is required as well).
class GetGrade(generics. GenericAPIView):
'''
GET check/
'''
queryset = Grade.objects.all()
serializer_class = DataSerializer
def get(self, request, *args, **kwargs):
a_grade = Grade.objects.all()
return Response(
data=DataSerializer(a_grade).data,
status=status.HTTP_200
)
My serializer is below:
class DataSerializer(serializers.ModelSerializer):
class Meta:
model = Grade
fields = ("grade",)
Everything seems straightforward. It might be something silly that I might be doing.
AttributeError at /check/
Got AttributeError when attempting to get a value for field `grade` on serializer `DataSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was:
'QuerySet' object has no attribute 'grade'.
Request Method: GET
Request URL: http://127.0.0.1:8000/check/
Django Version: 2.1.5
Exception Type: AttributeError
Exception Value: Got AttributeError when attempting to get a value for field `grade` on serializer `DataSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'grade'.
How about like this:
class GetGrade(generics.GenericAPIView):
def get(self, request, *args, **kwargs):
a_grade = Grade.objects.all()
return Response(
data=DataSerializer(a_grade, many=True).data, # passed many=True as known argument
status=status.HTTP_200
)
The error occured because you are passing a Queryset through DataSerializer. When you called data method of DataSerializer, it was trying to get value grade attribute from Queryset. That is why, you need to pass many=True, so that, serializer knows you are passing queryset or a list a objects. If you want the serializer to process a single object, then you can do it like this:
class GetGrade(generics.GenericAPIView):
def get(self, request, *args, **kwargs):
a_grade = Grade.objects.all().first() # it will return first object of queryset
return Response(
data=DataSerializer(a_grade).data,
status=status.HTTP_200
)
Finally, a cleaner approach is to use ListModelMixin. For example:
from rest_framework import mixins, generics
class GetGrade(mixins.ListModelMixin, generics.GenericAPIView):
queryset = Grade.objects.all()
serializer_class = DataSerializer
# thats it, no more code needed
In your Serailizers.py you need to use model method to get the objects from your model in API:
class GetGrade(generics. GenericAPIView):
'''
GET check/
'''
model = Grade # <---Add This in place queryset
serializer_class = DataSerializer
In my restful CreateAPIView I mutate my request.data dictionary.
Occasionally I receive an error not caught by my tests:
This QueryDict instance is immutable
For e.g. this:
class CreateView(CreateAPIView):
serializer_class = ...
queryset = ...
def post(self, request, *args, **kwargs):
request.data['user'] = request.user.pk
return self.create(request, *args, **kwargs)
request.data seems to be a normal dict in my tests. Why is it sometimes a QueryDict? How should this be dealt with? Should request.data not be mutated in general? How should you use the ModelSerializer class, when you need to populate some fields yourself?
Why this occasional behavior?
When we look into the SC of Request (as #Kenny Ackerman mentioned), it return a QueryDict object if you are passing a form media type ('application/x-www-form-urlencoded' or 'multipart/form-data') data to the view class. This check being execute within the is_form_media_type() method of Request class.
If you are passing a application/json data to the view, the request.data will be a dict object.
How to reproduce the behaviour?
It can be reproduce by using sending different ContentType data into view. (In POSTMAN tool, use form-data and raw JSON to get the behaviour)
How to get current logged-in user in serializer?
Method-1 pass extra argument to .save() (as #Linovia mentioned) by overriding the perform_create() method
class CreateView(CreateAPIView):
serializer_class = ...
queryset = ...
def post(self, request, *args, **kwargs):
request.data['user'] = request.user.pk
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Method-2 Use CurrentUserDefault() class as below
from django.contrib.auth import get_user_model
User = get_user_model()
class MySerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), default=serializers.CurrentUserDefault())
class Meta:
# your code
When you have to modify a QueryDict object received from a request, it is a immutable object, instead use this line of code if you wanna add attributes:
myNewRequest = request.GET.copy()
myNewRequest.data['some_attr'] = float(something)
based on the source code parser returns a querydict for data when the stream is empty(request.data calls _load_data_and_files method and _load_data_and_files calls _parse method).
and I think you can populate the fields using HiddenField or you can override the create or update method. for example
class TestSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Test
fields = ('id', 'text', 'user')
def create(self, validated_data):
validated_data['populate_field'] = 'value'
return super().create(validated_data)
Note that this really depends on your selected parser specified in rest_framework's DEFAULT_PARSER_CLASSES and the content type of your request:
JSONParser is implemented as follows:
return json.load(decoded_stream, parse_constant=parse_constant)
FormParser as follows:
return QueryDict(stream.read(), encoding=encoding)
I'm using Django Rest Framework and python-requests and passing several variables through the URL as shown below.
GET /api/boxobjects/?format=json&make=Prusa&model=i3&plastic=PLA HTTP/1.1
I'm passing the variables make, model, and plastic. The recommended method to access these parameters is shown below.
makedata = request.GET.get('make', '')
However, I have no idea where to place that line of code. I've completed the tutorial for Django Rest Framework and have my views set up to roughly match the tutorial.
views.py:
#api_view(['GET'])
#login_required
def api_root(request, format=None):
return Response({
'Users': reverse('api:user-list', request=request, format=format),
'Objects': reverse('api:object-list', request=request, format=format),
'Files': reverse('api:file-list', request=request, format=format),
'Config Files': reverse('api:config-list', request=request, format=format),
'Box-objects': reverse('api:box-object-list', request=request, format=format),
})
class BoxViewSet(viewsets.ModelViewSet):
queryset = Uploadobject.objects.all().exclude(verified=False)
serializer_class = BoxSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsBox)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
#Maybe get function here? Not displaying
'''
def get(self, request):
print ("request set here?")
'''
Where would I place the one line of code to access these request parameters?
class BoxViewSet(viewsets.ModelViewSet):
queryset = Uploadobject.objects.all().exclude(verified=False)
serializer_class = BoxSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsBox)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
req = self.request
print(req)
make = req.query_params.get('make')
if make:
self.queryset = uploadobject.objects.filter(make=make)
return self.queryset
else:
return self.queryset
What is the statement doing ?
If 'make' is in the query params of the request then overwrite the BoxViewSet queryset property with a new queryset based on 'make' and return it. otherwise return the default queryset that excludes any objects that isn't verified.
Based on Django Rest Framework's Filtering Documentation, there are two ways to access parameters from a request.
1. URL Params
If you are using URL params (such as mywebapp.com/api/<user_slug>/resource/), you can access the arguments like this: self.kwargs['param_name'] where param_name is the name of the parameter you're trying to get the value for. So for the example above, you'd have user_slug = self.kwargs['user_slug']
Example from the documentation
If your URL structure looks like this:
url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
...and want to filter on that username. You can override the get_queryset() and your view will look like this:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
username = self.kwargs['username']
return Purchase.objects.filter(purchaser__username=username)
2. Query Params
If you are using query parameters such as mywebapp.com/api/resource?user_slug=plain-jane, you can use self.request to access request as you can in plain vanilla Django REST methods. This gives you access to things like self.request.query_params. For the example above, you would say user_slug = self.request.query_params['user_slug']. You can also access the current user like user = self.request.user.
Example from the documentation
Let's say you want to support a request structure like this:
http://example.com/api/purchases?username=denvercoder9
...and want to filter on that username. Do this to override the queryset:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username=username)
return queryset
Django 2
Use request.parser_context
def perform_create(self, serializer):
post_id = self.request.parser_context['kwargs'].get('post_id')
post = Post.objects.get(id=post_id)
serializer.save(post=post)
adding a little to what they have already contributed, for the POST methods in modelviewset when an object is sent, it is necessary to use request.data['myvariable']
example:
class BoxViewSet(viewsets.ModelViewSet):
queryset = Uploadobject.objects.all().exclude(verified=False)
serializer_class = BoxSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsBox)
def create(self, request):
...
make = request.data['make']
I am using django-rest-framework generic views to create objects in a model via POST request. I would like to know how can I return the id of the object created after the POST or more general, any additional information about the created object.
This is the view class that creates (and lists) the object:
class DetectorAPIList(generics.ListCreateAPIView):
serializer_class = DetectorSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
parser_classes = (MultiPartParser, FileUploadParser,)
def pre_save(self, obj):
obj.created_by = self.request.user.get_profile()
def get_queryset(self):
return (Detector.objects
.filter(get_allowed_detectors(self.request.user))
.order_by('-created_at'))
The model serializer:
class DetectorSerializer(serializers.ModelSerializer):
class Meta:
model = Detector
fields = ('id', 'name', 'object_class',
'created_by', 'public', 'average_image', 'hash_value')
exclude = ('created_by',)
Thanks!
Here, DetectorSerializer inherits from ModelSerializer as well as your view inherits from generics ListCreateAPIView so when a POST request is made to the view, it should return the id as well as all the attributes defined in the fields of the Serializer.
Because it took me a few minutes to parse this answer when I had the same problem, I thought I'd summarize for posterity:
The generic view ListCreateApiView does return the created object.
This is also clear from the documentation listcreateapiview: the view extends createmodelmixin, which states:
If an object is created this returns a 201 Created response, with a serialized representation of the object as the body of the response.
So if you have this problem take a closer look at your client side!
post$.pipe(tap(res => console.log(res)))
should print the newly created object (assuming rxjs6 and ES6 syntax)
As mentioned above, To retrieve the id for the new created object, We need to override the post method, find the the update code for more details:
class DetectorAPIList(generics.ListCreateAPIView):
serializer_class = DetectorSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
parser_classes = (MultiPartParser, FileUploadParser,)
def post(self, request, format=None):
serializer = DetectorSerializer(data=request.data)
if serializer.is_valid():
obj = serializer.save()
return Response(obj.id, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)