from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin from django.db import models from django.utils import timezone from django.contrib.auth.base_user import BaseUserManager from apps.common.models import BaseModel, SoftDeleteModel class UserManager(BaseUserManager): def get_queryset(self): # 默认管理器只返回未删除(未停用)用户;停用用户无法登录/被列出 return super().get_queryset().filter(is_deleted=False) def create_user(self, username, password=None, **extra_fields): if not username: raise ValueError('用户名不能为空') user = self.model(username=username, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, username, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) extra_fields.setdefault('status', 1) return self.create_user(username, password, **extra_fields) class User(AbstractBaseUser, PermissionsMixin, BaseModel): """用户表""" GENDER_CHOICES = [ (0, '未知'), (1, '男'), (2, '女'), ] STATUS_CHOICES = [ (0, '禁用'), (1, '正常'), ] id = models.BigAutoField(primary_key=True) username = models.CharField('用户名', max_length=50, unique=True) password = models.CharField('密码', max_length=255) real_name = models.CharField('真实姓名', max_length=50, blank=True) phone = models.CharField('手机号', max_length=20, unique=True, blank=True) avatar = models.CharField('头像', max_length=255, blank=True) gender = models.SmallIntegerField('性别', choices=GENDER_CHOICES, default=0) role_type = models.CharField('主角色', max_length=30, blank=True) institution = models.ForeignKey( 'user.Institution', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='所属机构' ) department = models.ForeignKey( 'user.Department', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='所属科室' ) title_name = models.CharField('职称', max_length=50, blank=True) practice_years = models.CharField('执业年限', max_length=20, blank=True) major = models.CharField('专业', max_length=100, blank=True) training_stage = models.CharField('培训阶段', max_length=50, blank=True) learning_target = models.CharField('学习目标', max_length=255, blank=True) competency_profile = models.JSONField('能力画像', default=dict, blank=True) weak_dimensions = models.JSONField('薄弱项', default=list, blank=True) strong_dimensions = models.JSONField('优势项', default=list, blank=True) ai_preference = models.JSONField('AI训练偏好', default=dict, blank=True) total_training_count = models.IntegerField('总训练次数', default=0) total_case_count = models.IntegerField('完成病例数', default=0) current_level = models.CharField('当前能力等级', max_length=30, blank=True) status = models.SmallIntegerField('状态', choices=STATUS_CHOICES, default=1) last_login_time = models.DateTimeField('最后登录', null=True, blank=True) # Django required fields is_staff = models.BooleanField('staff status', default=False) is_active = models.BooleanField('active', default=True) date_joined = models.DateTimeField('date joined', default=timezone.now) # 软删除(停用 = 逻辑删除) is_deleted = models.BooleanField('是否删除', default=False, db_index=True) deleted_at = models.DateTimeField('删除时间', null=True, blank=True) objects = UserManager() # 默认:仅未删除 all_objects = BaseUserManager() # 含已删除(管理/恢复用) USERNAME_FIELD = 'username' REQUIRED_FIELDS = [] class Meta: db_table = 'user' verbose_name = '用户' verbose_name_plural = '用户' def __str__(self): return self.username def delete(self, using=None, keep_parents=False): """停用 = 逻辑删除(不物理删除,避免级联丢数据)。""" self.is_deleted = True self.deleted_at = timezone.now() self.save(using=using, update_fields=['is_deleted', 'deleted_at', 'updated_at']) def hard_delete(self, using=None, keep_parents=False): super().delete(using=using, keep_parents=keep_parents) class Role(BaseModel): """角色表""" id = models.BigAutoField(primary_key=True) role_code = models.CharField('角色编码', max_length=50, unique=True) role_name = models.CharField('角色名称', max_length=50) class Meta: db_table = 'role' verbose_name = '角色' verbose_name_plural = '角色' def __str__(self): return self.role_name class TeacherStudentRelation(SoftDeleteModel): """师生关系表""" STATUS_CHOICES = [ (0, '已结束'), (1, '进行中'), ] id = models.BigAutoField(primary_key=True) teacher = models.ForeignKey( User, on_delete=models.CASCADE, related_name='teacher_relations', verbose_name='带教老师' ) student = models.ForeignKey( User, on_delete=models.CASCADE, related_name='student_relations', verbose_name='学员' ) relation_type = models.CharField('关系类型', max_length=30, blank=True) start_time = models.DateTimeField('开始时间', null=True, blank=True) end_time = models.DateTimeField('结束时间', null=True, blank=True) status = models.SmallIntegerField('状态', choices=STATUS_CHOICES, default=1) class Meta: db_table = 'teacher_student_relation' verbose_name = '师生关系' verbose_name_plural = '师生关系' def __str__(self): return f"{self.teacher.real_name or self.teacher.username} -> {self.student.real_name or self.student.username}" class Institution(SoftDeleteModel): """医疗机构表""" id = models.BigAutoField(primary_key=True) code = models.CharField('机构编码', max_length=100, unique=True) name = models.CharField('名称', max_length=255) type = models.CharField('类型', max_length=30, default='hospital', blank=True) level = models.CharField('等级', max_length=30, blank=True) province = models.CharField('省份', max_length=50, blank=True) city = models.CharField('城市', max_length=50, blank=True) banner_url = models.CharField( '机构Banner图', max_length=500, blank=True, help_text='机构专属图片:可为静态相对路径(如 institutions/xxx.png)或完整 http(s) URL' ) class Meta: db_table = 'institution' verbose_name = '机构' verbose_name_plural = '机构' def __str__(self): return self.name class Department(SoftDeleteModel): """科室表(全局分类表,与机构无关;仅超级管理员维护,用于给病例分类)""" id = models.BigAutoField(primary_key=True) name = models.CharField('科室名称', max_length=100) category = models.CharField('科室分类', max_length=50, blank=True) class Meta: db_table = 'department' verbose_name = '科室' verbose_name_plural = '科室' def __str__(self): return self.name