DRF How to perform additional operations on an instance - django

Which of the given ways of writing additional logic is technically correct? The example includes changing the status of the document. After a quick reserach of similar questions, I understand that there are 3 possibilities described below. However, no answer describes which solution is used during daily practicals, and the examples from the documentation do not dispel doubts. Please help.
Providing custom data to the serializer and the standard model serializer:
class PZSaveAPIView(APIView):
#transaction.atomic
def patch(self, request, pk, format=None):
document = get_object_or_404(PZ, pk=pk)
print(request.data)
serializer = PZModelSerializer(
document, data={'status': 'E'}, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
After the object is serialized, change the status:
class PZSaveAPIView(APIView):
#transaction.atomic
def patch(self, request, pk, format=None):
document = get_object_or_404(PZ, pk=pk)
serializer = PZModelSerializer(
document, data=request.data, partial=True)
if serializer.is_valid():
pz = serializer.save()
pz.status = 'S'
pz.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Contain the logic in the serializer, and the view stays basic:
class PZUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = PZ
fields = '__all__'
def update(self, instance, validated_data):
instance.status = 'S'
instance.save()
return instance
Is it even necessary to use a serializer in such cases? Example:
class PZSaveAPIView(APIView):
def patch(self, pk):
document = get_object_or_404(PZ, pk=pk)
document.set_status_saved()
document.save()
return Response('Document saved')

If there is a need to validate data and return those data to the front end, the serializer is definitely required. So, whether to use a serializer or not depends upon the case, it's okay not to use a serializer if it is not in need.
And about whether to put logic on views or serializer, Django books recommend thick serializer and thin views. And Django rest framework itself provides update and create methods in ModelSerializer which mean it prefers the logic to update and create inside serializer and views to just return response.

Related

how to keep the old data fields when request.data does not include them

I created a model with fields title and description, and I have to always send a request with two fields even when I when I update only the title field. So, how can I make the view in which if the description field is missing then it keep the old description
class Paper(APIView):
def put(self, request, pk, format=None):
# TODO if any other field emtpy keep the old data
snippet = self.get_object(pk)
serializer = SerlizePaper(snippet, data=request.data)
if(serializer.is_valid() and request.user in snippet.who_can_edite.all()):
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I think what you are looking for is partial update.
Partial Update DRF documentation link
It can done in your case by
#note the partial keyword
serializer = SerlizePaper(snippet, data=request.data, partial=True)
You can take two approaches:
change your serializer
In your serializer,define the fields and "required=False" to both of
them:
class MySerializer(serializers.ModelSerializer):
title = serializers.CharField(required=False)
description = serializers.CharField(required=False)
Use a PATCH Http method
You can use PATCH method when you are calling your API and pass only
the field that you want to change

Creating custom generics for get, update, delete and post with Django Rest Framework in Django

I am thinking of refactoring my code because I think I'm repeating too much ending up with lines of code. Take of this instance below I have implemented a class based view to GET,PUT,and DELETE for the Unit Model. Later I will create another view for Department to do CRUD and will follow the same pattern,is there a way I can make custom generic model views that can be dynamically used in any other view.
class UnitDetailView(generics.RetrieveAPIView):
""" Class based view for Unit Details. """
serializer_class = UnitSerializer
queryset = Unit.objects.all()
def get_object(self, pk, org_id=None):
try:
return Unit.objects.get(pk=pk, org_id=org_id)
except Unit.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
"""Get a unit instance."""
unit_obj = self.get_object(pk, org_id=get_auth(request))
serializer = UnitSerializer(unit_obj)
return Response(serializer.data)
def put(self, request, pk, format=None):
"""Update a unit instance."""
unit_obj = self.get_object(pk, org_id=get_auth(request))
serializer = UnitSerializer(unit_obj, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
"""Remove a unit instance."""
unit_obj = self.get_object(pk, org_id=get_auth(request))
unit_obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
You can use viewsets.ModelViewSet. And you won't need to write every method (get, put, delete, update)

Adding Filter to Django REST API

I am pretty new to Django and REST and I want to be able to specify a value and have the REST api only return a row where that value is met. Kinda like in sql select * from exampleTBL where id = 1 and then the first row is returned. But it would be done through the url: www.website/api/tmpHost/?id=1 and t hen the first row is returned through the REST API
My view looks like:
class tmp_HostList(APIView):
def get (self, request, format=None):
tmp_hosts = tmp_Host.objects.all()
serializer = tmp_HostSerializer(tmp_hosts, many=True, context={'request': request})
return Response(serializer.data)
def post(self, request, format=None):
serializer = tmp_HostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
my url looks like:
url(r'^api/tmpHost/$', views.tmp_HostList.as_view()),
my serializer looks like:
class tmp_HostSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = tmp_Host
fields = '__all__'
How would I go about doing this? I've seen solutions around here but they don't seem to work for me. The differences where that I use APIView and my serializer line would be: serializer = tmp_HostSerializer(tmp_hosts, many=True, context={'request': request}) while theirs would be simple like: serializer = tmp_HostSerializer
The simplest way is just check for get parameters and return a filtered object by the parameter:
from django.shortcuts import get_object_or_404
class tmp_HostList(APIView):
def get (self, request, format=None):
param = request.GET.get('id')
if param:
tmp_host = get_object_or_404(Host, id=param)
serializer = tmp_HostSerializer(tmp_host)
else:
tmp_hosts = tmp_Host.objects.all()
serializer = tmp_HostSerializer(tmp_hosts, many=True)
return Response(serializer.data)
There is also built in filtering for generic views and viewsets doc link
But the best choice is create a separate view for detail page or use viewset/generic views.
So your view is stay the same and you add a new one for detail page.
urls:
url(r'^api/tmpHost/(?P<id>\d+)$', views.tmp_HostList.as_view())
views:
class tmp_HostDetail(APIView):
def get (self, request, id=None, format=None):
tmp_host = get_object_or_404(Host, id=id)
serializer = tmp_HostSerializer(tmp_host)
return Response(serializer.data)

Is it possible to add a new function to the class based view of django rest framework

In class-based views of django rest framework, we have by default functions like get, post etc.. Other than that, Is it possible to add our own function? If it is possible how we will refer that in url.
My required functions
def get_user_by_name(request, name, format=None):
jobseekers = JobSeeker.objects.filter(name=name)
serializer = JobseekerSerializer(jobseekers, many=True)
return Response(serializer.data)
def get_user_by_email(request, email, format=None):
jobseekers = JobSeeker.objects.filter(email=email)
serializer = JobseekerSerializer(jobseekers, many=True)
return Response(serializer.data)
def get_user_by_school(request, school, format=None):
schools = SchoolDetails.objects.filter(school=school)
jobseekers = JobSeeker.objects.filter(email=email)
serializer = JobseekerSerializer(jobseekers, many=True)
return Response(serializer.data)
def get_user_by_email(request, email, format=None):
jobseekers = JobSeeker.objects.filter(email=email)
serializer = JobseekerSerializer(jobseekers, many=True)
return Response(serializer.data)
Pass a type variable in your view, and use the available HTTP methods. Since it appears you are retreiving data, the GET method is usually used for that, but you can truthfully use whatever method you want. The GET method allows you to put your variables in the URL too, if that's what you want.
Once you put the type variable in the request, you can just use if statements to determine what to do with it.
You could also create a different view for each type of request, although that might be overkill.
This is an example assuming use of the GET method:
class foo(APIView):
# This uses the GET method. POST, PUT, PATCH etc. would use
# def post(...), def put(...)...
def get(self, request, format=None):
# Check what type of request is being made and return the proper response.
if request.POST['type'] == 'get_user_by_name':
jobseekers = JobSeeker.objects.filter(name=request.POST['name'])
serializer = JobseekerSerializer(jobseekers, many=True)
return Response(serializer.data)
elif request.POST['type'] == 'get_user_by_email':
jobseekers = JobSeeker.objects.filter(email=email)
serializer = JobseekerSerializer(jobseekers, many=True)
return Response(serializer.data)
elif ...

How to make a PATCH request using DJANGO REST framework

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.