0
votes

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:

  1. A one to one field for extending User model, so this has been achieved and the new model is called Customer.
  2. 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.
  3. 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': '[email protected]'}, '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': '[email protected]'},'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)
3
Now this question has become a chameleon question. - e4c5
Please re-read the question and also the comment. It is a simple test case, which tries to create a similar user twice, and I am expecting a status code of 409. I did mention it in the description as well. - Maverick

3 Answers

2
votes

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)
1
votes

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"})
0
votes

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.