Django rest update one field - django

I just got started with Django and got stuck on something that I believe should be simple, but I don't know how to do.
I have a model like this one:
id = models.AutoField(primary_key=true)
...
amount = models.IntegerField()
...
Basically, the user will give an amount and the model needs to be updated with the current amount + the amount that the user inputs.
I use serializers to create new objects, but I don't really know how to use them to do this.

Let's assume the following:
your model is called MyModel
your serializer class is named MyModelSerializer
AmountPartialUpdateView is extending APIView
your partial update url is defined like this -that is, model id is
passed in the pk URL variable and the amount to add is passed in the amount URL variable:
urlpatterns = patterns('',
# ...
url(r'^model/update-partial/(?P<pk>\d+)/(?P<amount>\d+)$', AmountPartialUpdateView.as_view(), name='amount_partial_update'),
# ...
)
Then, you should implement the correct update logic in the AmountPartialUpdateView.patch() method. One way to accomplish this is:
from django.shortcuts import get_object_or_404
from rest_framework import Response
class AmountPartialUpdateView(APIView):
def patch(self, request, pk, amount):
# if no model exists by this PK, raise a 404 error
model = get_object_or_404(MyModel, pk=pk)
# this is the only field we want to update
data = {"amount": model.amount + int(amount)}
serializer = MyModelSerializer(model, data=data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
# return a meaningful error response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This way, visiting the URL
model/update-partial/123/5
you will increase the amount of model id 123 by 5 units.

just use the partial=True as one of your serializer parameter and create an object for your filed that you want to update i.e i want to update the queue status
data = {'queue_status': 1}
serializer_patient_queue = PatientQueueSaveSerializer(queue_item, data=data, partial=True)

Related

django-rest-swagger can't seem to work for me. I can't get it to document anything beyond a title

It seems like django-rest-swagger dropped support for the YAML documentation, and replaced it with a vague non-documented way to do things. I've spent the last 48 hours trying to understand how I can have it document the parameters that go into my post methods.
For instance: I have this:
class user_addresses(APIView):
"""
get all addresses or post a new one
"""
authentication_classes = ([JSONWebTokenAuthentication])
def get(self, request, format=None):
addresses = Address.objects.filter(owner_id=request.user.id)
print (addresses)
serializer = address_serializer(addresses, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = address_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'success': True,
'result': serializer.validated_data},
status=status.HTTP_201_CREATED)
return Response({'success': False,
'result': serializer.errors},
status=status.HTTP_400_BAD_REQUEST)
But the django-rest-swagger will show it as:
Can someone point me in the direction of something that works where I can define all the rich data that swagger allows, like the post field names, if they're mandatory or not. etc. i'm just going crazy here running in circle and can't find anything but complaints that there's no way to do this.
So the idea with the 2.0 update was to use CoreAPI, the "internal" rest framework schema generation, and from it generate the swagger spec.
CoreAPI uses serializer and view classes to do its thing. From serializers it knows what fields are required, what type are those fields and if you want to add your personal description you can do so with help_text parameter:
some_field = serializers.Field(help_text='Field description')
In your case, the problem will be that it won't be able to understand the relationship between the APIView and your serializer. I suggest to take an extra step and move to generic views or viewsets, all of them support serializer_class attribute that can be used for the introspection. For your example something like this should work:
# serializer
class AddressSerializer(serializers.ModelSerializer):
line1 = serializers.CharField(help_text='Field documentation!')
class Meta:
model = Address
fields = '__all__'
read_only_fields = 'owner',
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super().create(validated_data)
# api class-based view
class UserAddresses(generics.ListCreateAPIView):
"""
General API documentation (not wisible in the swagger view)
get:
GET-specific documentation!
Lorem ipsum
post:
POST-specific documentation!
Dolor **sit amet**
"""
authentication_classes = ([JSONWebTokenAuthentication])
permission_classes = permissions.IsAuthenticated,
serializer_class = AddressSerializer
def get_queryset(self):
return Address.objects.filter(owner_id=self.request.user.id)
For views there is a specific docstirng format, it's very simple and hopefully, will improve overtime. In any way, you should have a bit more acceptable result now:
A CoreAPI Document can help you make a custom Swagger view. Swagger takes a coreapi json input to render the view - Django Rest Swagger uses the Python bindings of CoreAPI to generate that JSON (https://github.com/core-api/python-client).
What does the coreapi.Document object contain?
For each API, you can create a coreapi.Link() object.
Each Link object contains:
A URL
HTTP Method
Description
Fields
The list of fields must contain coreapi.Field() objects. A Field object has the parameters:
Name
Required (whether the field is mandatory)
Location (path parameter or query parameter)
Description
An Example
A sample Swagger Schema would look something like this, if we were to use CoreAPI:
import coreapi
def api_schema_generator():
api_schema = coreapi.Document(
title="My Swagger",
content={
"User Addresses": {
"int_api_get": coreapi.Link(
url="/int_api/v1/addresses/",
action="get",
description="Get addresses of a user",
fields=[
coreapi.Field(
name="user_id",
required=True,
location="path",
description="Unique ID of the user whose addresses are to be found"
),
]
),
"int_api_post": coreapi.Link(
url="/int_api/v1/addresses/",
action="post",
description="Add address for a user",
fields=[
coreapi.Field(
name="user_id",
required=True,
location="path",
description="Unique ID of the user"
),
coreapi.Field(
name="address",
required=True,
location="path",
description="Address of the user"
),
]
)
}
}
)
return api_schema
Our view would take this coreapi.Document object as input. We use the SwaggerUIRenderer, OpenAPIRenderer and CoreJSONRenderer decorators for our view.
views.py:
from rest_framework.decorators import api_view, renderer_classes
from rest_framework_swagger import renderers as swagger_renderer
from rest_framework import renderers
#api_view()
#renderer_classes([renderers.CoreJSONRenderer,
swagger_renderer.OpenAPIRenderer,
swagger_renderer.SwaggerUIRenderer,
])
def schema_view(request):
api_schema = api_schema_generator()
return response.Response(api_schema)
All that we need now is a URL mapping for our view.
urls.py:
from django.conf.urls import include, url
urlpatterns = [
url(r'^$', views.schema_view),
]
Writing a custom swagger might seem slightly tedious, but you have complete control over what data you want to expose in your Swagger View.

