如何在 django-rest-auth 上实现自定义细节序列化器并将其序列化

How to implement a custom detail serializer on django-rest-auth and serialize it

我想在创建用户实例时创建一个配置文件用户,我明白了,但是当我 运行 服务器时,它给我 TypeError 说 "NoneType" 对象不可迭代,我在 UserProfile 模型中创建了一个 post_save 信号

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

我的 UserProfile 模型是:

class UserProfile(TimeStampedModel):
MALE = 'M'
FEMALE = 'F'
NOT_SPECIFIED = 'NS'

GENDER_CHOICES = (
    (MALE, _('Male')),
    (FEMALE, _('Female')),
    (NOT_SPECIFIED, _('Not specified'))
)

VALIDATOR = [validators.RegexValidator(re.compile('^[\w]+$'),
                                       _('Only can has letters'), 'invalid')]

user_profile_id = models.AutoField(primary_key=True)
user = models.OneToOneField(auth_user, verbose_name=_('user'), blank=False, null=False,
                            on_delete=models.CASCADE, related_name='profile')
first_name = models.CharField(max_length=125, verbose_name=_('first name'),
                              validators=VALIDATOR, blank=True, null=False)
last_name = models.CharField(max_length=125, verbose_name=_('last name'),
                             validators=VALIDATOR, blank=True, null=False)
location = models.ForeignKey(Locations, on_delete=models.DO_NOTHING, verbose_name=_('location'),
                             related_name='location', blank=True, null=True)
profile_image = models.ImageField(verbose_name=_('profile image'), null=True)
gender = models.CharField(verbose_name=_('gender'), max_length=2, choices=GENDER_CHOICES, blank=True, null=True,
                          default=NOT_SPECIFIED)
DOB = models.DateField(verbose_name=_('date of birth'), blank=True, null=True)
occupation = models.TextField(verbose_name=_('occupation'), blank=True, null=False)
about = models.TextField(verbose_name=_('about'), blank=True, null=False)

我正在使用 django-rest-frameworkdjango-rest-authdjango-allauth. 这是我的 UserProfileSerializer:

class UserProfileSerializer(UserDetailsSerializer):
# profile_image = ImageSerializer()
user = UserSerializer(source='profile', many=True)

class Meta:
    model = UserProfile
    fields = '__all__'
    read_only_fields = ('created_at', 'updated_at',)

def update(self, instance, validated_data):
    instance.first_name = validated_data.get('first_name', instance.first_name)
    instance.last_name = validated_data.get('last_name', instance.last_name)
    instance.occupation = validated_data.get('occupation', instance.occupation)
    instance.about = validated_data.get('about', instance.about)
    instance.save()
    return instance

def to_internal_value(self, data):
    user_data = data['user']
    return super().to_internal_value(user_data)

def to_representation(self, instance):
    pass

当我访问 127.0.0.1:800/rest-auth/user or 127.0.0.1:800 /rest-auth/registration 并注册用户出现如下输出:

TypeError at /rest-auth/user/
'NoneType' object is not iterable
Request Method: GET
Request URL:    http://127.0.0.1:8000/rest-auth/user/
Django Version: 3.0.1
Exception Type: TypeError
Exception Value:    
'NoneType' object is not iterable
Exception Location: C:\PROGRA~1\Python37\venv\lib\site-packages\rest_framework\utils\serializer_helpers.py in __init__, line 18
Python Executable:  C:\PROGRA~1\Python37\venv\Scripts\python.exe
Python Version: 3.7.2
Python Path:    
['C:\Program Files\Python37\venv\Scripts\asta',
 'C:\PROGRA~1\Python37\venv\Scripts\python37.zip',
 'C:\PROGRA~1\Python37\venv\DLLs',
 'C:\PROGRA~1\Python37\venv\lib',
 'C:\PROGRA~1\Python37\venv\Scripts',
 'c:\program files\python37\Lib',
 'c:\program files\python37\DLLs',
 'C:\PROGRA~1\Python37\venv',
 'C:\PROGRA~1\Python37\venv\lib\site-packages',
 'C:\Program Files\Python37\venv\Scripts\asta\conf\backend']
Server time:    Qui, 6 Fev 2020 15:01:44 +000

