sync from local backup
This commit is contained in:
@@ -0,0 +1,463 @@
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils import timezone
|
||||
from django.core.cache import caches
|
||||
import os
|
||||
import re
|
||||
|
||||
from utils import RandCode
|
||||
|
||||
FUser = get_user_model()
|
||||
default_cache = caches['default']
|
||||
|
||||
|
||||
def get_fields_to_extract(validated_data, fields_to_extract):
|
||||
extracted = {}
|
||||
for field in fields_to_extract:
|
||||
if field in validated_data:
|
||||
extracted[field] = validated_data[field]
|
||||
return extracted
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
PROTECTED_FIELDS = ['is_active', 'password', 'email', 'is_staff', 'is_superuser', 'last_login']
|
||||
class Meta:
|
||||
model = FUser
|
||||
fields = [
|
||||
'id',
|
||||
'username',
|
||||
'email',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'password',
|
||||
'last_login',
|
||||
'date_joined',
|
||||
'phone_number',
|
||||
'avatar',
|
||||
'bio',
|
||||
'isSetPassword',
|
||||
'gender',
|
||||
'location',
|
||||
'points',
|
||||
'coins',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': True},
|
||||
'is_active': {'read_only': True},
|
||||
'is_staff': {'read_only': True},
|
||||
'is_superuser': {'read_only': True},
|
||||
'last_login': {'read_only': True},
|
||||
'date_joined': {'read_only': True},
|
||||
'id': {'read_only': True},
|
||||
'isSetPassword': {'read_only': True},
|
||||
'points': {'read_only': True},
|
||||
'coins': {'read_only': True},
|
||||
|
||||
}
|
||||
|
||||
|
||||
def create_by_email(self, validated_data):
|
||||
fields_to_extract = ['email']
|
||||
extracted = get_fields_to_extract(validated_data, fields_to_extract)
|
||||
|
||||
extracted['username'] = extracted['email']
|
||||
extracted['password'] = RandCode.get_alphanumeric_characters_code_8()
|
||||
|
||||
# 2. 创建用户
|
||||
user = FUser.objects.create_user(**extracted)
|
||||
|
||||
user.is_active = True
|
||||
user.is_staff = False
|
||||
user.is_superuser = False
|
||||
user.save()
|
||||
|
||||
return user
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
for attr, value in validated_data.items():
|
||||
if attr in self.PROTECTED_FIELDS :
|
||||
continue
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# 将所有字段设为非必填
|
||||
for field_name, field in self.fields.items():
|
||||
field.required = False
|
||||
|
||||
|
||||
class ChangePasswordSerializer(serializers.Serializer):
|
||||
old_password = serializers.CharField(required=False, allow_blank=True)
|
||||
new_password = serializers.CharField(required=True, min_length=8, max_length=128)
|
||||
confirm_password = serializers.CharField(required=True, min_length=8, max_length=128)
|
||||
|
||||
def validate_old_password(self, value):
|
||||
user = self.context['request'].user
|
||||
if user.has_usable_password() and not user.check_password(value):
|
||||
raise serializers.ValidationError('当前密码错误')
|
||||
return value
|
||||
|
||||
def validate_new_password(self, value):
|
||||
if len(value) < 8:
|
||||
raise serializers.ValidationError('密码长度不能少于8位')
|
||||
has_letter = any(c.isalpha() for c in value)
|
||||
has_digit = any(c.isdigit() for c in value)
|
||||
if not (has_letter and has_digit):
|
||||
raise serializers.ValidationError('密码必须包含字母和数字')
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs['new_password'] != attrs['confirm_password']:
|
||||
raise serializers.ValidationError({'confirm_password': '两次输入的新密码不一致'})
|
||||
return attrs
|
||||
|
||||
def save(self):
|
||||
user = self.context['request'].user
|
||||
user.set_password(self.validated_data['new_password'])
|
||||
if not user.isSetPassword:
|
||||
user.isSetPassword = True
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class UserUpdateSerializer(serializers.ModelSerializer):
|
||||
username = serializers.CharField(
|
||||
required=False,
|
||||
max_length=150,
|
||||
min_length=2,
|
||||
error_messages={
|
||||
'max_length': '用户名不能超过150个字符',
|
||||
'min_length': '用户名至少2个字符',
|
||||
}
|
||||
)
|
||||
gender = serializers.ChoiceField(
|
||||
required=False,
|
||||
choices=[(0, '保密'), (1, '男'), (2, '女')],
|
||||
error_messages={
|
||||
'invalid_choice': '性别值无效,可选值为:0(保密)、1(男)、2(女)',
|
||||
}
|
||||
)
|
||||
bio = serializers.CharField(
|
||||
required=False,
|
||||
max_length=500,
|
||||
allow_blank=True,
|
||||
error_messages={
|
||||
'max_length': '个人简介不能超过500个字符',
|
||||
}
|
||||
)
|
||||
location = serializers.CharField(
|
||||
required=False,
|
||||
max_length=100,
|
||||
allow_blank=True,
|
||||
error_messages={
|
||||
'max_length': '所在地区不能超过100个字符',
|
||||
}
|
||||
)
|
||||
phone_number = serializers.CharField(
|
||||
required=False,
|
||||
max_length=15,
|
||||
allow_blank=True,
|
||||
error_messages={
|
||||
'max_length': '手机号不能超过15个字符',
|
||||
}
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = FUser
|
||||
fields = [
|
||||
'username',
|
||||
'gender',
|
||||
'bio',
|
||||
'location',
|
||||
'phone_number',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'validators': []},
|
||||
}
|
||||
|
||||
def validate_username(self, value):
|
||||
user = self.context['request'].user
|
||||
if FUser.objects.filter(username=value).exclude(id=user.id).exists():
|
||||
raise serializers.ValidationError('该用户名已被使用')
|
||||
return value
|
||||
|
||||
def validate_phone_number(self, value):
|
||||
if value and not value.isdigit():
|
||||
raise serializers.ValidationError('手机号只能包含数字')
|
||||
return value
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class PointTransactionSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
transaction_type = serializers.CharField(read_only=True)
|
||||
currency_type = serializers.CharField(read_only=True)
|
||||
amount = serializers.IntegerField(read_only=True)
|
||||
balance_after = serializers.IntegerField(read_only=True)
|
||||
description = serializers.CharField(read_only=True)
|
||||
created_at = serializers.DateTimeField(read_only=True)
|
||||
|
||||
def to_representation(self, instance):
|
||||
return {
|
||||
'id': instance.id,
|
||||
'transaction_type': instance.transaction_type,
|
||||
'transaction_type_display': instance.get_transaction_type_display(),
|
||||
'currency_type': instance.currency_type,
|
||||
'currency_type_display': instance.get_currency_type_display(),
|
||||
'amount': instance.amount,
|
||||
'balance_after': instance.balance_after,
|
||||
'description': instance.description,
|
||||
'created_at': instance.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
}
|
||||
|
||||
|
||||
class SendEmailCodeSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField(required=True, error_messages={
|
||||
'required': '邮箱地址不能为空',
|
||||
'invalid': '邮箱格式不正确',
|
||||
})
|
||||
target = serializers.ChoiceField(
|
||||
required=True,
|
||||
choices=['old', 'new'],
|
||||
error_messages={
|
||||
'required': '验证目标不能为空',
|
||||
'invalid_choice': '验证目标无效',
|
||||
}
|
||||
)
|
||||
|
||||
def validate_email(self, value):
|
||||
user = self.context['request'].user
|
||||
target = self.initial_data.get('target', '')
|
||||
|
||||
if target == 'old':
|
||||
if user.email != value:
|
||||
raise serializers.ValidationError('请输入当前绑定的邮箱地址')
|
||||
elif target == 'new':
|
||||
if user.email == value:
|
||||
raise serializers.ValidationError('新邮箱与当前邮箱相同')
|
||||
if FUser.objects.filter(email=value).exists():
|
||||
raise serializers.ValidationError('该邮箱已被其他账号使用')
|
||||
|
||||
return value
|
||||
|
||||
def save(self):
|
||||
email = self.validated_data['email']
|
||||
target = self.validated_data['target']
|
||||
user = self.context['request'].user
|
||||
|
||||
code = RandCode.get_digit_characters_code_6()
|
||||
cache_key = f"email_change_{target}_{user.id}"
|
||||
default_cache.set(cache_key, {'code': code, 'email': email}, timeout=600)
|
||||
|
||||
from ..tasks import send_change_email_task
|
||||
from utils.safe_task import submit_task
|
||||
submit_task(send_change_email_task, email, code, target)
|
||||
|
||||
return email
|
||||
|
||||
|
||||
class BlacklistSerializer(serializers.ModelSerializer):
|
||||
blocked_user_id = serializers.IntegerField(write_only=True)
|
||||
blocked_user_username = serializers.CharField(source='blocked_user.username', read_only=True)
|
||||
blocked_user_email = serializers.CharField(source='blocked_user.email', read_only=True)
|
||||
blocked_user_avatar = serializers.ImageField(source='blocked_user.avatar', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = None
|
||||
fields = ['id', 'blocked_user_id', 'blocked_user_username', 'blocked_user_email', 'blocked_user_avatar', 'reason', 'created_at']
|
||||
read_only_fields = ['id', 'created_at']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
from ..models import Blacklist
|
||||
self.Meta.model = Blacklist
|
||||
|
||||
def validate_blocked_user_id(self, value):
|
||||
from ..models import FUser
|
||||
try:
|
||||
FUser.objects.get(id=value)
|
||||
except FUser.DoesNotExist:
|
||||
raise serializers.ValidationError('用户不存在')
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
user = self.context['request'].user
|
||||
blocked_user_id = attrs.get('blocked_user_id')
|
||||
if user.id == blocked_user_id:
|
||||
raise serializers.ValidationError({'blocked_user_id': '不能将自己加入黑名单'})
|
||||
from ..models import Blacklist
|
||||
if Blacklist.objects.filter(user=user, blocked_user_id=blocked_user_id).exists():
|
||||
raise serializers.ValidationError({'blocked_user_id': '该用户已在黑名单中'})
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
from ..models import Blacklist, FUser
|
||||
user = self.context['request'].user
|
||||
blocked_user = FUser.objects.get(id=validated_data['blocked_user_id'])
|
||||
blacklist = Blacklist.objects.create(
|
||||
user=user,
|
||||
blocked_user=blocked_user,
|
||||
reason=validated_data.get('reason', '')
|
||||
)
|
||||
return blacklist
|
||||
|
||||
|
||||
class ChangeEmailSerializer(serializers.Serializer):
|
||||
new_email = serializers.EmailField(required=True, error_messages={
|
||||
'required': '新邮箱地址不能为空',
|
||||
'invalid': '邮箱格式不正确',
|
||||
})
|
||||
code = serializers.CharField(required=True, max_length=6, min_length=6, error_messages={
|
||||
'required': '验证码不能为空',
|
||||
'min_length': '验证码必须为6位',
|
||||
'max_length': '验证码必须为6位',
|
||||
})
|
||||
|
||||
def validate_new_email(self, value):
|
||||
user = self.context['request'].user
|
||||
if user.email == value:
|
||||
raise serializers.ValidationError('新邮箱与当前邮箱相同')
|
||||
if FUser.objects.filter(email=value).exists():
|
||||
raise serializers.ValidationError('该邮箱已被其他账号使用')
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
user = self.context['request'].user
|
||||
code = attrs['code']
|
||||
new_email = attrs['new_email']
|
||||
|
||||
cache_key = f"email_change_new_{user.id}"
|
||||
cached = default_cache.get(cache_key)
|
||||
|
||||
if cached is None:
|
||||
raise serializers.ValidationError({'code': '验证码已过期,请重新获取'})
|
||||
if cached['code'] != code:
|
||||
raise serializers.ValidationError({'code': '验证码错误'})
|
||||
if cached['email'] != new_email:
|
||||
raise serializers.ValidationError({'new_email': '邮箱与发送验证码时的邮箱不一致'})
|
||||
|
||||
return attrs
|
||||
|
||||
def save(self):
|
||||
user = self.context['request'].user
|
||||
user.email = self.validated_data['new_email']
|
||||
user.save()
|
||||
|
||||
cache_key_old = f"email_change_old_{user.id}"
|
||||
cache_key_new = f"email_change_new_{user.id}"
|
||||
default_cache.delete(cache_key_old)
|
||||
default_cache.delete(cache_key_new)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
class LoginRecordSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
device = serializers.CharField(read_only=True)
|
||||
ip_address = serializers.IPAddressField(read_only=True)
|
||||
location = serializers.CharField(read_only=True)
|
||||
login_time = serializers.DateTimeField(read_only=True)
|
||||
status = serializers.CharField(read_only=True)
|
||||
|
||||
def to_representation(self, instance):
|
||||
return {
|
||||
'id': instance.id,
|
||||
'device': instance.device,
|
||||
'ip_address': instance.ip_address or '',
|
||||
'location': instance.location,
|
||||
'login_time': instance.login_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'status': instance.status,
|
||||
'status_display': instance.get_status_display(),
|
||||
}
|
||||
|
||||
|
||||
class SendPhoneCodeSerializer(serializers.Serializer):
|
||||
phone = serializers.CharField(required=True, max_length=15, error_messages={
|
||||
'required': '手机号不能为空',
|
||||
'max_length': '手机号不能超过15个字符',
|
||||
})
|
||||
target = serializers.ChoiceField(
|
||||
required=True,
|
||||
choices=['old', 'new'],
|
||||
error_messages={
|
||||
'required': '验证目标不能为空',
|
||||
'invalid_choice': '验证目标无效',
|
||||
}
|
||||
)
|
||||
|
||||
def validate_phone(self, value):
|
||||
if not value.isdigit():
|
||||
raise serializers.ValidationError('手机号只能包含数字')
|
||||
if len(value) != 11:
|
||||
raise serializers.ValidationError('手机号必须为11位')
|
||||
return value
|
||||
|
||||
def save(self):
|
||||
phone = self.validated_data['phone']
|
||||
target = self.validated_data['target']
|
||||
user = self.context['request'].user
|
||||
|
||||
code = RandCode.get_digit_characters_code_6()
|
||||
cache_key = f"phone_change_{target}_{user.id}"
|
||||
default_cache.set(cache_key, {'code': code, 'phone': phone}, timeout=300)
|
||||
|
||||
return phone
|
||||
|
||||
|
||||
class ChangePhoneSerializer(serializers.Serializer):
|
||||
new_phone = serializers.CharField(required=True, max_length=15, error_messages={
|
||||
'required': '新手机号不能为空',
|
||||
'max_length': '手机号不能超过15个字符',
|
||||
})
|
||||
code = serializers.CharField(required=True, max_length=6, min_length=6, error_messages={
|
||||
'required': '验证码不能为空',
|
||||
'min_length': '验证码必须为6位',
|
||||
'max_length': '验证码必须为6位',
|
||||
})
|
||||
|
||||
def validate_new_phone(self, value):
|
||||
if not value.isdigit():
|
||||
raise serializers.ValidationError('手机号只能包含数字')
|
||||
if len(value) != 11:
|
||||
raise serializers.ValidationError('手机号必须为11位')
|
||||
user = self.context['request'].user
|
||||
if user.phone_number == value:
|
||||
raise serializers.ValidationError('新手机号与当前手机号相同')
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
user = self.context['request'].user
|
||||
code = attrs['code']
|
||||
new_phone = attrs['new_phone']
|
||||
|
||||
cache_key = f"phone_change_new_{user.id}"
|
||||
cached = default_cache.get(cache_key)
|
||||
|
||||
if cached is None:
|
||||
raise serializers.ValidationError({'code': '验证码已过期,请重新获取'})
|
||||
if cached['code'] != code:
|
||||
raise serializers.ValidationError({'code': '验证码错误'})
|
||||
if cached['phone'] != new_phone:
|
||||
raise serializers.ValidationError({'new_phone': '手机号与发送验证码时的手机号不一致'})
|
||||
|
||||
return attrs
|
||||
|
||||
def save(self):
|
||||
user = self.context['request'].user
|
||||
user.phone_number = self.validated_data['new_phone']
|
||||
user.save()
|
||||
|
||||
cache_key_old = f"phone_change_old_{user.id}"
|
||||
cache_key_new = f"phone_change_new_{user.id}"
|
||||
default_cache.delete(cache_key_old)
|
||||
default_cache.delete(cache_key_new)
|
||||
|
||||
return user
|
||||
Reference in New Issue
Block a user