django rest framework PUT returns 404 instead of creating an object

I want to be able to create or update an object using the same request. The operation should be idempotent.
Sending a PUT request to DRF work as expected if the object exists but if the object doesn't exists I get a 404 instead of creating it.
models.py:
class Btilog(models.Model):
md5hash = models.CharField(primary_key=True, max_length=32)
vteip = models.ForeignKey('vte.VTE')
timestamp = models.DateTimeField(blank=False)
source = models.TextField()
code = models.CharField(max_length=10, blank=False)
msg = models.TextField(blank=False)
api.py:
class BtilogSerializer(serializers.ModelSerializer):
class Meta:
model = models.Btilog
class BtilogVSet(viewsets.ModelViewSet):
queryset = models.Btilog.objects.all()
serializer_class = BtilogSerializer
permission_classes = (permissions.AllowAny,)
urls.py:
...
router = routers.DefaultRouter()
router.register(r'btilog', api.BtilogVSet)
urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
...
)
Failing request
http --form PUT http://192.168.10.121:8888/logger/api/btilog/60c6b9e99c43c0bf4d8bc22d671169b1/ vteip='172.25.128.85' 'code'='Test' 'md5hash'='60c6b9e99c43c0bf4d8bc22d671169b1' 'timestamp'='2015-05-31T13:34:01' msg='Test' source='Test'
HTTP/1.0 404 NOT FOUND
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Date: Mon, 09 Feb 2015 15:16:47 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Cookie
{
"detail": "Not found"
}
As described here: http://restcookbook.com/HTTP%20Methods/put-vs-post/ the correct behaviour of put should be to create the object if it doesn't exists.
The same error occurs using The Browsable API Tool from DRF to make the request. Is the behaviour of DRF also alike? What I'm doing wrong?
Well, maybe you should try to overwrite update method inside your modelviewset, which handle the PUT http method:
class BtilogVSet(viewsets.ModelViewSet):
queryset = models.Btilog.objects.all()
serializer_class = BtilogSerializer
permission_classes = (permissions.AllowAny,)
def update(self, request, *args, **kwargs):
try:
instance = Btilog.objects.get(pk=kwargs['pk'])
serializer = serializers.BtilogSerializer(instance=instance,data=request.data)
if serializer.is_valid():
btilog=serializer.save()
return Response(serializer.data,status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Btilog.DoesNotExist:
serializer = serializers.BtilogSerializer(data=request.data)
if serializer.is_valid():
btilog=serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Yes, in general with DRF you will create an object using a POST and update an object using PUT. Http PUTs should be idempotent, whereas POSTs are not necessarily so -and POSTs will never be idemponent if you have an automatically created field like a timestamp in the created object.
To get the effect the OP wishes above you need to place the create functionality of the POST http method into the PUT method.
The issue is that PUTs are mapped to the "update" action only (when using DefaultRouter in urls.py) and the update action does expect the object to exist. So you have to slightly amend the update function (from rest_framework.mixins.UpdateModelMixin) to handle creating objects that do not currently exist.
I am arriving somewhat late to this question so perhaps this may assist someone working on later versions of Django Rest Framework, my version is v3.9.4 .
if you are using a ModelViewSet, then I would suggest inserting the following update function within your views.py file, within your class viewset :
It is simply a blend of the DRF´s existing update and create mixins -and you get some extra checking thrown in with those mixins (permission checking, get_serializer_class etc.) Plus it is a bit more portable as it does not contain references to models, - well done to DRF developers (yet again). You will need to import Http404 and ValidationError as shown below.
from django.http import Http404
from rest_framework import status
from rest_framework.exceptions import ValidationError
class BtilogVSet(viewsets.ModelViewSet):
queryset = models.Btilog.objects.all()
serializer_class = BtilogSerializer
permission_classes = (permissions.AllowAny,)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
try:
instance = self.get_object() #throws a Http404 if instance not found
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
except Http404:
#create the object if it has not been found
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # will throw ValidationError
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
except ValidationError: # typically serializer is not valid
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except:
raise
Note, PATCH is also mapped to the update() function indirectly via the function
partial_update().You don't need to include the partial_update code below, it is supplied by default from the file rest_framework.mixins.UpdateModelMixin, which is a mixin to the ModelViewSet. I show it here for purely illustrative purposes, you do not need to do anything to it.
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)

