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
Related
I have been using Django for some time, but running into some issues with trying something new.
I have built API's with Django-Rest-Framework using Class Based Views, and I have also built API's using Function based views returning a JsonResponse
Now what I am tasked to do is use CBV's to return a JsonResponse without using DRF. I am trying to produce a simple get request
class BusinessDetailView(DetailView):
model = BusinessDetail
def get_queryset(self):
business = BusinessDetail.objects.get(id=self.kwargs.get('pk'))
return JsonResponse({'business': list(business)})
Using the models pk I keep running into issues with this simple request. I am getting a TypeError 'BusinessDetail' object is not iterable and if I make some small changes and override get_object I'll get the same error, or I'll even get a 'BusinessDetail' object is not callable
Does anybody have any tips with using CBVs to return Json, without using DRF?
Thank you all in advance!
i would try something like this:
class BusinessDetailView(DetailView):
model = BusinessDetail
def get_queryset(self):
business = BusinessDetail.objects.get(id=self.kwargs.get('pk'))
return business
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
data = serializers.serialize("json", queryset)
return JsonResponse(data, status=200, safe=False)
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)
I'm using django-rest-framework.
All models in my app contain User field and I want to write to this field link to current user.
How can I pass user object to model?
I've tired to write User link in SerializerClass, but I think it's not the best solution.
In view:
def perform_create(self, serializer):
serializer.save(created_by=self.user)
Model:
class Tracker(models.Model):
serial_id = models.PositiveIntegerField(unique=True)
created_by = models.ForeignKey(MyUser)
You dont have to. DRF can do that for you.
As here.
Since you haven't shared your complete code I am inclined to share a VERY SIMPLE implementation without writing a lot of code yourself and relying on DRF's ability is:
from rest_framework import generics
class MyView(generics.CreateAPIView):
queryset = Tracker.objects
serializer_class = TrackerSerializer #Assuming that this is your serializer
def post(self , request , *args , **kwargs):
return self.create(request , *args , **kwargs)
DRF will itself take care of the relationships. This code works assuming that you have authenticaton set up and thus request.user is not an Anonymous User.
To grab the logged in user from a generic view, you should use self.request.user:
In view:
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
I implemented a basic rest api with the django rest framework. It works perfectly using the browsable api or communicating to it with requests. Next step would be submitting data to the rest api.
Here is what I have done so far.
settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.AllowAny',),
'PAGINATE_BY': 10
}
[UPDATE:]
models.py
class Request(models.Model):
name = models.TextField()
def save(self, *args, **kwargs):
super(Request, self).save(*args, **kwargs) # Call the "real" save() method.
serializers.py
class RequestSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Request
fields = ('id', 'name')
views.api
class RequestsViewSet(viewsets.ModelViewSet):
queryset = Request.objects.all()
serializer_class = RequestSerializer
Using the browsable api I see that those are the options supported:
Allow: GET, HEAD, OPTIONS
Obviously, POST (and also PUT) is missing.
What I am doing wrong?
Thanks!
Solved it by adding the post method to the modelviewset (in the view):
def post(self, request, format=None):
...
Thanks for helping!
Well, I think you only need to call save method on the model object to persist the object in the database.
First, import model to the view, instantiate a model object in the view, then call save method on the newly created object. If you have model connected to the backend, that will persist your changes.
models.py
class YourModel(models.Model):
name = models.CharField()
views.py
from models import YourModel
def yourView(request):
yourObject = YourModel(name='John')
yourObject.save()
...
Check also Django documentation for models here
Essentially, I'm trying to find a good way to attach more views to a Router without creating a custom Router. What's a good way to accomplish this?
Here is something sort of equivalent to what I'm trying to accomplish. Variable names have been changed and the example method I want to introduce is extremely simplified for the sake of this question.
Router:
router = routers.SimpleRouter(trailing_slash=False)
router.register(r'myobjects', MyObjectViewSet, base_name='myobjects')
urlpatterns = router.urls
ViewSet
class MyObjectsViewSet(viewsets.ViewSet):
""" Provides API Methods to manage MyObjects. """
def list(self, request):
""" Returns a list of MyObjects. """
data = get_list_of_myobjects()
return Response(data)
def retrieve(self, request, pk):
""" Returns a single MyObject. """
data = fetch_my_object(pk)
return Response(data)
def destroy(self, request, pk):
""" Deletes a single MyObject. """
fetch_my_object_and_delete(pk)
return Response()
One example of another method type I need to include. (There are many of these):
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
The end-result is that the following URL would work correctly and be implemented 'cleanly'.
GET example.com/myobjects/123/locations
The answer given by mariodev above is correct, as long as you're only looking to make GET requests.
If you want to POST to a function you're appending to a ViewSet, you need to use the action decorator:
from rest_framework.decorators import action, link
from rest_framework.response import Response
class MyObjectsViewSet(viewsets.ViewSet):
# For GET Requests
#link()
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
# For POST Requests
#action()
def update_location(self, request, pk):
""" Updates the object identified by the pk """
location = self.get_object()
location.field = update_location_field() # your custom code
location.save()
# ...create a serializer and return with updated data...
Then you would POST to a URL formatted like:
/myobjects/123/update_location/
http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing has more information if you're interested!
You can now do this with the list_route and detail_route decorators: http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
For example:
from rest_framework.decorators import list_route
from rest_framework.response import Response
...
class MyObjectsViewSet(viewsets.ViewSet):
...
#list_route()
def locations(self, request):
queryset = get_locations()
serializer = LocationSerializer(queryset, many=True)
return Response(serializer.data)
You define method like you do now, but you need to use the same url as method name and add link decorator, so for
/myobjects/123/locations/
You add method like this
#link(permission_classes=[...])
def locations(self, request, pk=None):
...
and router will pick it automatically.
From Routing to extra methods on a ViewSet:
I think you may need to route the method by hand, i.e. The Old-Fashioned Way™.
First pull the method out as a separate view:
set_password_view = UserViewSet.as_view({'post': 'set_password'})
(or such)
Then assign your URL:
url(r'^users/username_available/$', set_password_view, name-=...)
(Or such)
There's a related question on SO.
If you want to extend a viewset with a view that is or should not directly be written inside your viewset, you can write a “wrapper” action to pass the data through.
For example, with class based views:
from somewhere import YourExternalClassView
class SomeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
#action(detail=True)
def your_action(self, request, pk):
return YourExternalClassView.as_view()(request, pk=pk)
How does it work?
On class based views, the as_view method returns a view function, to which we will pass the data we received from the action. The view will then hand over to process further.
For non-class based view, the views can be called/wrapped directly without .as_view(...)(...).