User creation with unique constraint in Django Rest framework - django

I researched about it in Google, and have tried in lot of ways but still not able to get it right. Here are the requirements:
A one to one field for extending User model, so this has been achieved and the new model is called Customer.
Now, with new user 201 response is returned BUT not all the data is serialized, only date_of_birth is coming in json format, I want even the User to be in that json response.
If I try to add a user with a username which already exists it plays up really bad. I am trying with Try and Except but it really doesnt work. I want a response of 409 conflict to be sent if the username already exists.
Here is UserSerializer.py:
-
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
new_username = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('url', 'pk', 'username', 'email', 'is_staff', 'new_username')
extra_kwargs = {
'username': {'validators': []},
}
def get_new_username(self, obj):
return obj.username
Here is Customer model:
from django.db import models
from django.contrib.auth.models import User
class Customer(models.Model):
user = models.OneToOneField(User, related_name="customer", on_delete=models.CASCADE)
date_of_birth = models.DateField(max_length=8)
def __unicode__(self):
return u'%s' % self.user.username
Here is CustomerSerializer class:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from rest_framework import serializers, status
from rest_framework.response import Response
from customers.models import Customer
from api.serializers import UserSerializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Customer
fields = ('url', 'date_of_birth', 'user')
def create(self, validated_data):
print "coming inside serializer create"
user_data = validated_data.pop("user")
print user_data
try:
userinstance = User.objects.get_or_create(**user_data)[0]
print "user..."
print userinstance
print validated_data
customer = Customer.objects.create(user=userinstance, **validated_data)
print customer.user
return customer
except Exception as exception:
print exception
# print "customer --> %s " % customer
return customer
def update(self, instance, validated_data):
print "coming inside update"
user_data = validated_data.pop("user")
username = user_data.pop('username')
user = get_user_model().objects.get_or_create(username=username)[0]
user.username = username
user.email = user_data.get('email', user.email)
user.save()
# instance.user = user
instance.date_of_birth = validated_data.get('date_of_birth', instance.date_of_birth)
instance.save()
And here is view set for Customer:
from rest_framework import viewsets
from customers.models import Customer
from customers.serializers import CustomerSerializer
from api.permissions import IsOwnerOrAdmin
from rest_framework import authentication, permissions, status
from rest_framework.response import Response
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication,
authentication.SessionAuthentication, )
def get_permissions(self):
if self.action == 'list':
self.permission_classes = (permissions.IsAdminUser,)
elif self.action == 'create':
self.permission_classes = (permissions.AllowAny,)
return super(self.__class__, self).get_permissions()
def create(self, request, *args, **kwargs):
print "This is view create -----------------------------"
serializer = self.get_serializer(data=request.data)
# print serializer
if serializer.is_valid(): # It passes because here there are no new objects created yet
print "serializer is valid ......"
# self.pre_save(serializer.object)
# user_data = serializer.validated_data.get("user")
# print user_data
self.object = serializer.create(serializer.validated_data) # It creates the User (triggering the signal) instance and then when saving UserProfile, it give the integrity error
# self.post_save(self.object, created=True)
# headers = self.get_success_headers(serializer.data)
print 'coming here ....1'
print self.object
return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
print 'coming here..'
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
So, basically I would like to create new customers with all data returned as response and status 201 and if username already exists, then 409 or status code which I define and the some data which DRF should not complaint about i.e; right now it says that OrderDict does not contain PK if I modify the serializer.
Thanks
Edit 1
Here is updated serilizer with custom exception:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.db import IntegrityError
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from customers.models import Customer
from api.serializers import UserSerializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Customer
fields = ('url', 'pk', 'date_of_birth', 'user')
def create(self, validated_data):
print "coming inside serializer create"
user_data = validated_data.pop("user")
print user_data
try:
userinstance = User.objects.create_user(**user_data)
print "user..."
print userinstance
print validated_data
customer = Customer.objects.create(user=userinstance, **validated_data)
print customer.user
return customer
# except TypeError as exception:
# print exception
# # print "customer --> %s " % customer
# raise TypeError(exception)
except IntegrityError as exception:
raise Custom409(exception)
def update(self, instance, validated_data):
print "coming inside update"
user_data = validated_data.pop("user")
username = user_data.pop('username')
user = get_user_model().objects.get_or_create(username=username)[0]
user.username = username
user.email = user_data.get('email', user.email)
user.save()
# instance.user = user
instance.date_of_birth = validated_data.get('date_of_birth', instance.date_of_birth)
instance.save()
return instance
class Custom409(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = "User already there."
But still get :
Traceback (most recent call last):
File "/home/naveen/projects/gratis/customers/tests.py", line 37, in test_if_anyone_could_create_customers
format='json')
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 299, in post
path, data=data, format=format, content_type=content_type, **extra)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 221, in post
return self.generic('POST', path, data, content_type, **extra)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/test/client.py", line 379, in generic
return self.request(**r)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 288, in request
return super(APIClient, self).request(**kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 240, in request
request = super(APIRequestFactory, self).request(**kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/test/client.py", line 466, in request
six.reraise(*exc_info)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/viewsets.py", line 83, in view
return self.dispatch(request, *args, **kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/views.py", line 477, in dispatch
response = self.handle_exception(exc)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/views.py", line 437, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/views.py", line 448, in raise_uncaught_exception
raise exc
IntegrityError: duplicate key value violates unique constraint "auth_user_username_key"
DETAIL: Key (username)=(user2) already exists.
And also the test case is as follows:
def test_if_anyone_could_create_customers(self):
create_user = self.client.post('/api/customers/',
{'user':{'username': 'user2', 'email': 'user2#gmail.com'}, 'date_of_birth':"1982-10-20"},
format='json')
print create_user
self.assertEqual(create_user.status_code, 201)
create_user = self.client.post('/api/customers/',
{'user': {'username': 'user2', 'email': 'user2#gmail.com'},'date_of_birth': "1982-10-20"},
format='json')
print create_user
# no duplicates
user = User.objects.all()
print user
self.assertEqual(create_user.status_code, 409)

This is because you are catching exceptions far too broadly in your create method. In fact you should never do this (regardless of whether you use DRF or not)
except Exception as exception:
print exception
# print "customer --> %s " % customer
return customer
You should only catch the specific exceptions that you need to catch. And you shouldn't be returning a customer at all. In this case, examination of ModelSerializer tells us that the only one you really should catch is TypeError. Even that is raised again for the caller to handler.
except TypeError as exception:
print exception
# print "customer --> %s " % customer
raise TypeError(exception)
Now you should get what you want but according to your comments, you are not so try raising a custom error.
from rest_framework.exceptions import APIException
from django.utils.encoding import force_text
class Custom409(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = 'A conflict occurred'
And then
except IntergrityError as ext:
print exception
# print "customer --> %s " % customer
raise Custom409(ext)

The your code is raising IntegrityErorr because you are violated unique constraint while creating profile.
You should use get_or_create while creating user profile/customer instance. Something like this should work.
# CustomerSerializer
def create(self, validated_data):
"""...snip..."""
try:
userinstance = User.objects.get_or_create(**user_data)[0]
customer = Customer.objects.get_or_create(
user=userinstance, defaults=validated_data)[0]
return customer
except Exception as exception:
# custom validationerror
raise ConflictError({"user": "User already exists"})

Maybe you should try to catch it in your view directly:
views.py
from rest_framework import viewsets
from customers.models import Customer
from customers.serializers import CustomerSerializer
from api.permissions import IsOwnerOrAdmin
from rest_framework import authentication, permissions, status
from rest_framework.response import Response
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication,
authentication.SessionAuthentication, )
def get_permissions(self):
if self.action == 'list':
self.permission_classes = (permissions.IsAdminUser,)
elif self.action == 'create':
self.permission_classes = (permissions.AllowAny,)
return super(self.__class__, self).get_permissions()
def create(self, request, *args, **kwargs):
print "This is view create -----------------------------"
serializer = self.get_serializer(data=request.data)
# print serializer
try:
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)
except ValidationError as e:
if e.detail.get('username') == ['user with this username already exists.']:
raise Custom409()
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And removing the logic you put in the serializer create. DRF exceptions are meant to be used in views not serializer.

Related

a music app ,where i was trying to display the album detail once it is newlycreated using reverse...recieving the below mentioned error please assist

Traceback (most recent call last):
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\base.py", line 103, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\base.py", line 142, in dispatch
return handler(request, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 184, in post
return super().post(request, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 153, in post
return self.form_valid(form)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 136, in form_valid
return super().form_valid(form)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 65, in form_valid
return HttpResponseRedirect(self.get_success_url())
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 125, in get_success_url
url = self.object.get_absolute_url()
File "C:\Users\Sanath\Desktop\website\music\models.py", line 12, in get_absolute_url
return reverse('music:detail',kwargs = {'pk' : self.pk})
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\urls\base.py", line 88, in reverse
return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\urls\resolvers.py", line 828, in _reverse_with_prefix
raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'detail' with keyword arguments '{'pk': 11}' not found. 1 pattern(s) tried: ['music/(?P<album_id>[0-9]+)/\\Z']
urls.py
from django.urls import path
from . import views
app_name = 'music'
urlpatterns = [
#music/
path('', views.index,name = 'index'),
#music/71/
path('<int:album_id>/', views.detail,name = 'detail'),
path("register/",views.UserFormView.as_view(),name = 'register'),
path("album/add/",views.albumcreate.as_view(),name = 'album-add'),
#url pattern for the view albumcreate
]
models.py
from django.db import models
from django.urls import reverse
# Create your models here.
class album(models.Model): #inherit from models.Model
artist = models.CharField(max_length=250) #when migrated to db it will be a column with the same name automatically(here it is variables
#charfield ,foreignkey is datatype
album_title = models.CharField(max_length=500)
genre = models.CharField(max_length=100)
album_logo = models.CharField(max_length=1000)
def get_absolute_url(self):
return reverse('music:detail',kwargs = {'pk' : self.pk})
#returns the detail page of the album we just created
def __str__(self):
return self.album_title+'-'+self.artist#string representation of obj
class songs(models.Model):
#songs needs to be part of an album
album = models.ForeignKey(album,on_delete=models.CASCADE)
#bts another column with unique id 1st album id 1 2nd album id 2 and so on
#let pk of red = 1 then a song of that album will have fk as 1 so that they are linked
#when ever we delete the ablum red all the songs in the album should be deleted hence delete
file_type= models.CharField(max_length=10) #mp3etc
song_title = models.CharField(max_length=250)
def __str__(self):
return self.song_title
views.py
from django.http import HttpResponse
from .models import album
from django.shortcuts import render,redirect
from django.contrib.auth import login,logout,authenticate
from django.http import Http404
from django.views.generic import View
from .forms import UserForm
from django.views.generic.edit import CreateView,UpdateView,DeleteView # form to create new ,deleting,updating obj
def index(request):
all_albums = album.objects.all()
'''
html =''
for album1 in all_albums:
path='/music/' + str(album1.id) + '/'
html += '' + album1.album_title + '<br>'
'''
# template = loader.get_template('music/index.html')
context = { 'all_albums': all_albums,
}
#return HttpResponse(template.render(context,request))
#same thing as i did in alb1 code excpet i have done it though html file to prevent making a mess here
return render(request,'music/index.html',context)
#same as above but using render shortcut which internally creates http response
def detail(request, album_id):
try:
alb = album.objects.get(pk=album_id)
except album.DoesNotExist:
raise Http404("album does not exist")
return render(request,'music/detail.html',{ 'alb': alb})
#not using context because we make use of only one album at one time and there ia no iterating over
class UserFormView(View):
form_class = UserForm
template_name = 'music/reg_form.html'
def get(self,request):
# when ever user requests from this form this fn is called
#display a blank form
form = self.form_class(None) # nothing is present we have to input it
return render(request,self.template_name,{form:form})
#self. teemp name = where u want to return ,what html#
def post(self,request):
# when ever user submits the above form this fn is called
form = self.form_class(request.POST) # what ever is entered in get is posted
if form.is_valid():
user= form.save(commit=False) #storing itlocally not yet commited to db
#cleaned normalized data(so that everyone uses the gemeral format and can be entered into the db properly
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user.set_password(password) #method to set password we cannot do like user.password = 'raj' or = password
user.save()
# return user obj id credentials are correct
user = authenticate(username=username,password = password)
#what the above fun does it that it takes the username and pwd and chcecks with the db is they are an actual user
if user is not None: #for confirming above fn
if user.is_active:
login(request,user)
#logged in to the website
#request.user.username
return redirect('music:index')
return render(request,self.template_name,{form:form})
#new classsthat creates the form view for get abs url fn
class albumcreate( CreateView ): #using models and not by admin or shell queries but in the webpage itself
model = album
fields = ['artist','album_title','genre','album_logo']
#we havent specified template name cuz _form is the default file it is going to look at
expected result was to open up and show the details of the album i created
.............................................................................................................................................................................................................................................................................................................................................................................................................................................
The problem is in here:
def get_absolute_url(self):
return reverse('music:detail',kwargs = {'pk' : self.pk})
^^^^^^^^^^^^^^^^
In the urls, you are passing album_id, whereas here you are passing pk. Hence changing it to àlbum_id should fix it:
def get_absolute_url(self):
return reverse('music:detail',kwargs = {'album_id' : self.pk})

Django Rest Framework is throwing 400 before I can handle exception for ModelViewSet

I have a user model in my app with a unique field on email. However, I need to catch when a user is trying to do a duplicate request so I can do some processing in a different way.
I am making a POST call to create this user.
DRF of course throws a 400 with an existing email message.
But even when I create a validate_email method in my model or try to catch it with a custom exception it doesn't catch.
I created a custom ValidationError cause the general ValidationError didn't seem to have a way to filter it out specifically besides a generic code, unique, or matching the message string.
How can I catch this specific validation in Django Rest Framework?
Error Message:
{
"email": [
"user with this email address already exists."
],
"status_code": 400
}
User model:
class User(AbstractBaseUser, PermissionsMixin):
"""
User Model
"""
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
email = models.EmailField(_("email address"), unique=True)
name = models.CharField(_("name"), max_length=30, blank=True)
def validate_email(self):
email = self.email
valid_email = email is not None and email.endswith(self.email_domain)
if not valid_email:
raise ValidationError(
_("Email is not valid"),
code="invalid.email",
)
if Users.objects.filter(email=email).exists():
import ipdb; ipdb.sset_trace() #does not get called
raise EmailExists()
UserViewSet code:
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def get_serializer_class(self):
if self.action == 'list':
return UserSerializer
else:
return UserDetailSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except EmailExists:
logger.debug('caught the existing email')
import ipdb; ipdb.sset_trace()
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
custom exception:
from rest_framework.exceptions import ValidationError
class EmailExists(ValidationError):
"""Raised when user exists"""
pass
You need to capture a ValidationError exception instead. The reason behind this is because you've defined your email field unique=True and Django will automatically check that when you call serializer.is_valid().
In addition, you can check for unique constraint error by using exc. get_full_details()
try:
serializer.is_valid(raise_exception=True)
except ValidationError as exc:
errors = exc.get_full_details()
if 'email' in errors and errors['email']['code'] == 'unique':
# do something, maybe re-raise with your custom exception:
raise EmailExists from exc
Ref
Exceptions
Unique constraint error code

Django 'ModelBase' object is not iterable TypeError

Getting the below from a unit test in Django. Can't seem to figure out the reason this is happening.
My migrations look identical to the other migrations I have made.
Also, I was able to run this on my local machine in the admin section, so my other question is this big deal if it is happening during the test if it can be pushed live. Trying to follow best practices here, so appreciate others' opinions on that.
models.py
class LeadComplete(models.Model):
"""Complete lead info"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.DO_NOTHING,
)
title=models.CharField(max_length=255)
time_minutes = models.IntegerField()
score = models.DecimalField(max_digits=5, decimal_places=2)
link = models.CharField(max_length=255, blank=True)
client = models.ManyToManyField('Client')
def __str__(self):
return self.title
serializers.py
class LeadCompleteSerializer(serializers.ModelSerializer):
"""Serialize a leadComplete"""
client = serializers.PrimaryKeyRelatedField(
many=True,
queryset=Client.objects.all()
)
class Meta:
model = LeadComplete
fields = [
'id', 'title', 'time_minutes', 'score', 'link', 'client',
]
read_only_fields = ('id',)
views.py
class LeadCompleteViewSet(viewsets.ModelViewSet):
"""Manage LeadComplete in the database"""
serializer_class = serializers.LeadCompleteSerializer
queryset = LeadComplete.objects.all()
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_queryset(self):
"""Retrieve the leadcomplete for the authed user"""
return self.queryset.filter(user=self.request.user)
test_lead_complete_api.py
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from core. models import LeadComplete
from leads.serializers import LeadCompleteSerializer
LEADCOMPLETE_URL = reverse('leads:leadcomplete-list')
def sample_leadComplete(user, **params):
"""Create and return a sample leadComplete"""
defaults = {
'title': 'Samle Company',
'time_minutes': 10,
'score': 5.00,
}
defaults.update(params)
return LeadComplete.objects.create(user=user, **defaults)
def PublicLeadCompleteApiTests(TestCase):
"""Test unauthenticated LeacComplete access"""
def setUp(self):
self.client = APIClient()
def test_auth_required(self):
""""Test that authentication is required"""
res = selft.client.get(LEADCOMPLETE_URL)
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
class PrivateLeadCompleteApiTests(TestCase):
""""Test authenticated recipe API access"""
def setUp(self):
self.client = APIClient()
self.user = get_user_model().objects.create_user(
'test#unionresolute.com',
'testpass'
)
self.client.force_authenticate(self.user)
def test_retrieve_leadComplete(self):
"""Test retrieving LeadComplete list"""
sample_leadComplete(user=self.user)
sample_leadComplete(user=self.user)
res = self.client.get(LEADCOMPLETE_URL)
leadComplete = LeadComplete.objects.all().order_by('-id')
serializer = LeadCompleteSerializer(LeadComplete, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
def test_leadComplete_limited_to_user(self):
"""Test retrieving leadComplete for user"""
user2 = get_user_model().objects.create_user(
'other#unionresolute.com',
'password123'
)
sample_leadComplete(user=user2)
sample_leadComplete(user=self.user)
res = self.client.get(LEADCOMPLETE_URL)
leadComplete = LeadComplete.objects.filter(user=self.user)
serializer = LeadCompleteSerializer(leadComplete, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
error:
ERROR: test_retrieve_leadComplete (leads.tests.test_leadcomplete_api.PrivateLeadCompleteApiTests)
Test retrieving LeadComplete list
----------------------------------------------------------------------
Traceback (most recent call last):
File "/app/leads/tests/test_leadcomplete_api.py", line 58, in test_retrieve_leadComplete
self.assertEqual(res.data, serializer.data)
File "/usr/local/lib/python3.7/site-packages/rest_framework/serializers.py", line 768, in data
ret = super(ListSerializer, self).data
File "/usr/local/lib/python3.7/site-packages/rest_framework/serializers.py", line 262, in data
self._data = self.to_representation(self.instance)
File "/usr/local/lib/python3.7/site-packages/rest_framework/serializers.py", line 686, in to_representation
self.child.to_representation(item) for item in iterable
TypeError: 'ModelBase' object is not iterable
The test client returns data in the content property.
Try res.content instead of res.data
def test_retrieve_leadComplete(self):
"""Test retrieving LeadComplete list"""
sample_leadComplete(user=self.user)
sample_leadComplete(user=self.user)
res = self.client.get(LEADCOMPLETE_URL)
leadComplete = LeadComplete.objects.all().order_by('-id')
serializer = LeadCompleteSerializer(LeadComplete, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.content, serializer.data)

How can I specify the parameter for POST requests while using APIView with django-rest-swagger

In the latest version on Django REST Swagger (2.1.0) YAML docstrings have been deprecated. I cannot get swagger to show the POST request parameters.
Here is my view
class UserAuthenticationView(APIView):
def post(self, request, *args, **kwargs):
serializer = UserAuthenticationSerializer(data=self.request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
Here is my Serializer
class UserAuthenticationSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if username and password:
user = authenticate(username=username, password=password)
if user:
if not user.is_active:
msg = 'User account is disabled.'
raise serializers.ValidationError(msg, code='authorization')
else:
msg = 'Unable to log in with provided credentials.'
raise serializers.ValidationError(msg, code='authorization')
else:
msg = 'Must include "username" and "password".'
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
This is what I get in my generated
I do not get a form with the fields for the POST data. How do I get that?
django-rest-swagger uses rest_framework.schemas.SchemaGenerator to generate the schema and SchemaGenerator uses get_serializer_fields to get the serializer information of a view. get_serializer_fields checks if a view has a get_serializer method to generate the form. GenericAPIView provides the get_serializer so inheriting from it is enough.
Inherit view from GenericAPIView rather than simple APIView. And add serializer_class attribute with appropriate serializer
from rest_framework.generics import GenericAPIView
class UserAuthenticationView(GenericAPIView):
serializer_class = UserAuthenticationSerializer
def post(self, request, *args, **kwargs):
serializer = UserAuthenticationSerializer(data=self.request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
This is the rest framework get schema code (the part of it):
def get_serializer_fields(self, path, method, view):
"""
Return a list of `coreapi.Field` instances corresponding to any
request body input, as determined by the serializer class.
"""
if method not in ('PUT', 'PATCH', 'POST'):
return []
if not hasattr(view, 'get_serializer'):
return []
serializer = view.get_serializer()
if isinstance(serializer, serializers.ListSerializer):
return [
coreapi.Field(
name='data',
location='body',
required=True,
type='array'
)
]
...
As you can see - it should work if you define the get_serializer method on your view - which returns the UserAuthenticationSerializer.
-- EDIT --
Forget: Happy Coding.
A working example for a custom ViewSet, with django-rest-swagger==2.2.0:
from rest_framework import viewsets
from rest_framework.schemas import AutoSchema
from rest_framework.compat import coreapi, coreschema
from rest_framework.decorators import action
class DeviceViewSchema(AutoSchema):
"""
Schema customizations for DeviceViewSet
"""
def get_manual_fields(self, path, method):
extra_fields = []
if path.endswith('/send_command/'):
extra_fields = [
coreapi.Field(
"command",
required=True,
location="form",
schema=coreschema.String()
),
coreapi.Field(
"params",
required=False,
location="form",
schema=coreschema.String()
),
]
manual_fields = super().get_manual_fields(path, method)
return manual_fields + extra_fields
class DeviceViewSet(viewsets.ViewSet):
lookup_field = 'channel'
lookup_value_regex = '[\w-]+'
schema = DeviceViewSchema()
#action(methods=['post'], detail=True, url_name='send_command')
def send_command(self, request, channel):
"""
Send command to device
Parameters:
- command: string
- params: string (JSON encoded list or dict)
"""
...
The final result is:

Django REST framework: help on object level permission

Following this tutorial:
http://django-rest-framework.org/tutorial/1-serialization.html
through http://django-rest-framework.org/tutorial/4-authentication-and-permissions.html
I have this code:
# models.py
class Message(BaseDate):
"""
Private Message Model
Handles private messages between users
"""
status = models.SmallIntegerField(_('status'), choices=choicify(MESSAGE_STATUS))
from_user = models.ForeignKey(User, verbose_name=_('from'), related_name='messages_sent')
to_user = models.ForeignKey(User, verbose_name=_('to'), related_name='messages_received')
text = models.TextField(_('text'))
viewed_on = models.DateTimeField(_('viewed on'), blank=True, null=True)
# serialisers.py
class MessageSerializer(serializers.ModelSerializer):
from_user = serializers.Field(source='from_user.username')
to_user = serializers.Field(source='to_user.username')
class Meta:
model = Message
fields = ('id', 'status', 'from_user', 'to_user', 'text', 'viewed_on')
# views.py
from permissions import IsOwner
class MessageDetail(generics.RetrieveUpdateDestroyAPIView):
model = Message
serializer_class = MessageSerializer
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (permissions.IsAuthenticated, IsOwner)
# permissions.py
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit or delete it.
"""
def has_permission(self, request, view, obj=None):
# Write permissions are only allowed to the owner of the snippet
return obj.from_user == request.user
# urls.py
urlpatterns = patterns('',
url(r'^messages/(?P<pk>[0-9]+)/$', MessageDetail.as_view(), name='api_message_detail'),
)
Then opening the URL of the API i get this error:
**AttributeError at /api/v1/messages/1/
'NoneType' object has no attribute 'from_user'**
Traceback:
File "/var/www/sharigo/python/lib/python2.6/site-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/var/www/sharigo/python/lib/python2.6/site-packages/django/views/generic/base.py" in view
48. return self.dispatch(request, *args, **kwargs)
File "/var/www/sharigo/python/lib/python2.6/site-packages/django/views/decorators/csrf.py" in wrapped_view
77. return view_func(*args, **kwargs)
File "/var/www/sharigo/python/lib/python2.6/site-packages/rest_framework/views.py" in dispatch
363. response = self.handle_exception(exc)
File "/var/www/sharigo/python/lib/python2.6/site-packages/rest_framework/views.py" in dispatch
351. self.initial(request, *args, **kwargs)
File "/var/www/sharigo/python/lib/python2.6/site-packages/rest_framework/views.py" in initial
287. if not self.has_permission(request):
File "/var/www/sharigo/python/lib/python2.6/site-packages/rest_framework/views.py" in has_permission
254. if not permission.has_permission(request, self, obj):
File "/var/www/sharigo/sharigo/apps/sociable/permissions.py" in has_permission
17. return obj.from_user == request.user
Exception Type: AttributeError at /api/v1/messages/1/
Exception Value: 'NoneType' object has no attribute 'from_user'
It seems like None is being passed as the value for the parameter "obj" to isOwner.has_permission().
What am I doing wrong? I think i followed strictly the tutorial.
When has_permission() is called with obj=None it's supposed to return whether the user has permission to any object of this type. So you should handle the case when None is passed.
Your code should be something like:
def has_permission(self, request, view, obj=None):
# Write permissions are only allowed to the owner of the snippet
return obj is None or obj.from_user == request.user
Use function has_object_permission instead of has_permission.
ex:
def has_object_permission(self, request, view, obj=None):
return obj.from_user == request.user
and call function check_object_permissions inside get_object in views
def get_object(self):
obj = get_object_or_404(self.get_queryset())
self.check_object_permissions(self.request, obj)
return obj