How to PATCH a single field using Django Rest Framework? - python

I have a model 'MyModel' with many fields and I would like to update a field 'status' using PATCH method. I'm using class based views. Is there any way to implement PATCH?

Serializers allow partial updates by specifying partial=True when initializing the serialzer. This is how PATCH requests are handled by default in the generic views.
serializer = CommentSerializer(comment, data=request.data, partial=True)
This will allow you to update individual fields in a serializer, or all of the fields if you want, without any of the restrictions of a standard PUT request.

As Kevin Brown stated you could use the partial=True, which chefarov clarified nicely.
I would just like to correct them and say you could use generics freely, depending on the HTTP method you are using:
If you are using PATCH HTTP method like asked, you get it out of the box. You can see UpdateModelMixin code for partial_update:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
For any HTTP method different from PATCH, this can be accomplished by just overriding the get_serializer method as follows:
def get_serializer(self, *args, **kwargs):
kwargs['partial'] = True
return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)
This will create the serializer as partial, and the rest of the generics will work like a charm without any manual intervention in the update/partial_update mechanism.
Under the hood
I used the generic: generics.UpdateAPIView which uses the UpdateModelMixin which has this code:
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
…
So, if you override the get_serializer function, you can actually override the partial argument and force it to be true.
Please note, that if you want it partial only for some of the HTTP methods, this makes this approach more difficult.
I am using djangorestframework==3.5.3

It does seem to be supported out of the box. On your browser API, navigate to a model detail page, at the bottom next to the HTML Form tab click Raw data, delete everything from the JSON string except the ID field and the field you wish to change, and click PATCH. A partial PATCH update is performed.
I'm using djangorestframework==3.2.4, and haven't had to do anything to my ViewSets and Serializers to enable this.
In this exampe we are updating the bool status_field field of the model, and I'm using jquery 2.2.1. Add the following to the <head>:
<script src="{% static 'my_app/jquery.min.js' %}"></script>
<script>
$(document).ready(function(){
var chk_status_field = $('#status_field');
chk_status_field.click(function(){
$.ajax({url: "{% url 'model-detail' your_rendering_context.id %}",
type: 'PATCH', timeout: 3000, data: { status_field: this.checked }
})
.fail(function(){
alert('Error updating this model instance.');
chk_status_field.prop('checked', !chk_status_field.prop('checked'));
});
});
});
</script>
Then in a <form>:
<input type="checkbox" id="status_field" {% if your_rendering_context.status_field %}
checked {% endif %} >
I chose to permit the checkbox to change, then revert it in the case of failure. But you could replace click with mousedown to only update the checkbox value once the AJAX call has succeeded. I think this will lead to people repeatedly clicking the checkbox for slow connections though.

If anyone is still planning to find a simple solution using ModelSerializer without changing much of your views, you can subclass the ModelSerializer and have all your ModelSerializers inherit from that.
class PatchModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
kwargs['partial'] = True
super(PatchModelSerializer, self).__init__(*args, **kwargs)
class ArticleSerializer(PatchModelSerializer):
class Meta:
model = Article

