update a model in the view of a django restframework view - django

I can't find examples of using patch to update a partial view in rest framework and it isn't computing for me. Here is my code:
class ArworkIsSold(generics.RetrieveUpdateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = ArtworkSerializer
queryset = Artwork.objects.all()
def partial_update(self, request, pk=None):
data = {sold:True,forSale:False}
serializer = ArtworkSerializer(context={'request': request},data=data, partial=True)
serializer.is_valid()
serializer.save()
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
However, it doesn't update and I get this error:
NameError: name 'sold' is not defined
My model does have sold and I am trying to just set the data in the view instead of sending it in from the ajax request. I just want to hit a view and have it update two fields.

You can't use undefined variable as a dictionary key. Use strings as keys and then pass dictionary as "data" parameter:
class ArworkIsSold(generics.RetrieveUpdateAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = ArtworkSerializer
queryset = Artwork.objects.all()
def partial_update(self, request, pk=None):
data = {'sold':True, 'forSale':False}
serializer = ArtworkSerializer(context={'request': request},data=data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

Related

How to get lookup_url_kwarg in serializer of django rest framework

this my Django view
class CreateForeignTableView(CreateAPIView):
"""
create foreign_table finally not difference a normal table ??
"""
serializer_class = CreateForiegnTableSerializer
queryset = None
lookup_url_kwarg = 'foreign_server_id'
I want get lookup_url_kwarg in my create serializer function
Simple you can override create method to achieve this.
def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={
'request': request,
'id': self.kwargs.get(self.lookup_url_kwarg)})
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
In the serializer, you can use id = self.context['id']
You can override get_serializer_context method to achieve this.
class CreateForeignTableView(CreateAPIView):
"""
create foreign_table finally not difference a normal table ??
"""
serializer_class = CreateForiegnTableSerializer
queryset = None
lookup_url_kwarg = 'foreign_server_id'
def get_serializer_context(self):
context = super(CreateForeignTableView, self).get_serializer_context()
context.update({
"foreign_server_id": self.kwargs.get(self.lookup_url_kwarg)
})
return context
In the serializer you can use self.context.get("foreign_server_id", "") to get foreign_server_id.
You can follow this post to know farther.
You only need to access the serializer context. GenericApiView sets the view itself into the serializer context, so you may access lookup_url_kwarg like this:
def create(self, validated_data):
my_url_kwarg = self.context['view'].lookup_url_kwarg

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)

Django rest framework - self.context doesn't have request attribute

class MyModelSerializer(serializers.ModelSerializer):
field1 = serializers.CharField()
field2 = serializers.SerializerMethodField('get_awesome_user')
def get_current_user(self):
request = self.context.get("request")
if request and hasattr(request, "user"):
return request.user
return None
def get_awesome_user(self, obj):
user = self.get_current_user()
## use this user object, do some stuff and return the value
return ...
My api(which uses authentication_classes and permission_classes) is using this serializer and the get_current_user function always returns None. when I debug it, I found that self.context is empty dictionary, i.e {}. to be double sure I also printed self.context.keys(), still it's empty list.
I followed this thread.
Get current user in Model Serializer
PS: I'm using djangorestframework==3.3.3, Django==1.9.1
EDIT: adding viewset code
class MyModelViewSet(viewsets.ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication, TokenAuthentication)
permission_classes = (IsAuthenticated,)
def list(self, *args, **kwargs):
queryset = MyModel.objects.all()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = MyModelSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = MyModelSerializer(queryset, many=True)
return Response(serializer.data)
How do you create serializer in your viewset's list() method? You should call
serializer = self.get_serializer(data=request.data)
to get your serializer context filled automatically as it is done in default implementation of this method in DRF mixins., but I have a feeling that you're just creating it manually, like this:
serializer = MyModelSerializer(instance)
So, to fix this, you should either call get_serializer(), or pass extra context argument to serializer constructor:
serializer = MyModelSerializer(instance, context={'request': request, ...})

Django Rest Framework - modify serializer.data

I have a view where I'm doing the following -
def retrieve(self, request, pk=None):
queryset = MyClass.objects.all()
class_data = get_object_or_404(queryset, pk=pk)
serializer = self.get_serializer(class_data)
new_data = serializer.data.copy()
new_data['my_field'] = 'updated info!'
serializer = self.get_serializer(data=new_data)
serializer.is_valid()
return Response(serializer.data)
I'd like to not have to make a copy of the serializer data to update the info. Is there a way to modify a field in a serializer before display through the view?
edit -
serializer.data['my_field'] = 'updated info!'
does not work unless I make a copy.
Well the straightforward solution is just to set the retrieved object attribute (class_data.my_field = 'updated info!').
In my case, I need to update the serializer.data with some exta dict.
I solved in the following way, Merged the ordered dicts
serializer.data[0] and the extra dict.
from itertools import chain
from collections import OrderedDict
class MyCreationApiView(generics.CreateAPIView):
def create(self, request, *args, **kwargs):
data = ...
serializer = self.get_serializer(data=data, many=True, required=True,
context={'request': self.request, 'search': search})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
extra_dict = {'test': []}
return Response([OrderedDict(chain(serializer.data[0].items(), extra_dict.items()))], status=status.HTTP_201_CREATED).
ref:

How do I create multiple model instances with Django Rest Framework?

I would like to save and update multiple instances using the Django Rest Framework with one API call. For example, let's say I have a "Classroom" model that can have multiple "Teachers". If I wanted to create multiple teachers and later update all of their classroom numbers how would I do that? Do I have to make an API call for each teacher?
I know currently we can't save nested models, but I would like to know if we can save it at the teacher level.
Thanks!
I know this was asked a while ago now but I found it whilst trying to figure this out myself.
It turns out if you pass many=True when instantiating the serializer class for a model, it can then accept multiple objects.
This is mentioned here in the django rest framework docs
For my case, my view looked like this:
class ThingViewSet(viewsets.ModelViewSet):
"""This view provides list, detail, create, retrieve, update
and destroy actions for Things."""
model = Thing
serializer_class = ThingSerializer
I didn't really want to go writing a load of boilerplate just to have direct control over the instantiation of the serializer and pass many=True, so in my serializer class I override the __init__ instead:
class ThingSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(ThingSerializer, self).__init__(many=many, *args, **kwargs)
class Meta:
model = Thing
fields = ('loads', 'of', 'fields', )
Posting data to the list URL for this view in the format:
[
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}
]
Created two resources with those details. Which was nice.
I came to a similar conclusion as Daniel Albarral, but here's a more succinct solution:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
Here's another solution, you don't need to override your serializers __init__ method. Just override your view's (ModelViewSet) 'create' method. Notice many=isinstance(request.data,list). Here many=True when you send an array of objects to create, and False when you send just the one. This way, you can save both an item and a list!
from rest_framework import status, viewsets
from rest_framework.response import Response
class ThingViewSet(viewsets.ModelViewSet):
"""This view snippet provides both list and item create functionality."""
#I took the liberty to change the model to queryset
queryset = Thing.objects.all()
serializer_class = ThingSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data,list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
I couldn't quite figure out getting the request.DATA to convert from a dictionary to an array - which was a limit on my ability to Tom Manterfield's solution to work. Here is my solution:
class ThingSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(ThingSerializer, self).__init__(many=many, *args, **kwargs)
class Meta:
model = Thing
fields = ('loads', 'of', 'fields', )
class ThingViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ):
queryset = myModels\
.Thing\
.objects\
.all()
serializer_class = ThingSerializer
def create(self, request, *args, **kwargs):
self.user = request.user
listOfThings = request.DATA['things']
serializer = self.get_serializer(data=listOfThings, files=request.FILES, many=True)
if serializer.is_valid():
serializer.save()
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And then I run the equivalent of this on the client:
var things = {
"things":[
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}]
}
thingClientResource.post(things)
I think the best approach to respect the proposed architecture of the framework will be to create a mixin like this:
class CreateListModelMixin(object):
def create(self, request, *args, **kwargs):
"""
Create a list of model instances if a list is provided or a
single model instance otherwise.
"""
data = request.data
if isinstance(data, list):
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
Then you can override the CreateModelMixin of ModelViewSet like this:
class <MyModel>ViewSet(CreateListModelMixin, viewsets.ModelViewSet):
...
...
Now in the client you can work like this:
var things = [
{'loads':'foo','of':'bar','fields':'buzz'},
{'loads':'fizz','of':'bazz','fields':'errrrm'}
]
thingClientResource.post(things)
or
var thing = {
'loads':'foo','of':'bar','fields':'buzz'
}
thingClientResource.post(thing)
EDIT:
As Roger Collins suggests in his response is more clever to overwrite the get_serializer method than the 'create'.
You can simply overwrite the get_serializer method in your APIView and pass many=True into get_serializer of the base view like so:
class SomeAPIView(CreateAPIView):
queryset = SomeModel.objects.all()
serializer_class = SomeSerializer
def get_serializer(self, instance=None, data=None, many=False, partial=False):
return super(SomeAPIView, self).get_serializer(instance=instance, data=data, many=True, partial=partial)
I came up with simple example in post
Serializers.py
from rest_framework import serializers
from movie.models import Movie
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = [
'popularity',
'director',
'genre',
'imdb_score',
'name',
]
Views.py
from rest_framework.response import Response
from rest_framework import generics
from .serializers import MovieSerializer
from movie.models import Movie
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
class MovieList(generics.ListCreateAPIView):
queryset = Movie.objects.all().order_by('-id')[:10]
serializer_class = MovieSerializer
permission_classes = (IsAuthenticated,)
def list(self, request):
queryset = self.get_queryset()
serializer = MovieSerializer(queryset, many=True)
return Response(serializer.data)
def post(self, request, format=None):
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
These line are the actual logic of Multiple Instance -
data = request.data
if isinstance(data, list): # <- is the main logic
serializer = self.get_serializer(data=request.data, many=True)
else:
serializer = self.get_serializer(data=request.data)
If you are confused with many=True, see this
When we send data it will be inside list somewhat like this -
[
{
"popularity": 84.0,
"director": "Stanley Kubrick",
"genre": [
1,
6,
10
],
"imdb_score": 8.4,
"name": "2001 : A Space Odyssey"
},
{
"popularity": 84.0,
"director": "Stanley Kubrick",
"genre": [
1,
6,
10
],
"imdb_score": 8.4,
"name": "2001 : A Space Odyssey"
}
]
The Generic Views page in Django REST Framework's documentation states that the ListCreateAPIView generic view is "used for read-write endpoints to represent a collection of model instances".
That's where I would start looking (and I'm going to actually, since we'll need this functionality in our project soon as well).
Note also that the examples on the Generic Views page happen to use ListCreateAPIView.
Most straightforward method I've come across:
def post(self, request, *args, **kwargs):
serializer = ThatSerializer(data=request.data, many=isinstance(request.data, list))
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)