Django: Update a specific field with a GET instead of a PATCH - python

Context
For a specific use case I need to be able to update a single field of my Visitor model using a GET request instead of a PATCH request.
My relevant Visitor model looks like this:
# models.py
class Visitor(models.Model):
visitor_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
customers = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='customer_visitors')
audiences = models.ManyToManyField(Audience, related_name='audience_visitors')
cid = models.CharField(max_length=255, unique=True)
uid = models.CharField(max_length=255)
cup = JSONField(null=True)
def __str__(self):
return self.cid
class Meta:
db_table = 'visitor'
I am using a straightforward serializer like this:
# serializers.py
class VisitorSerializer(serializers.ModelSerializer):
class Meta:
model = Visitor
fields = ('customers', 'cid', 'uid', 'cup')
I am able to update just the cup field for a specific Visitor which is looked up using the unique cid field with a PATCH like this:
# views.py
class VisitorViewSet(viewsets.ModelViewSet):
serializer_class = VisitorSerializer
queryset = Visitor.objects.all()
lookup_field = 'cid'
def list(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serializer_class(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
Problem
The problem is that I am unable to update the cup field of a Visitor based on a given unique cid field using a GET request.
What I tried
As this answer by Uri Shalit suggested, I tried to override get_serializer() inside my VisitorViewSet and tried to use it in list() like this:
# views.py
class VisitorViewSet(viewsets.ModelViewSet):
serializer_class = VisitorSerializer
queryset = Visitor.objects.all()
lookup_field = 'cid'
def get_serializer(self, *args, **kwargs):
kwargs['partial'] = True
return super(VisitorViewSet, self).get_serializer(*args, **kwargs)
def list(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
However, updating just the cup field of a specific Visitor based on the cid field works with a PATCH request but does not update said field with a GET request. There is no error either.
Expected behaviour
Making a GET request which contains cid to identify a Visitor and cup with data that needs to be updated for the given Visitor. I know it breaks REST principles but for this use case I need to do this partial update using a GET request instead of a PATCH request.
Any help or pointers in the right direction would be much appreciated!

Add a classmethod in your model.
class Visitor(models.Model):
visitor_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
customers = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='customer_visitors')
audiences = models.ManyToManyField(Audience, related_name='audience_visitors')
cid = models.CharField(max_length=255, unique=True)
uid = models.CharField(max_length=255)
cup = JSONField(null=True)
def __str__(self):
return self.cid
class Meta:
db_table = 'visitor'
#classmethod
def update_cup(cls, cid, cup_new):
instance = cls.objects.get(cid=cid)
instance.cup = new_cup
instance.save()
In ModelViewSet override the get_queryset method, see below:
IDK how u calc new_cup I guess u get it as a queryparam
def get_queryset(self):
queryset = Visitor.objects.all()
cup_new = self.request.query_params.get('cup_new', None)
cid = self.request.query_params.get('cid', None)
[obj.update_cup(obj.cid, cup_new) for obj in queryset if obj.cid == cid]
return queryset

I recommend using an api_view to accomplish what you want. api_view is an annotation provided by the rest framework so it should be available already in your case.
#api_view(["GET"])
def update_function(request):
query_params = request.GET # Getting the parameters from request
cid = query_params["cid"]
cup = query_params["cup"]
visitor = Visitor.objects.get(cid = cid)
visitor["cup"] = cup
serializer = VisitorSerializer(data = visitor, partial=True)
if serializer.is_valid():
serializer.save()
else:
print(serializer.errors)
However I am not sure about the syntax but the approch is sufficient for your problem.
Make sure to add the function to urls.py and have a look to the documentation to get better information than mine Api Views. But dont expect it to have information about you specific problem. In your case you have to understand the api_view concept and adapt it for your needs.

I think I don't understand the problem fully. Why can't you simply override the method get_object() in your view and do custom logic in it to update the object?
def get_object(self):
obj = super().get_object()
serializer = self.get_serializer(obj, data=self.request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return obj

Related

How to pass data to serializers in django

I want to pass user_id from view to serializer
I have model Answer
class Answer(models.Model) :
text = models.CharField(max_length=500)
question_id = models.CharField(max_length=25)
user_id = models.CharField(max_length=25, default=1)
This is my Serializer
class CreateAnswer(generics.CreateAPIView) :
def get_serializer_context(self):
context = super().get_serializer_context()
context["id"] = self.request.user.id
return context
serializer_class = AnswerQuestionSerializer
queryset = Answer.objects.all()
What I need to write in my view to take user_id and create model with this user_id ?
You can override the perform_create method & pass the user_id field to save method of the serializer.
class CreateAnswerView(generics.CreateAPIView) :
serializer_class = AnswerQuestionSerializer
def perform_create(self, serializer):
serializer.save(user_id=self.request.user.id)
You can use serializers.Hiddenfield to get current user in serializer class
https://www.django-rest-framework.org/api-guide/fields/#hiddenfield
There are multiple ways to do this task. One of them is to override create in your serializer.
Following is the code snippet:
class BlogSerializer(serializers.Serializer):
def create(self, validated_data):
user = self.context['request'].user
blog = Blog.objects.create(
user=user,
**validated_data
)
return blog
Explanation: A context is passed to the serializer which contains the request by default. So you can access the user easily with self.context['request'].user

update_or_create in my django rest framework api work wrong

My problem in GIF
Instead of updating the user's rating DRF creating new.
Maybe i made a mistake in serializer?
I wrote documentation but i dont kwon where i wrong.
My code:
views.py:
class CreateReviewView(APIView):
def post(self, request):
review = CreateReviewSerializer(data= request.data)
if review.is_valid():
review.save()
return Response(status=201)
class CreateRatingView(APIView):
def get_user(self, request):
user= request.user
if user =="AnonymousUser":
return "noname in CreateRaringView"
return user
def post(self, request):
serializer = CreateRatingSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(user=self.get_user(request))
return Response(status=201)
else:
return Response(status=400)
serializers.py:
class Meta:
model = Rating
fields = ('star','movie')
def new(self,validated_data):
rating = Rating.objects.update_or_create(
user= validated_data.get('user',None),
movie= validated_data.get('movie',None),
defaults={'start': validated_data.get("star")}
)
return rating
models.py:
class Rating(models.Model):
"""Рейтинг"""
user = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name="Пользователь",related_name='user')
star = models.ForeignKey(RatingStar, on_delete=models.CASCADE, verbose_name="Звезда",related_name="star")
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, verbose_name="Фильм",related_name="movie")
def __str__(self):
return f"{self.star} - {self.movie}"
class Meta:
#unique_together = ['user','movie','star']
verbose_name = "Рейтинг"
verbose_name_plural = "Рейтинги"
According to documentation, Calling .save() will either create a new instance, or update an existing instance, depending on if an existing instance was passed when instantiating the serializer class:
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
In your case you are only passing new data and missing existing instance.
def post(self, request):
serializer = CreateRatingSerializer(data=request.data)
I renamed def new(self,validated_data): to def create(self,validated_data): in serializers.py and all started working. >.<

How to handle PUT request in Django RestFramework

I am trying to update my data in 'VoterList' model by using PUT api, but i don't know which function should i use in my 'views.py' file to handle the coming PUT request because in PUT api, we use parameters from URL to pick the relevent entry from model for updation and then update it by using data received from PUT api.
model.py
class VoterList(models.Model):
# id = models.IntegerField(auto_created= True, primary_key=True)
name = models.CharField( max_length=20)
email = models.EmailField()
mobile = models.IntegerField()
city = models.CharField( max_length=20)
type = models.CharField(max_length=20)
def __str__(self):
return self.name
serializers.py
class FillVoterListSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = VoterList
fields = ('id','name', 'email', 'mobile', 'city', 'type')
def update(self, instance, validated_data):
instance.name = validated_data.pop("name", instance.name)
instance.email = validated_data.pop("email", instance.email)
instance.save()
return instance
I will manage the code for PUT in serializers by myself.
views.py
class UpdateVoter(APIView):
serializer_class = FillVoterListSerializers
permission_classes = (AllowAny,)
def post(self, request,*args,**kwargs):
isDataExist = VoterList.objects.get(id=request.data.get('id'))
if not isDataExist:
return Response({"message":"No Voter exist with this id."})
else:
isDataUpdated = self.serializer_class(isDataExist, request.data, partial=True)
if isDataUpdated.is_valid():
isDataUpdated.save()
return Response({"message": "Voter updated."})
else:
return Response({"message": "All fields are Mandatory."})
urls.py
urlpatterns = [
url('api/updateVoter/(?P<id>[0-9]+)/$', UpdateVoter.as_view(), name= "updateVoter")]
So what code should i write in my view.py to handle the PUT request.
Note: I want to tell you that i am preparing api for mobile applications, so please respond accordingly.
Any help is appreciated.
You can use the put() function in your view similar to the post() which you've used
def put(self, request, pk, format=None):
# Your code here
Refer the DRF docs : https://www.django-rest-framework.org/tutorial/3-class-based-views/

How can I highlight a section of an article in the backend using django rest framework

This is how I create my article
`class CreateArticleView(ListCreateAPIView):
"""
Class handles creating of articles
"""
permission_classes = (IsAuthenticatedOrReadOnly,)
serializer_class = ArticleSerializer
renderer_classes = (ArticleJSONRenderer,)
queryset = Article.objects.all()
def list(self, request, *args, **kwargs):
queryset = Article.objects.all()
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
article = request.data.get('article', {})
if self.request.user.is_verified is False:
message = error_messages['email_verification']
return Response(message, status=status.HTTP_401_UNAUTHORIZED)
context = {"request": request}
serializer = self.serializer_class(data=article, context=context)
serializer.is_valid(raise_exception=True)
serializer.save(author=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)`
`
This is how I get to view a single article, therefore I would like to fetch a single article and be able to highlight several parts of the article and manage to comment on them
class GetUpdateDeleteArticle(RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (ArticleJSONRenderer,)
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = 'slug'
#staticmethod
def validate_author(request, article):
if request.user.pk != article.author_id:
message = error_messages['unauthorised']
return Response(message, status.HTTP_403_FORBIDDEN)
def get(self, request, *args, **kwargs):
"""
:param request: user requests to get an article
:param kwargs: slug field is passed in the url
:return: data and response if article exists
"""
try:
article = Article.objects.get(slug=kwargs['slug'])
except Article.DoesNotExist:
message = error_messages['article_404']
return Response(message, status=status.HTTP_404_NOT_FOUND)
serializer = ArticleSerializer(
instance=article, context={'request': request})
return Response(serializer.data, status=status.HTTP_200_OK)
This is more of a design decision in the model level and less of an API issue. I would go with the following DB design:
A model Highlight that stores a reference to the article, the begining index of the highlighted part of the text and the ending index. So, something roughly like this:
class Highlight(models.Model):
article = models.ForeignKey(Article, related_name='highlights')
start = models.PositiveIntegerField()
end = models.PositiveIntegerField()
Then a Comment model. A common design is to use generic relations so that comments can be used with any model in your application. Something like this should suffice:
class Comment(TimeStampedModel):
text = models.TextField(blank=False)
author = models.ForeignKey(User, related_name='comments')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
You can now build an API around this design where clients create highlights by sending the article id, start and end indices of the highlighted text. Comments can also be created using the highlight's id. Retrieving the highlights for an article should also be fairly easy to implement.

'collections.OrderedDict' object has no attribute 'pk' - django rest framework

I have a model and I want to write an update() method for it in order to update.
The below snippet is my model:
class Klass(models.Model):
title = models.CharField(max_length=50)
description = models.CharField(max_length=500)
university = models.CharField(max_length=50,blank=True, null=True)
teacher = models.ForeignKey(Profile, related_name='teacher', on_delete=models.CASCADE)
and the below snippet is corresponding Serializer:
class KlassSerializer(ModelSerializer):
teacher = ProfileSerializer()
url = HyperlinkedIdentityField(view_name='mainp-api:detail', lookup_field='pk')
klass_settings = KlassSettingsSerializer()
class Meta:
model = Klass
fields = ('url', 'id', 'title', 'description', 'university','teacher')
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('description', instance.description)
instance.university = validated_data.get('university', instance.university)
instance.save()
return instance
And for update, I use below snippet:
class KlassAPIView(APIView):
def put(self, request, pk=None):
if pk == None:
return Response({'message': 'You must specify class ID'}, status=HTTP_400_BAD_REQUEST)
klass = Klass.objects.get(pk=pk)
if request.user.profile.type != 't':
raise PermissionDenied(detail={'message': 'You aren't teacher of this class, so you can't edit information.'})
serializer = KlassSerializer(data=request.data, context={'request': request})
serializer.initial_data['teacher'] = request.user.profile.__dict__
if serializer.is_valid():
serializer.update(instance=klass, validated_data=serializer.data) # Retrieve teacher and store
return Response({'data': serializer.data}, status=HTTP_200_OK)
else:
return Response({'data': serializer.errors}, status=HTTP_400_BAD_REQUEST)
but when I send data with PUT method, it returns below error:
AttributeError at /api/class/49/
'collections.OrderedDict' object has no attribute 'pk'
and the error occurs in serializer.update(instance=klass, validated_data=serializer.data) line.
Just ran into the same error.
In my case the problem was I accessed serializer.data before doing serializer.save().
Google dropped me here, so maybe someone else will also find this helpful.
Source: https://github.com/encode/django-rest-framework/issues/2964
i don't know if this helps. I always add the id field in the serializer due to that similar issue:
id = serializers.ModelField(model_field=YourModel._meta.get_field('id'), required=False)
Make sure it's required=False because when you create a new record the id field is not present.
Well in my case, I was doing:
champions_list = []
for champion in champions_serializer.data:
c = {"id": champion.id}
champions_list.append(c)
And the correct way to do it is:
champions_list = []
for champion in champions_serializer.data:
c = {"id": champion["id"]}
champions_list.append(c)
And make sure that you return the id inside the serializer.
Many answers to this question note that serializer.save() must be called before using serializer.data.
In my case, I was definitely calling serializer.save(), however, I was overriding the save method on my serializer and did not set self.instance on the serializer in that method.
So if you are overriding save be sure to do:
class MySerializer(serializers.ModelSerializer):
def save(self, *args, **kwargs):
...
self.instance = instance
return self.instance

Categories