I struggled with this one for a while, but it is a very straightforward implementation using generic views or a combination of generic views and mixins.
In the case of using a generic update view (generics.UpdateAPIView), just use the following code, making sure the request type is PATCH:
class UpdateUser(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
There's nothing else to it!
If you're using an update mixin (mixins.UpdateModelMixin) in combination with a generic view (generics.GenericAPIView), use the following code, making sure the request type is PATCH:
class ActivateUser(mixins.UpdateModelMixin, generics.GenericAPIView):
serializer_class = UserSerializer
model = User
lookup_field = 'email'
def get_queryset(self):
queryset = self.model.objects.filter(active=False)
return queryset
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
The second example is more complex, showing you how to override the queryset and lookup field, but the code you should pay attention to is the patch function.

Related

Make REST Framework require authentication for GET method

I'm working on an Django app that uses REST Framework together with Swagger. Also added some models, and one of them is called Example. Added some views based on mixins in views.py for the model previously mentioned.
In views.py, I've created two classes: ExampleList (that uses GET to get all the objects made out from that model and POST to add a new model) and ExampleIndividual, that uses methods such as individual GET, PUT and DELETE.
Anyways, this is how my ExampleList class looks like:
class ExampleList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
In the settings.py file, in the REST_FRAMEWORK configuration, I've set:
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
Everything works fine at this moment. What I want to do, is, whenever I want to get the list of all objects from the Example model (access the get() method from the ExampleList() class, I want it to work only if I am authenticated (using the Authorize option from Swagger). If not, a status code like "FORBIDDEN" should be returned.
I tried using permission_classes = [IsAuthenticated] at the beginning of the method, but it didn't work. It seems that I can still GET all the objects without being authenticated into Swagger.
Any advice on how I can do that properly? Thank you.
Did you tried it with Token Authentication? With this method in each request made to the API, the token must be included in the Header Field Authorization. https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication to test it in Browser you can install an extension called Mod Header https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj there you can include the token in the header field.

Is there a 'DetailView' in Django Admin?

I know there is a change/update view in Django admin but is there any detail view that just lists out the record's attributes? Kind of like the DetailView in the Django app?
Or does anyone know any 3rd party packages I can install to provide the same functionality?
I too was investigating this recently.
One approach that works is to create a custom ModelAdmin with a detail_view method that forwards the call to ModelAdmin's changeform_view() method. Then this view is added to the urls list via overriding ModelAdmin.get_urls().
Then, in this method set a class attribute, say __detail_view to True. Then override has_change_permission() method, which returns False, if __detail_view is detected and set to True. This will cause AdminSite to render the fields in readonly mode (using the AdminReadonlyField wrapper fields) instead of the standard AdminField objects.
You can also change the change_form_template to a custom template for detail_view to accommodate custom rendering for detail views.
class CustomModelAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
if getattr(self, '__detail_view', None):
return False
return super().has_change_permission(request, obj)
def detail_view(self, request, object_id, form_url='', extra_context=None):
setattr(self, '__detail_view', True)
# Custom template for detail view
org_change_form_template = self.change_form_template
self.change_form_template = self.detail_view_template or self.change_form_template
ret = self.changeform_view(request, object_id, form_url, extra_context)
self.change_form_template = org_change_form_template
delattr(self, '__detail_view')
return ret
def get_urls(self):
urls = super().get_urls()
# add detail-view for the object
from django.urls import path
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
# Replace the backwards compatibility (Django<1.9) change view
# for the detail view.
urls[len(urls)-1] = path('<path:object_id>/', wrap(self.detail_view), name='%s_%s_detail' % info)
return urls
I haven't tried the custom template approach, but using the __detail_view object attribute to force readonly rending seems to work.
The default change_form_template still shows the delete button at the bottom, which is okay I guess. But it needs another button to actually take you to the real change page where the object can be changed. Again template customization is the way to go here. Hint: look at {% submit_row %} in admin templates and model a custom inclusion template tag that displays the Change button, if the user has change permission. Take note to call the has_change_permission() here to get the real permission before setting the __detail_view attribute.
Not sure if there are other implications for doing it this way, but it ought to work.
HTH

When to use get, get_queryset, get_context_data in Django?

I recently learned that you should override the get method when you specifically want to do something other than what the default view does:
class ExampleView(generic.ListView):
template_name = 'ppm/ppm.html'
def get(self, request):
manager = request.GET.get('manager', None)
if manager:
profiles_set = EmployeeProfile.objects.filter(manager=manager)
else:
profiles_set = EmployeeProfile.objects.all()
context = {
'profiles_set': profiles_set,
'title': 'Employee Profiles'
}
That's simple enough, but when should I use get_queryset or get_context_data over get? To me it seems like they basically do the same thing or am I just missing something? Can I use them together? This is a major source of confusion for me.
So to reiterate: In what cases would I use get over get_queryset or get_context_data and vise versa?
They indeed do different things.
get()
This is a top-level method, and there's one for each HTTP verb - get(), post(), patch(), etc. You would override it when you want to do something before a request is processed by the view, or after. But this is only called when a form view is loaded for the first time, not when the form is submitted. Basic example in the documentation. By default it will just render the configured template and return the HTML.
class MyView(TemplateView):
# ... other methods
def get(self, *args, **kwargs):
print('Processing GET request')
resp = super().get(*args, **kwargs)
print('Finished processing GET request')
return resp
get_queryset()
Used by ListViews - it determines the list of objects that you want to display. By default, it will just give you all for the model you specify. By overriding this method you can extend or completely replace this logic. Django documentation on the subject.
class FilteredAuthorView(ListView):
template_name = 'authors.html'
model = Author
def get_queryset(self):
# original qs
qs = super().get_queryset()
# filter by a variable captured from url, for example
return qs.filter(name__startswith=self.kwargs['name'])
get_context_data()
This method is used to populate a dictionary to use as the template context. For example, ListViews will populate the result from get_queryset() as author_list in the above example. You will probably be overriding this method most often to add things to display in your templates.
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['page_title'] = 'Authors'
return data
And then in your template, you can reference these variables.
<h1>{{ page_title }}</h1>
<ul>
{% for author in author_list %}
<li>{{ author.name }}</li>
{% endfor %}
</ul>
Now to answer your main question, the reason you have so many methods is to let you easily stick your custom logic with pin-point accuracy. It not only allows your code to be more readable and modular, but also more testable.
The documentation should explain everything. If still not enough, you may find the sources helpful as well. You'll see how everything is implemented with mixins which are only possible because everything is compartmentalized.
Let's look at the default implementation of ListView's get method:
https://github.com/django/django/blob/92053acbb9160862c3e743a99ed8ccff8d4f8fd6/django/views/generic/list.py#L158
class BaseListView(MultipleObjectMixin, View):
"""
A base view for displaying a list of objects.
"""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if (self.get_paginate_by(self.object_list) is not None
and hasattr(self.object_list, 'exists')):
is_empty = not self.object_list.exists()
else:
is_empty = len(self.object_list) == 0
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
context = self.get_context_data()
return self.render_to_response(context)
You will notice that get_queryset gets called in the first line. You can simply overwrite that if you just want to return your model's queryset after applying some filtering/ordering etc.
You don't need to overwrite the whole get method for that because you will be missing on all this provided functionality i.e. pagination, 404 checks etc.
get_context_data merges the resulting queryset together with context data like querystring parameters for pagination etc.
What I would recommend would be to check with django's source every once in a while and try to understand it a little bit so that you can recognize the most appropriate method you can overwrite/replace.

Python Django RestFramework route trigger

I'am building an API using python 2.7 and django 1.7 and I'm facing a problem. I'm not an expert in Rest Framework but I understand the basic mechanisms.
I can resume my problem by giving you an example. I have a route lets say
/api/project/
Django Rest Framework provides me all basic operations for this route and I don't need to write them e.g:
POST, GET, PUT, DELETE => /api/project/
The fact is, I want to do some operation when I create a new project. I want to add the id of the user who has created the new project.
I want to add some kind of trigger/callback to the create function:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def create(self, request, *args, **kwargs):
I want to keep the internal behavior of Rest Framework (I don't want to rewrite the route functions), but I want the route to do extra stuff and I need the request object in my trigger/callback. Something like
def callback(request, instance):
instance.created_by = request.user
instance.save()
Do you have any ideas?
Thank you.
You need to add creator_id field to your serializer as well as to model represented by the resource. Then you can do something like this in your view:-
import copy
class ProjectViewSet(viewsets.ModelViewSet):
...
def create(self, request, *args, **kwargs):
data = copy.deepcopy(request.data)
data['creator_id'] = request.user.id
request._data = data
return super(ProjectViewSet, self).create(request, *args, **kwargs)

How does class based view with multiple methods work with urls in django?

I have a created a view class "class Demo", which has 3 functions
def update_time()
def get_context()
def before_response()
urls.py : url(r'^demo/$', Demo.as_view(),name='demo_class'),
When i'll enter url /demo/ how will it determine which function to call from "class Demo" ?
Because Django’s URL resolver expects to send the request and associated arguments to a callable function, not a class, class-based views have an as_view() class method which serves as the callable entry point to your class. The as_view entry point creates an instance of your class and calls its dispatch() method. dispatch looks at the request to determine whether it is a GET, POST, etc, and relays the request to a matching method if one is defined, or raises HttpResponseNotAllowed if not.
just read the docs
Basically class based views are recommended when you need to handle both get and post requests at one point. For example in get method of class Register, you can render the registration form and in its post method, you can handle the form submission. If its a get request it will automatically invoke the get() method in the class, same for post request. Also you can write any common code in the dispatch() method which will be invoked for every request.eg:
class Register(View):
def dispatch(self, *args, **kwargs):
'''
common code here
'''
return super(Register, self).dispatch(*args, **kwargs)
def get(self, request):
registration_form = RegistrationForm()
return render(request, 'new.html', { 'form': registration_form })
def post(self, request):
registration_form = RegistrationForm(request.POST or None)
if registration_form.is_valid():
#save form
return HttpResponseRedirect(reverse('success-show'))
return render(request,new.html', { 'form': registration_form })
For references you can check this website.
You need to subclass a class based views, and depending on that it will have one or another method.
For example TemplateView renders a template you pass in the template_name attribute.
All class based views care about is that the attributes needed to work properly are setted. That is done via different methods. You can check the django's documentation for specifics.
For example, if you want to render a form in your template view, you will need to pass the form in the context, so you can override get_context_data() like:
def get_context_data(self, **kwargs):
context = super(DemoClass, self).get_context_data(**kwargs)
context['form'] = MyForm()
return context
There are different methods to handle different things, like querysets, objects, etc.
They are not complicated, but they are specific. If a class based view does not fit what you need, it may be easier to implement the functionality from a more general view (View, TemplateView) than forcing a more specific one to do things it is not intended for.
slightly change the url
add numbers one to three in url and put the condition in your view.
Ex.
url(r'^abc/(?P<newid>.*)$', 'class_demo'),
so your url will be like abc/1 or abc/2 or abc/3
view
def class_demo(requests, newid):
if newid==1:
update_time()
elif newid==2:
get_context()

Categories