from django.contrib.auth.models import User from django.db import transaction from rest_framework import serializers from .models import Member from .phone import normalize_phone class StaffUserSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=False, allow_blank=True) class Meta: model = User fields = [ "id", "username", "first_name", "email", "is_superuser", "is_active", "last_login", "date_joined", "password", ] read_only_fields = ["last_login", "date_joined"] def create(self, validated_data): password = validated_data.pop("password", None) or "" if not password: raise serializers.ValidationError({"password": "新建时密码必填"}) user = User( username=validated_data["username"], first_name=validated_data.get("first_name", ""), email=validated_data.get("email", ""), is_superuser=validated_data.get("is_superuser", False), is_active=validated_data.get("is_active", True), is_staff=True, ) user.set_password(password) user.save() return user def update(self, instance, validated_data): password = validated_data.pop("password", None) for attr, value in validated_data.items(): setattr(instance, attr, value) instance.is_staff = True if password: instance.set_password(password) instance.save() return instance def _ensure_member_user(member, phone, password=None, name=""): """为会员确保绑定非 staff 的 User;手机号作为 username。""" if not phone: return member user = member.user if user is None: # 复用同名非 staff 账号,避免唯一约束冲突 user = User.objects.filter(username=phone, is_staff=False).first() if user is None: user = User(username=phone, is_staff=False, is_active=True) if password: user.set_password(password) else: user.set_unusable_password() user.save() member.user = user else: if user.username != phone: if User.objects.filter(username=phone).exclude(pk=user.pk).exists(): raise serializers.ValidationError({"phone": "该手机号已被占用"}) user.username = phone if password: user.set_password(password) user.is_staff = False user.is_active = not member.is_disabled user.save() if name and not user.first_name: user.first_name = name user.save(update_fields=["first_name"]) return member class MemberSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=False, allow_blank=True) class Meta: model = Member fields = [ "id", "name", "phone", "organize", "is_vip", "last_login_at", "is_disabled", "password", "created_at", "updated_at", ] read_only_fields = ["last_login_at", "created_at", "updated_at"] def validate_phone(self, value): # 更新时允许留空(不改手机号);新建在 create 中再强制必填 if value in (None, "") and self.instance: return self.instance.phone phone = normalize_phone(value, required=not bool(self.instance)) if not phone: return phone qs = Member.objects.filter(phone=phone) if self.instance: qs = qs.exclude(pk=self.instance.pk) if qs.exists(): raise serializers.ValidationError("该手机号已被注册") return phone @transaction.atomic def create(self, validated_data): password = validated_data.pop("password", None) or "" phone = validated_data.get("phone") or "" if not phone: raise serializers.ValidationError({"phone": "手机号必填"}) if not password: raise serializers.ValidationError({"password": "新建时密码必填"}) member = Member(**validated_data) member.save() _ensure_member_user(member, phone, password=password, name=member.name) member.save(update_fields=["user", "updated_at"]) return member @transaction.atomic def update(self, instance, validated_data): password = validated_data.pop("password", None) or "" for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() if instance.phone: _ensure_member_user( instance, instance.phone, password=password or None, name=instance.name, ) instance.save(update_fields=["user", "updated_at"]) elif password: raise serializers.ValidationError({"phone": "设置密码前需填写手机号"}) return instance class MemberPublicSerializer(serializers.ModelSerializer): """前台登录/注册返回的会员信息(不含禁用标记写入)。""" class Meta: model = Member fields = [ "id", "name", "phone", "organize", "is_vip", "last_login_at", ]