|
@@ -1,79 +1,103 @@
|
|
from rest_framework import serializers
|
|
from rest_framework import serializers
|
|
-from .models import CustomUser # 导入你的 CustomUser 模型
|
|
|
|
-from django.contrib.auth.password_validation import validate_password # Django 自带密码验证
|
|
|
|
-# from django.core.exceptions import ValidationError # 如果需要在 validate 方法中手动抛出
|
|
|
|
|
|
+from .models import CustomUser, InterestTag # 确保 InterestTag 也被导入
|
|
|
|
+from django.contrib.auth.password_validation import validate_password
|
|
|
|
+
|
|
|
|
+class InterestTagSerializer(serializers.ModelSerializer):
|
|
|
|
+ class Meta:
|
|
|
|
+ model = InterestTag
|
|
|
|
+ fields = ('id', 'name', 'created_at')
|
|
|
|
+ read_only_fields = ('id', 'created_at')
|
|
|
|
|
|
class UserRegistrationSerializer(serializers.ModelSerializer):
|
|
class UserRegistrationSerializer(serializers.ModelSerializer):
|
|
password = serializers.CharField(
|
|
password = serializers.CharField(
|
|
write_only=True,
|
|
write_only=True,
|
|
required=True,
|
|
required=True,
|
|
validators=[validate_password],
|
|
validators=[validate_password],
|
|
- style={'input_type': 'password'} # 在DRF的可浏览API中显示为密码输入框
|
|
|
|
|
|
+ style={'input_type': 'password'}
|
|
)
|
|
)
|
|
password2 = serializers.CharField(
|
|
password2 = serializers.CharField(
|
|
write_only=True,
|
|
write_only=True,
|
|
required=True,
|
|
required=True,
|
|
- label="确认密码", # 在DRF的可浏览API中的标签
|
|
|
|
|
|
+ label="确认密码",
|
|
style={'input_type': 'password'}
|
|
style={'input_type': 'password'}
|
|
)
|
|
)
|
|
- # 如果avatar是注册时可选上传的,确保在Meta中处理
|
|
|
|
avatar = serializers.ImageField(required=False, allow_null=True, max_length=None, use_url=True)
|
|
avatar = serializers.ImageField(required=False, allow_null=True, max_length=None, use_url=True)
|
|
|
|
|
|
-
|
|
|
|
class Meta:
|
|
class Meta:
|
|
model = CustomUser
|
|
model = CustomUser
|
|
- # 列出注册时需要用户提供的字段
|
|
|
|
- # 确保这些字段与 CustomUser 模型中定义的一致,特别是 USERNAME_FIELD 和 REQUIRED_FIELDS
|
|
|
|
fields = ('email', 'phone_number', 'nickname', 'password', 'password2', 'school', 'bio', 'avatar')
|
|
fields = ('email', 'phone_number', 'nickname', 'password', 'password2', 'school', 'bio', 'avatar')
|
|
extra_kwargs = {
|
|
extra_kwargs = {
|
|
- # password 和 password2 已经在上面定义了 write_only=True
|
|
|
|
- 'nickname': {'required': True}, # 根据你的 CustomUser.REQUIRED_FIELDS,nickname是必填
|
|
|
|
|
|
+ 'nickname': {'required': True},
|
|
'school': {'required': False, 'allow_blank': True},
|
|
'school': {'required': False, 'allow_blank': True},
|
|
'bio': {'required': False, 'allow_blank': True},
|
|
'bio': {'required': False, 'allow_blank': True},
|
|
- # 'avatar': {'required': False} # 已经在上面字段定义中处理
|
|
|
|
}
|
|
}
|
|
|
|
|
|
def validate(self, attrs):
|
|
def validate(self, attrs):
|
|
if attrs['password'] != attrs['password2']:
|
|
if attrs['password'] != attrs['password2']:
|
|
- raise serializers.ValidationError({"password2": "两次输入的密码不匹配。"}) # 将错误关联到password2字段
|
|
|
|
|
|
+ raise serializers.ValidationError({"password2": "两次输入的密码不匹配。"})
|
|
return attrs
|
|
return attrs
|
|
|
|
|
|
def create(self, validated_data):
|
|
def create(self, validated_data):
|
|
- validated_data.pop('password2') # 移除 password2,它不需要保存到模型
|
|
|
|
-
|
|
|
|
- # 使用 CustomUser 模型的 create_user 方法创建用户
|
|
|
|
- # 它会处理密码哈希
|
|
|
|
- # validated_data 现在包含了 'email', 'phone_number', 'password', 'nickname' 以及可选的 'school', 'bio', 'avatar'
|
|
|
|
|
|
+ validated_data.pop('password2')
|
|
user = CustomUser.objects.create_user(**validated_data)
|
|
user = CustomUser.objects.create_user(**validated_data)
|
|
return user
|
|
return user
|
|
|
|
|
|
class UserProfileSerializer(serializers.ModelSerializer):
|
|
class UserProfileSerializer(serializers.ModelSerializer):
|
|
- """
|
|
|
|
- 用于获取和更新用户个人资料的序列化器。
|
|
|
|
- """
|
|
|
|
- avatar_url = serializers.SerializerMethodField() # 用于获取完整的头像URL
|
|
|
|
|
|
+ avatar_url = serializers.SerializerMethodField()
|
|
|
|
+ interests = serializers.PrimaryKeyRelatedField(
|
|
|
|
+ queryset=InterestTag.objects.all(),
|
|
|
|
+ many=True,
|
|
|
|
+ required=False,
|
|
|
|
+ allow_empty=True
|
|
|
|
+ )
|
|
|
|
|
|
class Meta:
|
|
class Meta:
|
|
model = CustomUser
|
|
model = CustomUser
|
|
- # 列出用户可以查看和修改的字段
|
|
|
|
- # 不应包含敏感信息如 password (除非是 write_only 用于修改密码的特殊场景)
|
|
|
|
fields = (
|
|
fields = (
|
|
'id', 'email', 'phone_number', 'nickname',
|
|
'id', 'email', 'phone_number', 'nickname',
|
|
'avatar', 'avatar_url', 'bio', 'school',
|
|
'avatar', 'avatar_url', 'bio', 'school',
|
|
- 'date_joined', 'last_login', 'is_active' # is_active 可以考虑是否允许用户自己修改
|
|
|
|
|
|
+ 'interests',
|
|
|
|
+ 'date_joined', 'last_login', 'is_active'
|
|
)
|
|
)
|
|
- # 定义只读字段 (在更新时不能被修改,或者需要特殊流程)
|
|
|
|
read_only_fields = ('id', 'email', 'phone_number', 'date_joined', 'last_login', 'avatar_url')
|
|
read_only_fields = ('id', 'email', 'phone_number', 'date_joined', 'last_login', 'avatar_url')
|
|
- # avatar 字段本身可以用于上传,所以不是read_only,但 avatar_url 是只读的。
|
|
|
|
- # email 和 phone_number 通常不应通过这个接口随意更改,如果允许更改,需要额外的验证流程(比如发送验证码)。
|
|
|
|
|
|
|
|
def get_avatar_url(self, obj):
|
|
def get_avatar_url(self, obj):
|
|
request = self.context.get('request')
|
|
request = self.context.get('request')
|
|
if obj.avatar and hasattr(obj.avatar, 'url'):
|
|
if obj.avatar and hasattr(obj.avatar, 'url'):
|
|
if request is not None:
|
|
if request is not None:
|
|
return request.build_absolute_uri(obj.avatar.url)
|
|
return request.build_absolute_uri(obj.avatar.url)
|
|
- return obj.avatar.url # Fallback if request is not available
|
|
|
|
- return None # 如果没有头像或无法获取URL
|
|
|
|
|
|
+ return obj.avatar.url
|
|
|
|
+ return None
|
|
|
|
+
|
|
|
|
+class RecommendedUserSerializer(serializers.ModelSerializer): # 新增的序列化器
|
|
|
|
+ """
|
|
|
|
+ 用于推荐用户列表中显示用户信息的序列化器。
|
|
|
|
+ """
|
|
|
|
+ avatar_url = serializers.SerializerMethodField()
|
|
|
|
+ common_interests_tags = InterestTagSerializer(many=True, read_only=True, source='common_interests_list') # 假设在视图中注入
|
|
|
|
|
|
- # 如果允许通过这个Serializer更新头像,不需要特别做什么,DRF的ModelSerializer会自动处理ImageField
|
|
|
|
- # 确保在视图中传递 request 到 serializer context,以便生成完整的URL
|
|
|
|
|
|
+ class Meta:
|
|
|
|
+ model = CustomUser
|
|
|
|
+ fields = (
|
|
|
|
+ 'id',
|
|
|
|
+ 'nickname',
|
|
|
|
+ 'avatar_url',
|
|
|
|
+ 'school',
|
|
|
|
+ 'bio',
|
|
|
|
+ 'common_interests_tags',
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ def get_avatar_url(self, obj):
|
|
|
|
+ request = self.context.get('request')
|
|
|
|
+ if obj.avatar and hasattr(obj.avatar, 'url'):
|
|
|
|
+ if request is not None:
|
|
|
|
+ return request.build_absolute_uri(obj.avatar.url)
|
|
|
|
+ return obj.avatar.url
|
|
|
|
+ return None
|
|
|
|
+
|
|
|
|
+ def to_representation(self, instance):
|
|
|
|
+ representation = super().to_representation(instance)
|
|
|
|
+ bio = representation.get('bio')
|
|
|
|
+ if bio and len(bio) > 50: # 截断bio到50字符
|
|
|
|
+ representation['bio'] = bio[:50] + '...'
|
|
|
|
+ return representation
|