【问题标题】:User creation with unique constraint in Django Rest framework在 Django Rest 框架中创建具有唯一约束的用户
【发布时间】:2017-03-27 07:35:41
【问题描述】:

我在 Google 上进行了研究,并尝试了很多方法,但仍然无法正确解决。以下是要求:

  1. 用于扩展用户模型的一对一字段,因此这已经实现,新模型称为客户。
  2. 现在,返回新的用户 201 响应,但并非所有数据都被序列化,只有 date_of_birth 以 json 格式出现,我什至希望用户也在该 json 响应中。
  3. 如果我尝试添加一个用户名已经存在的用户,它的效果会非常糟糕。我正在尝试尝试和除外,但它确实不起作用。如果用户名已经存在,我希望发送 409 冲突响应。 这是 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

这是客户模型:

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

这是 CustomerSerializer 类:

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()

这是为客户设置的视图:

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)

所以,基本上我想创建新客户,所有数据返回为响应和状态 201,如果用户名已经存在,那么 409 或我定义的状态代码以及 DRF 不应该抱怨的一些数据,即;现在它说如果我修改序列化程序,OrderDict 不包含 PK。

谢谢


编辑 1


这里是带有自定义异常的更新序列化程序:

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."

但仍然得到:

    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.

测试用例如下:

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)

【问题讨论】:

  • 现在这个问题变成了变色龙问题。
  • 请重新阅读问题和评论。这是一个简单的测试用例,它尝试创建一个相似的用户两次,我期望状态码为 409。我在描述中也确实提到过。

标签: django python-2.7 django-rest-framework django-users django-1.8


【解决方案1】:

也许您应该尝试直接在视图中捕捉它:

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)

并删除您在序列化程序中创建的逻辑。 DRF 异常用于视图而非序列化程序。

【讨论】:

    【解决方案2】:

    这是因为您在 create 方法中捕获的异常过于广泛。事实上,你永远不应该这样做(不管你是否使用 DRF)

        except Exception as exception:
            print exception
            # print "customer --> %s " % customer
            return customer
    

    您应该只捕获您需要捕获的特定异常。而且您根本不应该回头客。在这种情况下,对ModelSerializer 的检查告诉我们,您真正应该捕获的唯一一个是 TypeError。即使是调用者再次提出处理程序。

        except TypeError as exception:
            print exception
            # print "customer --> %s " % customer
            raise TypeError(exception)
    

    现在你应该得到你想要的,但根据你的 cmets,你不是那么尝试引发自定义错误。

    从 rest_framework.exceptions 导入 APIException 从 django.utils.encoding 导入 force_text

    class Custom409(APIException):
        status_code = status.HTTP_409_CONFLICT
        default_detail = 'A conflict occurred'
    

    然后

        except IntergrityError as ext:
            print exception
            # print "customer --> %s " % customer
            raise Custom409(ext)
    

    【讨论】:

    • 视图集创建方法应该有哪些变化?
    • 我做到了,但我想要么 create 方法不应该出现在视图中,要么我现在应该捕获完整性错误。我的对象很简单,创建用户或者用户已经存在然后应该返回 409。
    • 好的,我从那个视图中删除了它,但如果用户名存在,我仍然需要一种方法来返回 409,因为好像现在它正在给出 IntegrityError。
    • 更新了完整的堆栈跟踪。
    • 这似乎与您当前的问题无关。这是您的一个测试用例中的错误。
    【解决方案3】:

    您的代码正在引发IntegrityErorr,因为您在创建配置文件时违反了唯一约束。

    您应该在创建用户配置文件/客户实例时使用get_or_create。这样的事情应该可以工作。

    # 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"})
    

    【讨论】:

    • 引发异常不会解决它。它给出了类似这样的 AttributeError: 'tuple' object has no attribute 'user'。我返回异常,但这并不能解决我的问题,因为状态是 201,这意味着已经创建了一个新用户,但这就是我想要停止的。
    • 您在创建客户实例时是否放入了[0] 索引?
    • 是的,我做到了,但它仍然不起作用,还有一件事,Customer 的 get_or_create 中的 defaults=**validated_data 也不起作用。
    • 我的错,应该是defaults=validated_data,更新答案。在使用get_or_create 时,您应该只使用唯一键。确保在创建/获取用户实例时只使用唯一键。也许它会阻止创建新用户
    • 但即便如此,它也会创建一条记录,但如果用户名存在,则不会引发冲突。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多