我的base.py(设置)是:

    REST_FRAMEWORK = {
    # CHANGE IT, to use oauth2 by django-oauth2-toolkit : already
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        # 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

REST_AUTH_REGISTER_SERIALIZERS = {
    'REGISTER_SERIALIZER': 'backend.users.serializers.SignUpSerializer'
}

REST_AUTH_SERIALIZERS = {
    'USER_DETAILS_SERIALIZER': 'backend.users.serializers.UserProfileSerializer',
    'LOGIN_SERIALIZER': 'backend.users.serializers.CustomLoginSerializer'
}

REST_USE_JWT = True
ACCOUNT_LOGOUT_ON_GET = True
OLD_PASSWORD_FIELD_ENABLED = True
SOCIALACCOUNT_QUERY_EMAIL = True
CORS_ORIGIN_ALLOW_ALL = True

JWT_AUTH = {
    'JWT_VERIFY_EXPIRATION': False
}

当我将 user = UserSerializer(source='profile', many=True) 更改为 user = UserSerializer(source='profile') 时,出现了其他错误:

AttributeError at /rest-auth/user/
Got AttributeError when attempting to get a value for field `username` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `UserProfile` instance.
Original exception text was: 'UserProfile' object has no attribute 'username'.

您应该删除 UserProfileSerializer 上的 many=True 标志

class UserProfileSerializer(UserDetailsSerializer):
# profile_image = ImageSerializer()
user = UserSerializer(source='profile')

根据 documentation:

If the field is used to represent a to-many relationship, you should add the many=True flag to the serializer field.

所以一对一关系不应该有 many=True 标志。

编辑

好的,编辑后另一个问题变得清晰了。在您的设置中,您将 'USER_DETAILS_SERIALIZER' 更改为 'backend.users.serializers.UserProfileSerializer'。如果您阅读 documentation of django-rest-auth about how the /rest-auth/user/ url is implemented you can see that they use UserDetailsView and pass an instance of User to the serializer. Thus either you make your custom UserDetailsView or, if you want to keep the implementation of the library, you need to extend the implementation of the library 并在那里处理一对一关系。

注1: 在 Django Rest Framework 中,嵌套的序列化器默认是不可写的。因此,您需要实施 update logic yourself 或者您可以在自己的视图中将 UserProfile 更新逻辑与 UserDetailsView 分开。

注二: 您可以编辑 Django Rest AuthRegisterSerializer 并确保在那里创建了 UserProfile,这样您就可以在该序列化程序中处理 'nested creation' 而不是通过在一个 [=58] 中获取数据来使用信号=]打电话。

编辑 2

根据请求的示例,非常固执地尝试遵循该包。

REST_AUTH_SERIALIZERS = {
    # Changed
    'USER_DETAILS_SERIALIZER': 'backend.users.serializers.CustomUserDetailsSerializer',

    # Not changed
    'LOGIN_SERIALIZER': 'backend.users.serializers.CustomLoginSerializer'
}

制作您的 CustomUserDetailsS​​erializer

# Get the UserModel
UserModel = get_user_model()

# Your UserProfileSerializer
class UserProfileSerializer(serializers.ModelSerializer):
    # profile_image = ImageSerializer()

    class Meta:
        model = UserProfile
        fields = '__all__'
        read_only_fields = ('created_at', 'updated_at',)

# Custom serializer based on rest_auth.serializers.UserDetailsSerializer
class CustomUserDetailsSerializer(serializers.ModelSerializer):
    """
    User model w/o password
    """
    user_profile = UserProfileSerializer()

    class Meta:
        model = UserModel
        fields = ('pk', 'username', 'email', 'user_profile')
        read_only_fields = ('email', )

    # Nested writes are not possible by default so you should implement it yourself
    # https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers

    def update(self, instance, validated_data):
        # Do updates on instance and save
        # updates...
        # eg. instance.username = validated_data.get('username', instance.username)

        # get user profile data
        user_profile_data = validated_data.pop('user_profile')

        # access UserProfile through User instance one-to-one
        user_profile = instance.profile

        # do updates on user_profile with data in user_profile_data
        # updates...

        # save
        user_profile.save()

        return instance

示例JSON

{
   "pk": 1,
   "email": "test@test.com",
   "username":"test",
   "user_profile":{
      "first_name":"Jane",
      ...
   }
}