Django Form Primary Key on save() method - getting NoneType traceback

I was using the q&a at
Get Primary Key after Saving a ModelForm in Django.
It's exactly on point with what I need to do.
I have the following model:
class meetingEvent(models.Model):
'''
A meeting event
'''
name = models.CharField(max_length=64, help_text="a name for this meeting")
account_number = models.ForeignKey(account)
meeting_type = models.ForeignKey(meetingType)
meeting_group = models.ForeignKey(meetingGroup)
start_time = models.DateTimeField(help_text="start time for this event")
end_time = models.DateTimeField(help_text="end time for this event")
created_time = models.DateTimeField(auto_now_add=True)
listed_products = models.ForeignKey(product)
additonal_notes = models.TextField(help_text="additional notes for this meeting")
def __unicode__(self):
return self.name
I have the following form:
class meetingEventForm(forms.ModelForm):
"""
to create a new meeting event.
"""
portal_user = forms.CharField(help_text="username to access portal data")
portal_pass = forms.CharField(widget=forms.PasswordInput, help_text="password to add access portal data")
def save(self, commit=True):
super(meetingEventForm, self).save(commit=commit)
class Meta:
model = meetingEvent
I have the following view:
def meeting_event(request):
if request.method == 'POST':
form = meetingEventForm(request.POST)
if form.is_valid():
new_agenda=form.save()
return HttpResponseRedirect(reverse('agenda_detail', args=(new_agenda.pk,)))
else:
form = meetingEventForm()
return render_to_response('agendas/event.html',{'form':form,}, context_instance=RequestContext(request))
I've confirmed that this makes it into the database cleanly.
However, I get the following error:
Traceback:
File "/usr/lib/python2.6/site-packages/Django-1.5.2-py2.6.egg/django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/lib/python2.6/site-packages/Django-1.5.2-py2.6.egg/django/contrib/auth/decorators.py" in _wrapped_view
25. return view_func(request, *args, **kwargs)
File "/var/www/html/tamtools/agendas/views.py" in meeting_event
44. return HttpResponseRedirect(reverse('agenda_detail', args=(new_agenda.pk,)))
Exception Type: AttributeError at /agendas/add/
Exception Value: 'NoneType' object has no attribute 'pk'
Has something changed in Django 1.5 that I don't know about? new_agenda should be a meetingEventForm type, shouldn't it?
You overwrite save methid in modelform, but you forgot to return model.
return super( ....
You don't need to override the ModeForm save method, since you aren't doing anything special with it. Your ModelForm should look like:
class MeetingEventForm(forms.ModelForm):
"""
to create a new meeting event.
"""
class Meta:
model = meetingEvent
I also changed the class name to conform with the Python style guide.
You also have two extra fields in the form that have nothing to do with your model. There could be two reasons - one, you need to save these fields in another model, or the second option is that you want someone to authorize themselves before they can add a new event.
Since the second one seems more plausible, restrict access to the form from your view:
from django.contrib.auth.decorators import login_required
from django.shorcuts import render, redirect
#login_required()
def meeting_event(request):
form = MeetingEventForm(request.POST or {})
context = {'form': form}
if request.method == 'POST':
if form.is_valid():
new_agenda = form.save()
return redirect('agenda_detail', args=(new_agenda.pk,))
else:
return render(request, 'agendas/event.html', context)
else:
return render(request, 'agendas/event.html', context)
As this is a common task, and you are using django 1.5, why not use the generic class based views?
Your code will be reduced, and you don't have to worry about the mundane details:
First, in your views.py, create a class that inherits from the generic CreateView which is used to display a model form for a model, let the user fill it in, and save the details:
from django.views.generic.edit import CreateView
class CreateMeetingRequest(CreateView):
template_name = 'agendas/event.html'
model = meetingRequest
Now, to map the view to a url, we add it to urls.py. Since we also want the user to be logged in before they can add a meeting request - the login_required decorator takes care of that for us. It will check if the user is logged in - if not, redirect the user to a login form and once they have logged in, redirect them back to the form:
from django.contrib.auth.decorators import login_required
from .views import CreateMeetingRequest
urlpatterns = patterns('',
# your other views
url(r'meeting-request/add/$',
login_required(CreateMeetingRequest.as_view()), name='add-meeting-req'),
)
Finally, we need to tell the view where to go once the form is successful. CreateView will check if the model has a get_absolute_url method, and call that. So in your models.py:
from django.core.urlresolvers import reverse
class meetingRequest(models.Model):
# your normal fields
def get_absolute_url(self):
return reverse('agenda_detail', args=(self.pk,))

Django Rest Framework bulk updates inserting instead of updating

I'm trying to build out a bulk update view for a specific model using Django Rest Framework. In the short term, it only needs to update one field (toggling an invite from submitted=False to submitted=True), but I'd like it to be able to provide more functionality in the future. Whenever I test the view, however, a new object is being created instead of the current one being modified.
I feel like this must be a simple mistake on my part, but I can't figure out what's going on. The serializer object appears to be ignoring the value for "id" passed in through JSON, which may be contributing to the issue. Current code is:
class InviteBulkUpdateView(generics.UpdateAPIView):
def get_queryset(self):
order = self.kwargs['order']
invite = get_objects_for_user(self.request.user, 'sourcing.view_invite')
return invite.filter(order=order)
serializer_class = InviteInputSerializer
def put(self, request, *args, **kwargs):
data = request.DATA
serializer = InviteInputSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
class InviteInputSerializer(serializers.ModelSerializer):
class Meta:
model = Invite
fields = ('id', 'order', 'team', 'submitted')
Can anybody shed some light onto what I might be doing wrong?
Just in case somebody is looking for a library to handle this, I wrote a Django-REST-Framework-bulk which allows to do that in a couple of lines (the example only does bulk update but the library also allows bulk create and delete):
from rest_framework_bulk import ListCreateBulkUpdateAPIView
class FooView(ListCreateBulkUpdateAPIView):
model = FooModel
You're not passing object instances to your serializer. (Thus it will create new instances rather than update.) See the docs on dealing with multiple objects in serializers where you'll see your QuerySet passed in.
Django has update method to handle that. You may want to read full info from django documentation.
Here is a sample code where you can use to update given field for multiple records:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import APIException
class Room_Update_ViewSet(APIView):
def put(self, request,*args, **kwargs):
hotel_id = self.kwargs.get('hotel_id')
room_ids = self.request.query_params.get('room_ids')
room_ids = list(map(int, room_ids.split(',')))
try:
Room.objects.filter(hotel_id=hotel_id,id__in=room_ids).update(booked_status=False)
instances = Room.objects.filter(hotel_id=hotel_id,id__in=room_ids)
serializer = RoomSerializer(instance=instances, many=True)
return Response(serializer.data,status=status.HTTP_200_OK)
except Exception as e:
print("Error udating rooms-->",e)
raise APIException

How would I create a CBV using django rest framework which will either retrieve a model instance or create a new one?

I have a person model with the fields first_name, last_name and email. I'd like to send these fields to a view that would check the DB for an existing instance. If there is one the ID will be returned, if not a new instance will be created and the new ID returned. Using a standard FBV I would do this (cut down version, minus validation etc):
from django.http import HttpResponse
from mysite.models import Person
import json
def get_or_create_person(request):
try:
person = Person.objects.get(first_name=request.POST['first_name'],
last_name=request.POST['last_name'], email=request.POST['email'])
except Person.DoesNotExist:
person = Person(first_name=request.POST['first_name'],
last_name=request.POST['last_name'], email=request.POST['email'])
person.save()
response = {'id': person.id}
return HttpResponse(json.dumps(response))
Is there any real point in converting it to use a CBV and tie it in with the rest framework and if so how would I go about doing it? The main reason I want it as a CBV is so I can use mixins etc that I already include in other parts of my app.
Edit: I thought about using the ViewSets available through the rest framework but they split this functionality into GET and POST methods, with get retrieving a record and post either updating or creating one. I basically need a view which can accept either.
I had to kinda combine the get and post functions as follows:
class GetOrCreateCustomerView(APIView):
'''
API endpoint that takes an email address, first name & surname and then
either returns the matching customer id or creates a new customer and
returns that id
'''
required_fields = ('first_name', 'last_name', 'email')
def get(self, request, format=None):
request_data = request.GET
response = self.get_customer_id(request_data)
return JSONResponse(response)
def post(self, request, format=None):
request_data = request.POST
response = self.get_customer_id(request_data)
return JSONResponse(response)
def get_customer_id(self, data):
kwargs = {}
for f in self.required_fields:
if f in data:
kwargs[f] = data[f]
else:
return None
try:
customer = Customer.objects.get(**kwargs)
except Customer.DoesNotExist:
customer = Customer(**kwargs)
customer.save()
if customer.id is not None:
response = {'customer_id': customer.id}
else:
response = {
'error': 'Please provide a first name, surname and email address'
}
return response
The JSONResponse referenced here is the one from the rest framework docs.
for classbased views in django-rest-framework this is usually done by:
a GET request to the resource will retrieve records
a POST request to the same resource will create a record
I believe there are a couple examples of this in the django rest framework documentation