This is my serializer:
class MetaDataSerializer(serializers.Serializer):
bg_colors = ColorSerializer(Color.objects.all(), many=True)
button_choices = serializers.SerializerMethodField()
class Meta:
fields = ('bg_colors', 'button_choices')
def get_button_choices(self, obj):
return {
'save': 1, 'continue': 2, 'cancel': 3, 'back': 4
}
I'm calling this serializer from my view like this:
class MetaDataView(RetrieveAPIView):
serializer_class = MetaDataSerializer
def get(self, request, *args, **kwargs):
return Response(self.get_serializer().data)
In the response I'm getting only the bg_colors field. The other field is absent from response, and its get_field method is also not called.
What am I doing wrong here?
Instead of passing the Colors queryset directing in serializer definition, pass it from your viewset.
Make this changes:
In serializer :-
class MetaDataSerializer(serializers.Serializer):
bg_colors = ColorSerializer(many=True)
button_choices = serializers.SerializerMethodField()
class Meta:
fields = ('bg_colors', 'button_choices')
def get_button_choices(self, obj):
return {
'save': 1, 'continue': 2, 'cancel': 3, 'back': 4
}
In view :-
class MetaDataView(RetrieveAPIView):
serializer_class = MetaDataSerializer
def get(self, request, *args, **kwargs):
final_data = {"bg_colors": Colors.objects.all()}
return Response(self.get_serializer(final_data).data)
You're not validating your data, you can only access the serializer.initial_data right now, which does not include the custom serializer fields that you've added. You must make a serializer instance first and then check if serializer.is_valid() and then you'll be able to access the custom serializer fields. Remember you can access the serializer data before validating by using serializer.initial_data (here serializer is the serializer instance name), after validation and before saving or inside if serializer.is_valid(): as serializer.validated_data and after saving the validated data as serializer.data
serializer = MetaDataSerializer(request.data)
print(serializer.initial_data)
if serializer.is_valid(raise_exception=True):
print(serializer.validated_data)
serializer.save()
print(serializer.data)
return Response({'data': serializer.data}, status=status.HTTP_200_OK)
now if it got validated without raising any errors then serializer.data will have data saame as the validated_data and if it does not get validate then it will have data same as serializer.initial_data
Related
I have created an API and am currently testing my post request with Postman. However, I keep getting this error.
views.py
def post(self, request, *args, **kwargs):
serializer = TagSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(name=request.POST.get("name"),
language=request.POST.get("language"))
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('id', 'name', 'language')
def create(self, validated_data):
return Tag.objects.create(**validated_data)
def to_representation(self, data):
data = super().to_representation(data)
return data
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
Tag.objects.create(**tag_data)
return tag_data
def __str__(self):
return self.name or ''
The name field in your tag model is not nullable. So it looks like you are trying to create a Tag model without a name value.
My guess is your request.POST is probably empty. Did you check it ? The recommanded way is to use request.data instead.
https://www.django-rest-framework.org/api-guide/requests/
By the way, if you want to use serializer, you can do all the process calling its functions.
So, you are supposed to check if the serializer is valid by calling
serializer.is_valid(raise_exception=True) and then create the object with save(). And if you want something more custom, you can override the .save() method of your serializer
def post(self, request, *args, **kwargs):
serializer = TagSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save() # the result contains the tag instance
It may be also more efficient if you use CreateAPIView directly instead
https://www.django-rest-framework.org/api-guide/generic-views/
ps: It can't see where you are going with serializer.save(tag_input).
I have a list of data coming in a request, and after filtering taken out data that needs creation, I'm passing it to the serializer create method, but I'm getting this error:
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `apps.some_app.serializers.SomeSerializer`, or set `read_only=True` on nested serializer fields.
My view looks like this:
class MyViewSet(ModelViewSet):
# Other functions in ModelViewset
#action(methods=['post'], url_path='publish', url_name='publish', detail=False)
def publish_data(self, request, *args, **kwargs):
new_data = util_to_filter_data(request.data)
serializer = self.serializer_class(data=new_data, many=True)
if serializer.is_valid(raise_exception=True):
serializer.create(serializer.validated_data)
return Response()
I understood the error, that I am passing nested fileds in the create method. But, when I am directly calling my Viewset with single POST request, it is created successfully, even though it too contains nested fields.
What am I doing wrong here?
Here's my serializer:
class SomeSerializer(ModelSerializer):
# fields
def create(self, validated_data):
print("in create")
return super().create(validated_data)
def save(self, **kwargs):
print("in save")
my_nested_field = self.validated_data.pop('my_nested_field', '')
# Do some operations on this field, and other nested fields
obj = None
with transaction.atomic():
obj = super().save(kwargs)
# Save nested fields
return obj
Here in create is being seen in terminal, but not in save.
you should not use serializer create method directly; instead use save. Also no need to check serializer.is_valid with if when raise_exception=True, if it is not valid it will return the exception
Try following.
class MyViewSet(ModelViewSet):
# Other functions in ModelViewset
#action(methods=['post'], url_path='publish', url_name='publish', detail=False)
def publish_data(self, request, *args, **kwargs):
new_data = util_to_filter_data(request.data)
serializer = self.serializer_class(data=new_data, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
I want to do the following:
models.py
class MyModel(TimeStampedModel, models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
serializers.py
class MyModelSerializerCreate(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (
'name',
)
And I would like to add as owner the current user in request.user.
Currently I am adding this in my view directly by uptading request.data with user and then pass the updated data to my serializer.
data = request.data
# Add owner to data
data["owner"] = request.user.pk
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
I would like to do this in my serializer directly but can't find a way to properly do it because it looks like data validation to me. Is this a good idea ? Should I keep this logic in my views or move it to my serializer ?
You can get a user from serializer context:
self.context['request'].user
It is passed from a method get_serializer_context which originally created in a GenericAPIView:
class GenericAPIView(APIView):
....
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
Inside a serializer you need to override a create method:
class MyModelSerializerCreate(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('name', )
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super(MyModelSerializerCreate, self).create(validated_data)
You could also override an update and delete methods if you need some special interactions with the User model.
Unfortunatly I dont have the reputation points to comment on #ivan-Semochkin post above, but should the last line not be:
return super(MyModelSerializerCreate, self).create(validated_data)
The solution from Ivan Semochkin did not work for me, as it never entered into the create() method of the serializer. As request.data field is immutable, you need to copy it and then extend it.
from django.http import HttpRequest
from rest_framework.request import Request
class MyModelViewSet(ModelViewSet):
def _extend_request(self, request):
data = request.POST.copy()
data['owner'] = request.user
request_extended = Request(HttpRequest())
request_extended._full_data = data
def create(self, request, *args, **kwargs):
request_extended = self._extend_request(request)
return super().create(request_extended, *args, **kwargs)
class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.CharField()
field2 = serializers.SerializerMethodField('get_awesome_user')
def get_current_user(self):
request = self.context.get("request")
if request and hasattr(request, "user"):
return request.user
return None
def get_awesome_user(self, obj):
user = self.get_current_user()
## use this user object, do some stuff and return the value
return ...
My api(which uses authentication_classes and permission_classes) is using this serializer and the get_current_user function always returns None. when I debug it, I found that self.context is empty dictionary, i.e {}. to be double sure I also printed self.context.keys(), still it's empty list.
I followed this thread.
Get current user in Model Serializer
PS: I'm using djangorestframework==3.3.3, Django==1.9.1
EDIT: adding viewset code
class MyModelViewSet(viewsets.ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication, TokenAuthentication)
permission_classes = (IsAuthenticated,)
def list(self, *args, **kwargs):
queryset = MyModel.objects.all()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = MyModelSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = MyModelSerializer(queryset, many=True)
return Response(serializer.data)
How do you create serializer in your viewset's list() method? You should call
serializer = self.get_serializer(data=request.data)
to get your serializer context filled automatically as it is done in default implementation of this method in DRF mixins., but I have a feeling that you're just creating it manually, like this:
serializer = MyModelSerializer(instance)
So, to fix this, you should either call get_serializer(), or pass extra context argument to serializer constructor:
serializer = MyModelSerializer(instance, context={'request': request, ...})
I am not very experience with Django REST framework and have been trying out many things but can not make my PATCH request work.
I have a Model serializer. This is the same one I use to add a new entry and ideally I Would want to re-use when I update an entry.
class TimeSerializer(serializers.ModelSerializer):
class Meta:
model = TimeEntry
fields = ('id', 'project', 'amount', 'description', 'date')
def __init__(self, user, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
super(TimeSerializer, self).__init__(*args, **kwargs)
self.user = user
def validate_project(self, attrs, source):
"""
Check that the project is correct
"""
.....
def validate_amount(self, attrs, source):
"""
Check the amount in valid
"""
.....
I tried to use a class based view :
class UserViewSet(generics.UpdateAPIView):
"""
API endpoint that allows timeentries to be edited.
"""
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
My urls are:
url(r'^api/edit/(?P<pk>\d+)/$', UserViewSet.as_view(), name='timeentry_api_edit'),
My JS call is:
var putData = { 'id': '51', 'description': "new desc" }
$.ajax({
url: '/en/hours/api/edit/' + id + '/',
type: "PATCH",
data: putData,
success: function(data, textStatus, jqXHR) {
// ....
}
}
In this case I would have wanted my description to be updated, but I get errors that the fields are required(for 'project'and all the rest). The validation fails. If add to the AJAX call all the fields it still fails when it haves to retrieve the 'project'.
I tried also to make my own view:
#api_view(['PATCH'])
#permission_classes([permissions.IsAuthenticated])
def edit_time(request):
if request.method == 'PATCH':
serializer = TimeSerializer(request.user, data=request.DATA, partial=True)
if serializer.is_valid():
time_entry = serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
This did not work for partial update for the same reason(the validation for the fields were failing) and it did not work even if I've sent all the fields. It creates a new entry instead of editing the existing one.
I would like to re-use the same serializer and validations, but I am open to any other suggestions.
Also, if someone has a piece of working code (ajax code-> api view-> serializer) would be great.
class DetailView(APIView):
def get_object(self, pk):
return TestModel.objects.get(pk=pk)
def patch(self, request, pk):
testmodel_object = self.get_object(pk)
serializer = TestModelSerializer(testmodel_object, data=request.data, partial=True) # set partial=True to update a data partially
if serializer.is_valid():
serializer.save()
return JsonResponse(code=201, data=serializer.data)
return JsonResponse(code=400, data="wrong parameters")
Documentation
You do not need to write the partial_update or overwrite the update method. Just use the patch method.
Make sure that you have "PATCH" in http_method_names. Alternatively you can write it like this:
#property
def allowed_methods(self):
"""
Return the list of allowed HTTP methods, uppercased.
"""
self.http_method_names.append("patch")
return [method.upper() for method in self.http_method_names
if hasattr(self, method)]
As stated in documentation:
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the partial argument in order to allow partial updates.
Override update method in your view:
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = TimeSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save(customer_id=customer, **serializer.validated_data)
return Response(serializer.validated_data)
Or just override method partial_update in your view:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Serializer calls update method of ModelSerializer(see sources):
def update(self, instance, validated_data):
raise_errors_on_nested_writes('update', self, validated_data)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Update pushes the validated_data values to the given instance. Note that update should not assume all the fields are available. This helps to deal with partial updates (PATCH requests).
The patch method is worked for me using viewset in DRF. I'm changing you code:
class UserViewSet(viewsets.ModelViewSet):
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
def perform_update(self, serializer):
user_instance = serializer.instance
request = self.request
serializer.save(**modified_attrs)
return Response(status=status.HTTP_200_OK)
Use ModelViewSet instead and override perform_update method from UpdateModelMixin
class UserViewSet(viewsets.ModelViewSet):
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
def perform_update(self, serializer):
serializer.save()
# you may also do additional things here
# e.g.: signal other components about this update
That's it. Don't return anything in this method. UpdateModelMixin has implemented update method to return updated data as response for you and also clears out _prefetched_objects_cache. See the source code here.
I ran into this issues as well, I solved it redefining the get_serializer_method and adding custom logic to handle the partial update. Python 3
class ViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == "partial_update":
return PartialUpdateSerializer
Note: you may have to override the partial_update function on the serializer. Like so:
class PartialUpdateSerializer(serializers.Serializer):
def partial_update(self, instance, validated_data):
*custom logic*
return super().update(instance, validated_data)
Another posiblity is to make the request by URL. For example, I have a model like this
class Author(models.Model):
FirstName = models.CharField(max_length=70)
MiddleName = models.CharField(max_length=70)
LastName = models.CharField(max_length=70)
Gender = models.CharField(max_length=1, choices = GENDERS)
user = models.ForeignKey(User, default = 1, on_delete = models.CASCADE, related_name='author_user')
IsActive = models.BooleanField(default=True)
class Meta:
ordering = ['LastName']
And a view like this
class Author(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
So can enter http://127.0.0.1:8000/author/ to get or post authors. If I want to make a PATCH request you can point to http://127.0.0.1:8000/author/ID_AUTHOR from your client. For example in angular2, you can have something like this
patchRequest(item: any): Observable<Author> {
return this.http.patch('http://127.0.0.1:8000/author/1', item);
}
It suppose you have configured your CORS and you have the same model in back and front.
Hope it can be usefull.