Django Rest Framework : Почему сложный вложенный сериализатор пытается создать вложенные поля в базе данных при вызове .is_valid()?
Контекст
У меня есть проект, в котором есть три сущности : Account, Community и JoinRequest.
JoinRequest связывает аккаунт (пользователя) с сообществом. И не должно быть более одного JoinRequest для любой пары (аккаунт, сообщество).
Проблема
Я создал соответствующие модели, сериализаторы и unittest, которые вы можете увидеть ниже. Но когда я запускаю свой тест, он терпит неудачу при возврате join_request_serializer.is_valid() = False
Что приводит к следующей ошибке :
join_request_serializer.errors
{'user': {'email': [ErrorDetail(string='account with this email already exists.', code='unique')], 'username': [ErrorDetail(string='account with this username already exists.', code='unique')], 'password': [ErrorDetail(string='This field is required.', code='required')]}, 'community': {'name': [ErrorDetail(string='community with this name already exists.', code='unique')]}}
Похоже, что метод .is_valid() сериализатора JoinRequestSerializer пытается воссоздать Account и Community, данные которых ранее были переданы в качестве аргументов при создании экземпляра...
Есть идеи, почему появляется эта ошибка?
Unit Test
class JoinRequestSerializerTestCase(TestCase):
def test_join_request_serializer_create_success(self):
account = register()
account_serializer = AccountSerializer(account)
community_data = {
'name': 'CommunityNameExample'
}
community = Community(community_data)
community_serializer = CommunitySerializer(community)
data = {'user':account_serializer.data, 'community':community_serializer}
join_request_serializer = JoinRequestSerializer(data=data)
self.assertEqual(join_request_serializer.is_valid(), True)
join_request_serializer.save()
self.assertEqual(JoinRequest.objects.count(), 1)
Account
class MyAccountManager(BaseUserManager):
def create_user(self, email, username, password=None):
if not email:
raise ValueError('Users must have an email address')
if not username:
raise ValueError('Users must have an username')
user = self.model(
email=self.normalize_email(email),
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, password):
# This method must be overridden to use MyAccountManager class
user = self.create_user(
email=self.normalize_email(email),
username=username,
password=password,
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Account(AbstractBaseUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(
max_length=30,
unique=True,
validators = [
RegexValidator(
regex='^[a-zA-Z0-9]*$',
message='Username must be Alphanumeric.',
code='invalid_username'
)
]
)
date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login', auto_now_add=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = 'username' # Generic (not explicit) keyword for the login field
REQUIRED_FIELDS = ['email']
objects = MyAccountManager() # What is the point of this line?
def create(self, email, username, password, **kwargs):
account = Account.objects.create_user(
username = username,
email = email,
password = password
)
post_save(sender=Account, instance=account, created = True, raw=True)
return account
Сообщество
class Community(models.Model):
name = models.CharField(
max_length=30,
unique=True,
validators = [
RegexValidator(
regex='^[a-zA-Z0-9]*$',
message='Community name must be Alphanumeric.',
code='invalid_username'
)
]
)
bio = models.CharField(max_length=150, blank=True, default='')
slug = models.SlugField(max_length=30, blank=True, default=slugify(name))
class Meta(object):
verbose_name_plural = 'Communities'
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Community, self).save(*args, **kwargs)
JoinRequest
class JoinRequest(models.Model):
community = models.ForeignKey(Community, on_delete=models.CASCADE)
user = models.ForeignKey(Account, on_delete=models.CASCADE)
creation_date = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['community', 'user'], name='unique_joinrequest')
]
AccountSerializer
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['email', 'username', 'password']
extra_kwargs = {'password':{'write_only':True}}
def create(self):
account = Account.objects.create_user(
email = self.validated_data['email'],
username = self.validated_data['username'],
password = self.validated_data['password'],
)
return account
CommunitySerializer
class CommunitySerializer(serializers.ModelSerializer):
class Meta:
model = Community
fields = ['name', 'bio']
lookup_field = 'slug'
extra_kwargs = {
'url':{'lookup_field':'slug'}
}
JoinRequestSerializer
class JoinRequestSerializer(serializers.ModelSerializer):
user = AccountSerializer(
source='account_set',
)
community=CommunitySerializer(
source='community_set',
)
class Meta:
model = JoinRequest
fields = ['user', 'community']
read_only_fields = ('user', 'community')
validators = [
UniqueTogetherValidator(
queryset=JoinRequest.objects.all(),
fields=['user', 'community'],
message='A Join Request for this couple of User'\
' and Community already exists.'
)
]
extra_kwargs = {
'user':{'read_only':True},
'commnuity':{'read_only':True}
}
def create(self):
join_request = JoinRequest(
user = self.validated_data['user'],
community = self.validated_data['community'],
)
return join_request