Django REST 框架:自定义用户模型上的 UniqueConstraint 不起作用

时间:2021-04-06 01:45:48

标签: django-rest-framework

我有一个应用程序,我需要两种用户类型(UserType1 和 UserType2)。我使用带有布尔标志的 AbstractBaseUser 模型,然后在用户类型对象上创建 OneToOne 字段返回给用户(此时没有添加额外数据)。

我有一个独特的要求,我想强制执行一个 UniqueConstraint,其中给定的电子邮件只能有一种用户类型。

用于确定用户类型的布尔标志是 is_user_type_1is_user_type_2,模型定义如下:

from django.conf import settings
from django.contrib.auth.models import AbstractUser, UserManager
from django.core.exceptions import ValidationError
from django.db import models
from django_extensions.db.models import TimeStampedModel


class UserManager(BaseUserManager):

    def create_user(self, username, email, password=None, **kwargs):
        if not username:
            raise ValueError('The given username must be set')

        user = self.model(
            username=username,
            email=self.normalize_email(email),
            **kwargs
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, username, email, password=None):
        user = self.create_user(
            username=username,
            email=self.normalize_email(email),
            password=password,
        )
        user.is_user_type_1 = False
        user.is_user_type_2 = False
        user.save(using=self._db)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        max_length=150,
        unique=True,
        validators=[username_validator],
        error_messages={
            'unique': "A user with that username already exists.",
        },
    )
    email = models.EmailField()
    is_user_type_1 = models.BooleanField(default=False)
    is_user_type_2 = models.BooleanField(default=False)

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    objects = UserManager()

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=[
                    'email',
                    'is_user_type_1',
                    'is_user_type_2',
                ],
                name='unique_email_by_type'
            )
        ]

class UserType1(TimeStampedModel):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        primary_key=True,
        related_name='user_type1',
    )


class UserType2(TimeStampedModel):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        primary_key=True,
        related_name='user_type2'
    )

我根据 UniqueConstraintUser.Metaemailuser_type_1 中使用 user_type_2 来满足特定要求。我试图在此处遵循 Django 文档:https://docs.djangoproject.com/en/3.1/ref/models/constraints/

为了添加更多上下文,我还为 UserType1UserType2 提供了以下序列化程序,我只想公开用户类型的端点并隐藏 User 对象的管理:

from django.db import transaction
from rest_framework import serializers

from .models import User, Athlete, Trainer


class AbstractUserTypeSerializer(serializers.ModelSerializer):
    username = serializers.CharField(source='user.username')
    email = serializers.EmailField(source='user.email')
    password = serializers.CharField(source='user.password', write_only=True)

    @transaction.atomic
    def create(self, validated_data):
        user_data = validated_data.pop('user')

        user_data['is_user_type_1'] = self._is_user_type_1()
        user_data['is_user_type_2'] = self._is_user_type_2()

        user = User.objects.create(
            **user_data
        )

        user_type = self.Meta.model.objects.create(
            user=user,
            **validated_data
        )

        return user_type

    @transaction.atomic
    def update(self, instance, validated_data):
        user = instance.user

        updated_user_data = validated_data.pop('user')
        user.email = updated_user_data.get('email', user.email)

        user.save()

        instance.user = user
        instance.save()

        return instance

    def _is_user_type_1(self) -> bool:
        return False

    def _is_user_type_2(self) -> bool:
        return False

    @staticmethod
    def _get_default_fields() -> tuple:
        return (
            'pk',
            'username',
            'email',
            'password',
            'created',
            'modified',
        )

    @staticmethod
    def _get_default_extra_kwargs() -> tuple:
        return {
            'pk': {
                'read_only': True
            }
        }


class UserType1Serializer(AbstractUserTypeSerializer):

    class Meta:
        model = UserType1
        fields = AbstractUserTypeSerializer._get_default_fields()
        extra_kwargs = AbstractUserTypeSerializer._get_default_extra_kwargs()

    def _is_user_type_1(self):
        return True

    def _is_user_type_2(self):
        return False


class UserType2Serializer(AbstractUserTypeSerializer):

    class Meta:
        model = UserType2
        fields = AbstractUserTypeSerializer._get_default_fields()
        extra_kwargs = AbstractUserTypeSerializer._get_default_extra_kwargs()

    def _is_user_type_1(self):
        return False

    def _is_user_type_2(self):
        return True

用户模型已在我的设置中注册:

AUTH_USER_MODEL = 'user.User'

但是,UniqueConstraint 不起作用,我仍然能够创建两个用户,其中 emailuser_type_1user_type_2 字段是相同的。我认为这与上面的嵌套类有关,但无法在我的测试或在线中找到原因。

对于解释为什么在创建对象时没有满足此约束的任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:0)

我不确定这是否是解决问题的最佳方法,但如果其他人遇到此问题,我确实找到了解决方案。我认为验证未运行的主要原因是嵌套模型。由于我们通过从 User 调用 UserType1 隐式地通过 UserType2create_user 对象创建 UserManager,因此 Django 不会自动在 {{ 1}}。

为了解决这个问题,我在上面的 save 类的 validate_unique 方法中显式调用了 create_user,如下所示:

UserManager