I have a model Ingredient which has ForeignKey field. I use modelSerializer to pass data. I'm passing recipe_id in url like this recipes/{recipe.id}/ingredients. My current solution is to modify request.data in the view but I'm sure if it's corrent way for such a common case.
What's the best way to pass Recipe to serializer?
models.py
class Ingredient(TimeStamp):
name = models.CharField(max_length=50)
quantity = models.PositiveSmallIntegerField()
recipe = models.ForeignKey(
Recipe,
on_delete=models.CASCADE,
related_name='ingredients',
)
serializers.py
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = [
'id',
'name',
'quantity',
'unit',
'recipe'
]
views.py
class IngredientViewSet(viewsets.ModelViewSet):
serializer_class = IngredientSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
request.data['recipe'] = self.kwargs['pk']
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
don't forget to pass the serializer the context dict
you can create your own or get the default one:
context = self.get_serializer_context()
serializer = self.get_serializer(data=request.data, context=context)
if you'll at the source code for get_serializer_context:
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
it adds the view class and the request to the serializer context.
in the serializer, I override the create method and pass the creation data dict the related object id.
class S(ModelSerializer):
def create(self, validated_data):
validated_data['recipe_id'] = self.kwargs['pk']
return super(S, self).create(validated_data)
and no need to override the create of the view
Related
I'm trying to update boolean fields using PATCH request and partial-update in DRF. However, only the name field gets updated and not the others
Model:
class UserModel(models.CustomModel):
name = models.CharField(max_length=64)
enabled = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
Serializer
class UserSerializer(serializers.CustomModelSerializer):
class Meta:
model = UserModel
fields = (
'id', 'name', 'enabled', 'admin'
)
read_only_fields = ('id',)
View and function
class UserView(viewsets.CustomModelViewSet):
def get_queryset(self):
return User.users.for_company(company=self.request.company.pk)
def get_serializer_class(self):
return UserSerializer
def update(self, request, id):
try:
instance = self.get_queryset().get(id=id)
except User.DoesNotExist:
return Response('User not in DB', status=status.HTTP_404_NOT_FOUND)
if 'name' in request.data and self.get_queryset().filter(name=request.data['name']).exists():
return Response('Duplicated user name', status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Url
urlpatterns = [ url(r'^(?P<id>${uuid})/$', UserView.as_view(
{'patch': 'update'}), name='user-update') ]
Currently if I send a PATCH request with a user_id and the following data in the body, ONLY the name field gets updated in the DB.
name = user2
enabled = True
admin = True
However if I change the update method as follows, the DB gets updated correctly
def update(self, request, id):
try:
instance = self.get_queryset().get(id=id)
except User.DoesNotExist:
return Response('User not in DB', status=status.HTTP_404_NOT_FOUND)
if 'name' in request.data and self.get_queryset().filter(name=request.data['name']).exists():
return Response('Duplicated user name', status=status.HTTP_400_BAD_REQUEST)
serializer = self.get_serializer(instance, data=request.data, partial=True)
if 'enabled' or 'admin' in request.data:
instance.enabled = request.data.get('enabled', instance.enabled)
instance.admin = request.data.get('admin', instance.admin)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Is this the correct way to update the fields by modifying the instance? Shouldn't the Django partial-update method take care of this if I'm passing the other boolean fields in the request and update them correctly in the database?
I investigated further and understand that the serializer.save() calls an instance.save() under the hood, and validates the data, only the name fields gets through as validated data and not the other two boolean fields.
I am making a test that partial updates or patch some fields in my Product model. I should be enable to update any fields in my Product model. In my serializer, I have a url builder from the request object. However, request is not passed on my serializer or it is a None type. Thus, I am getting an error: AttributeError: 'NoneType' object has no attribute 'build_absolute_uri'
How do I pass a request to my serializer?
def test_patch_products(self):
data = {
'description': 'This is an updated description.',
'type': 'New'
}
...
...
response = self.client.patch('/data/products/1/',
data=data, format='json')
In my view:
def partial_update(self, request, pk=None):
product= get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product,
data=self.request.data, partial=True,
)
if serializer.is_valid():
serializer.save(uploaded_by=self.request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
In my serializer:
class ProductSerializer(serializers.ModelSerializer):
uploaded_by= serializers.StringRelatedField()
thumbnail = serializers.SerializerMethodField()
class Meta:
model = CaptureGroup
fields = ('__all__')
def get_thumbnail(self, object):
request = self.context.get('request') # request is None here
thumbnail = object.thumbnail.url
return request.build_absolute_uri(thumbnail) # here is the url builder
In your view, you can pass some additional context to your serializer. That's useful to manipulate your request.
# views.py
def partial_update(self, request, pk=None):
product= get_object_or_404(Product, pk=pk)
serializer = ProductSerializer(product,
data=request.data, partial=True,
context={'request': request}, # you're missing this line
)
# ...
And then you can manipulate it your serializer with self.context.get('request') but I see you already got that part in your code.
In case you would like more information, here is the link to DRF documentation about this.
I am trying to partially update my ProfileSerializer when i am making PATCH request. However i am not able to make it beacuse by default Serializer doesn't allow partial changes to be made. I am using Django Rest Framwork UpdateModelMixin to handle my patch request. Where can i set partial=True in my case?
View:
class ProfileViewPartialUpdate(GenericAPIView, UpdateModelMixin):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
lookup_field = 'token'
lookup_url_kwarg = 'pk'
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
Serializer:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('token', 'bio', 'name', 'email', 'sport', 'location', 'image')
View:
from rest_framework import generics
class ProfileViewPartialUpdate(generics.RetrieveUpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
lookup_field = 'pk'
Serializer:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('token', 'bio', 'name', 'email', 'sport', 'location', 'image')
Look at this snippet code from one of my projects. You can modify accordingly. Add this to ProfileViewPartialUpdate class instead of patch.
def partial_update(self, request, slug):
user = self.request.user
get_blog = Blog.objects.get(slug=slug)
instance = self.get_object()
serializer = BlogSerializer(instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data, status=HTTP_201_CREATED)
return Response(data="wrong parameters", status=HTTP_400_BAD_REQUEST)
Hope this might help
I'm using Django version 3 and the Rest Framework, i have a model with a user foreignkey.
so every time a model is saved the user also need to be saved.
To be able to add or to edit a model via rest you need to be authenticated using token authentication.
I'm using ClassBasedViews, the problem is that i can't find a way to add a model because in my serializer the field user is excluded because i don't want it to be editable.
models.py:
class Chambre(models.Model):
local_id=models.PositiveIntegerField(unique=True)
nom=models.CharField(max_length=255)
user=models.ForeignKey(User,on_delete=models.CASCADE,blank='true')
class Meta:
unique_together = ('local_id', 'user',)
serializers.py:
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
views.py:
class ChambreListApi(APIView):
"""
List all chambres, or create a new chambre.
"""
authentication_classes=(TokenAuthentication,)
permission_classes=(IsAuthenticated,)
def get(self, request, format=None):
chambres = Chambre.objects.filter(user=request.user)
serializer = ChambreSerializer(chambres, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = ChambreSerializer(data=request.data)
if serializer.is_valid():
serializer.save(commit=False)
serializer.user=request.user
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Try moving the logic that sets the user to the serializer. By default GenericAPIView will pass the request to the serializer via the context.
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
def create(self, validated_data):
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
When I followed the accepted solution posted by schillingt, I had to add context={'request': request} when I instantiated the serializer. The Django REST Framework docs mention adding context to a serializer here.
serializers.py
class NoteCreateSerializer(serializer.ModelSerializer):
class Meta:
model = Note
exclude = ['owner',]
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
views.py
#api_view['POST']
def create_note(request):
note_serializer = NoteCreateSerializer(
data = request.data,
context = {'request': request} # this will populate 'self.context'
# in the serializer instance
)
# ...
Thanks so much for this solution, though! It ended an hours-long struggle.
thanks for the reply that was very useful, i've solved it by adding method to serializer.py like this :
class ChambreSerializer(serializers.ModelSerializer):
class Meta:
model = Chambre
exclude =['user',]
def set_the_user(self,request):
self.user=request.user
def create(self, validated_data):
validated_data["user"] = self.user
return super().create(validated_data)
and in the views.py :
def post(self, request, format=None):
serializer = ChambreSerializer(data=request.data)
serializer.set_the_user(request)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have a Location model and I need to create a Location without specifying a user, if user in empty then the user will be placed in request.user in the viewset.
This is my model:
class Location(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=100)
serializer:
class LocationSerializer(serializers.ModelSerializer):
def is_valid(self):
if not 'user' in self.init_data:
# avoid this validation.. I manage this in the viewset
pass
return not self.errors
class Meta:
model = Location
and viewset
class LocationViewSet(ModelViewSet):
"""
API endpoint that allows location to be created or viewed.
"""
model = Location
serializer_class = LocationSerializer
renderer_classes = (JSONRenderer, JSONPRenderer)
def get_queryset(self):
return self.request.user.locations.filter(deleted=False)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object)
if not self.object.user:
self.object.user = request.user
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Thanks for any suggestion
You can use required=False in serializer:
class LocationSerializer(serializers.ModelSerializer):
user = serializers.RelatedField(required=False)
EDIT
Also you can simplify your viewset with:
class LocationViewSet(ModelViewSet):
def pre_save(self, obj):
if obj.user_id is None:
obj.user = self.request.user
This avoid copying code from DRF core.