Lets say i have a model like so:
class MyModel(models.Model):
first_field = models.CharField()
second_field = models.CharField()
and an API view like so:
class MyModelDetailAPI(GenericAPIView):
serializer_class = MyModelSerializer
def patch(self, request, *args, **kwargs):
# Do the update
def post(self, request, *args, **kwargs):
# Do the post
The first_field is a field that is only inserted in the POST method (and is mandatory) but on each update, the user can't change its value so the field in the PATCH method is not mandatory.
How can i write my serializer so that the first_field is required on POST but not required on PATCH. Is there any way of dynamically setting the required field so i can still use the DRF validation mechanism? Some sort of validator dispatcher per request method?
I want something like this for example:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = {
'POST': ['first_field']
'PATCH': []
}
I need more space than comments provide to make my meaning clear. So here is what I suggest:
Different formatting means different serializers.
So here you have, for instance a MyModelSerializer and a MyModelCreationSerializer. Either create them independently, or have one inherit the other and specialize it (if it makes sense).
Use the appropriate GenericAPIView hook to return the correct serializer class depending on self.action. A very basic example could be:
class MyModelDetailAPI(GenericAPIView):
# serializer_class = unneeded as we override the hook below
def get_serializer_class(self):
if self.action == 'create':
return MyModelCreationSerializer
return MyModelSerializer
Default actions in regular viewsets are documented here, they are:
create: POST method on base route url
list: GET method on base route url
retrieve: GET method on object url
update: PUT method on object url
partial_update: PATCH method on object url
destroy: DELETE method on object url
Related
With some help, I solved this issue.
My api is work, but today I found this error when I try to access '/api/v1/docs'
AttributeError at /api/v1/docs/
'NoneType' object has no attribute 'method'
I know that the error is here:
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', 'PATCH', 'PUT']:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
When I remove .method, the access to the /api/v1/docs/ works, but my solution to post some products in bundleproducts, doesn't work.
My code:
view.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
This is probably caused by this serializer being used as a nested serializer in another serializer. So lets say the definition for the serializer in question is:
class MySerializer(serializers.Serializer):
...
And you have another serializer like this:
class OtherSerializer(serializers.Serializer):
my_field = MySerializer()
In this case, when instantiating an instance of OtherSerializer, its context is not passed automatically to MySerializer, so there would not be a request in the context of MySerializer. You can either add the context to nested serializer manually, or in the get_fields method, check that request exists in self.context and proceed accordingly.
Also, I am not sure what you are trying to accomplish, but if you provide a field with
write_only=True
in serializer class definition, the field would not be present when reading the serializer, i.e for get requests in general, which seems like what you are trying to do here. So adding the products field as write_only would have the same effect, you do not need to override get_fields method
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 have the following code for my serializers.py:
from rest_framework import serializers
from django.db import transaction
from secdata_finder.models import File
class FileListSerializer(serializers.ListSerializer):
#transaction.atomic
def batch_save_files(file_data):
files = [File(**data) for data in file_data]
return File.objects.bulk_create(files)
def create(self, validated_data):
print("I am creating multiple rows!")
return self.batch_save_files(validated_data)
class FileSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FileListSerializer
model = File
fields = (...) # omitted
I'm experimenting with it on my Django test suite:
def test_file_post(self):
request = self.factory.post('/path/file_query', {"many":False})
request.data = {
... # omitted fields here
}
response = FileQuery.as_view()(request)
It prints I am creating multiple rows!, which is not what should happen.
Per the docs:
... customize the create or update behavior of multiple objects.
For these cases you can modify the class that is used when many=True is passed, by using the list_serializer_class option on the serializer Meta class.
So what am I not understanding? I passed in many:False in my post request, and yet it still delegates the create function to the FileListSerializer!
Per the docs:
The ListSerializer class provides the behavior for serializing and
validating multiple objects at once. You won't typically need to use
ListSerializer directly, but should instead simply pass many=True when
instantiating a serializer
You can add many=True to your serializer
class FileSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
kwargs['many'] = kwargs.get('many', True)
super().__init__(*args, **kwargs)
Potential dupe of How do I create multiple model instances with Django Rest Framework?
I want to access the request object in my Views.py and Serializers.py in DRF.
My Views.py:
class ProductViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = Product.objects.all()
serializer_class = ProductSerializer(context={'request': request})
My Serializers.py:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
get_sr_price = serializers.SerializerMethodField('get_sr_price_func')
def get_sr_price_func(self, obj):
return self.request.user ??
class Meta:
model = Product
fields = (
'title', 'slug', 'product_stores', 'get_sr_price')
In Serializers.py I get ProductSerializer' object has no attribute 'request'. Also In views.py I get NameError: name 'request' is not defined
How do I access request object? Do I have to pass it from views to serializers? Also what's the difference between views.py and serializers.py? Generally I write all the business logic in Views.py ; here also should I do all the queries/filters in the views or should I do them in serializers or it doesn't make a difference. New to DRF please help.
You don't need to include request object in the context as the generic views passes request object to the serializer context.
DRF Source code snippet:
# rest_framework/generics.py
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request, # request object is passed here
'format': self.format_kwarg,
'view': self
}
In your serializer, you can access the request object using .context attribute.
The context dictionary can be used within any serializer field logic,
such as a custom .to_representation() method, by accessing the
self.context attribute.
class ProductSerializer(serializers.HyperlinkedModelSerializer):
get_sr_price = serializers.SerializerMethodField('get_sr_price_func')
def get_sr_price_func(self, obj):
return self.context['request'].user # access the request object
Serializers are the way external data is mapped from / to models (Django or simple Python classes).
Views are dealing with how the data will be shown. Throttling, pagination, authentication are managed by the view. They also handle the data set.
DRF provides a context to pass request specific data to the serializer without having to redefine the init. This is likely what you're looking for.
I'd like to fill one field of my model automatically. It holds a client IP.
I've defined an CreateView as follows:
class MyView(CreateView):
def post(self, request, *args, **kwargs):
self.form_class.client_ip = request.META.get('REMOTE_ADDR')
super(MyView, self).post(request, *args, **kwargs)
model = MyModel
form_class = MyForm
and MyForm in that way:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ('ip',)
And I have no idea how to fill this exluded field.
In MyView you should add a method called get_initial which returns the initial values of the form (as a dictionary). For example:
def get_initial(self):
return { 'ip': ... }
These initial values are then used when the form is created.
excluded fields have be to manually filled. you can look into the middleware processors that can add to the request.POST dict and you can override the __init__ on the MyForm, [first call super] and then set the model's ip field.
the middleware processor can also add to POST some other request level attributes, making it available for future use.