1

I have a test case that looks like this (the point being to prevent duplicate user profiles):

def test_create_duplicate_profile(self):
    new_user = models.User(
        username='bobjones',
        password=';alsdfkj;asoi'
    )
    new_user.save()
    client = APIClient()
    client.force_authenticate(new_user)
    response = client.post(
        path='/api/profiles/'
    )
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    same_user = models.User.objects.get(
        username='bobjones'
    )
    response = client.post(
        path='/api/profiles/'
    )
    self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
    client.logout()

This results in an error (as well as failing test):

(.virtualenv) nbascoutingdotcom $ python manage.py test profiles
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E..
======================================================================
ERROR: test_create_duplicate_profile (profiles.tests.test_api.ProfilesAPITest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/evanzamir/nbascoutingdotcom/.virtualenv/lib/python3.6/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/Users/evanzamir/nbascoutingdotcom/.virtualenv/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 328, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: UNIQUE constraint failed: profiles_profile.user_id

My model looks like this:

class Profile(TimestampModerated):
    id = models.BigAutoField(primary_key=True, db_column='id', null=False)
    uuid = models.UUIDField(db_index=True, default=uuid_lib.uuid4(), editable=False)
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE, related_name='profiles', blank=False,
                                unique=True)
    bio = models.CharField(max_length=140, blank=True, null=True)
    media_url = models.URLField(blank=True, null=True)
    dob = models.DateField(blank=True, null=True)

    class Meta:
        verbose_name_plural = "profiles"

Here is the serializer:

class ProfileSerializer(serializers.ModelSerializer):
    user = serializers.CharField(source='user.username', read_only=True,
                                 validators=[UniqueValidator(queryset=Profile.objects.all())])
    email = serializers.CharField(source='user.email', read_only=True)
    first = serializers.CharField(source='user.first_name', read_only=True)
    last = serializers.CharField(source='user.last_name', read_only=True)
    last_login = serializers.DateTimeField(source='user.last_login', read_only=True)
    date_joined = serializers.DateTimeField(source='user.date_joined', read_only=True)

    class Meta:
        model = Profile
        fields = ('id', 'created', 'moderation_code', 'user', 'updated', 'uuid', 'bio', 'email',
                  'first', 'last', 'last_login', 'media_url', 'dob', 'date_joined')

Here is my ViewSet:

class ProfileViewSet(viewsets.ModelViewSet):
    """
    This viewsetomatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.

    """
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer
    authentication_classes = (SessionAuthentication,)
    permission_classes = (permissions.AllowAny, IsOwnerOrReadOnly)
    lookup_field = 'user'

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

I would like to be able to send back a valid HTTP status code (HTTP_40X_), but I can't seem to be able to hook into the validation error. I tried putting some code in the perform_create method to raise a ValidationError, but that didn't work for me. Any suggestions?

1
  • Could you check in the viewset if a Profile for self.request.user already exists and return early with your error code of choice? Commented Jul 23, 2017 at 1:00

1 Answer 1

1

You'd have to hook into ModelViewSet.create(...) where the actual response is created and returned:

from rest_framework import status
from rest_framework.response import Response


def create(self, request, *args, **kwargs):
    try:
        return super(ProfileViewSet, self).create(request, *args, **kwargs):
    except IntegrityError:
        return Response(status=status.HTTP_409_CONFLICT)

For a more general project-wide approach, you might also read the drf docs on custom exception handling.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.