feat: add profile api
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.14 on 2026-06-08 08:47
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0004_institution_code_unique'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='institution',
|
||||
name='banner_url',
|
||||
field=models.CharField(blank=True, help_text='机构专属图片:可为静态相对路径(如 institutions/xxx.png)或完整 http(s) URL', max_length=500, verbose_name='机构Banner图'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='practice_years',
|
||||
field=models.CharField(blank=True, max_length=20, verbose_name='执业年限'),
|
||||
),
|
||||
]
|
||||
@@ -51,6 +51,7 @@ class User(AbstractBaseUser, PermissionsMixin, BaseModel):
|
||||
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)
|
||||
@@ -138,6 +139,10 @@ class Institution(BaseModel):
|
||||
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'
|
||||
|
||||
@@ -11,8 +11,8 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
'id', 'username', 'real_name', 'phone', 'avatar',
|
||||
'gender', 'role_type', 'institution', 'institution_name',
|
||||
'department', 'department_name', 'title_name', 'major',
|
||||
'training_stage', 'learning_target', 'competency_profile',
|
||||
'department', 'department_name', 'title_name', 'practice_years',
|
||||
'major', 'training_stage', 'learning_target', 'competency_profile',
|
||||
'weak_dimensions', 'strong_dimensions', 'ai_preference',
|
||||
'total_training_count', 'total_case_count', 'current_level',
|
||||
'status', 'last_login_time', 'created_at', 'updated_at'
|
||||
@@ -44,11 +44,27 @@ class UserUpdateSerializer(serializers.ModelSerializer):
|
||||
model = User
|
||||
fields = [
|
||||
'real_name', 'phone', 'avatar', 'gender', 'role_type',
|
||||
'institution', 'department', 'title_name', 'major',
|
||||
'training_stage', 'learning_target', 'status'
|
||||
'institution', 'department', 'title_name', 'practice_years',
|
||||
'major', 'training_stage', 'learning_target', 'status'
|
||||
]
|
||||
|
||||
|
||||
class StudentProfileConfigSerializer(serializers.Serializer):
|
||||
"""医学生信息配置(首次进入系统)——科室、专业职称、执业年限"""
|
||||
department = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Department.objects.all(), help_text='执业科室 ID'
|
||||
)
|
||||
title_name = serializers.CharField(max_length=50, help_text='专业职称,如:住院医师')
|
||||
practice_years = serializers.CharField(max_length=20, help_text='执业年限,如:1-3年')
|
||||
|
||||
def validate_department(self, value):
|
||||
"""科室必须属于当前用户所在机构"""
|
||||
user = self.context['request'].user
|
||||
if user.institution_id and value.institution_id != user.institution_id:
|
||||
raise serializers.ValidationError('所选科室不属于您所在的机构')
|
||||
return value
|
||||
|
||||
|
||||
class UserPasswordSerializer(serializers.Serializer):
|
||||
"""密码修改序列化器"""
|
||||
old_password = serializers.CharField(required=True, write_only=True)
|
||||
|
||||
@@ -19,6 +19,10 @@ urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
# 移动端机构列表(不分页,登录前可调用)
|
||||
path('institution_list/', views.institution_list, name='institution-list'),
|
||||
# 移动端配置页(登录后):机构信息 / 所属机构科室列表 / 学生信息配置
|
||||
path('institution_info/', views.institution_info, name='institution-info'),
|
||||
path('my_departments/', views.my_departments, name='my-departments'),
|
||||
path('profile/config/', views.student_profile_config, name='student-profile-config'),
|
||||
# 认证相关
|
||||
path('auth/send-code/', send_code, name='send-code'),
|
||||
path('auth/register/', register, name='register'),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.conf import settings
|
||||
from rest_framework import viewsets, filters, status
|
||||
from rest_framework.decorators import action, api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
@@ -10,6 +11,7 @@ from .auth import TRIAL_INSTITUTION_NAME
|
||||
from .models import User, Role, TeacherStudentRelation, Institution, Department
|
||||
from .serializers import (
|
||||
UserSerializer, UserCreateSerializer, UserUpdateSerializer,
|
||||
StudentProfileConfigSerializer,
|
||||
RoleSerializer,
|
||||
TeacherStudentRelationSerializer, InstitutionSerializer, DepartmentSerializer
|
||||
)
|
||||
@@ -228,3 +230,101 @@ def institution_list(request):
|
||||
for inst in institutions
|
||||
]
|
||||
return Response(data)
|
||||
|
||||
|
||||
# ── 配置页相关接口(移动端,首次进入系统)─────────────────────────────────────
|
||||
|
||||
def _build_banner_url(request, banner_value):
|
||||
"""把机构 banner 字段转成完整可访问 URL。
|
||||
|
||||
- 空值回退到 settings.DEFAULT_INSTITUTION_BANNER
|
||||
- 已是 http(s) 完整 URL 时原样返回
|
||||
- 否则视为相对 STATIC_URL 的静态路径,拼成绝对 URL
|
||||
"""
|
||||
value = banner_value or settings.DEFAULT_INSTITUTION_BANNER
|
||||
if value.startswith(('http://', 'https://')):
|
||||
return value
|
||||
path = '/' + settings.STATIC_URL.strip('/') + '/' + value.lstrip('/')
|
||||
return request.build_absolute_uri(path)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='机构信息获取接口',
|
||||
description='返回当前登录学生所属机构的信息,含机构专属 Banner 图 URL(用于配置页/首页顶部)。',
|
||||
responses={200: None},
|
||||
tags=['机构'],
|
||||
)
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def institution_info(request):
|
||||
"""机构信息获取接口 — 当前用户所属机构 + Banner 图 URL"""
|
||||
inst = request.user.institution
|
||||
if inst is None:
|
||||
raise AppError('USER_INSTITUTION_NOT_FOUND', '当前账号未关联机构', status_code=404)
|
||||
|
||||
return Response({
|
||||
'id': inst.id,
|
||||
'code': inst.code,
|
||||
'name': inst.name,
|
||||
'type': inst.type,
|
||||
'level': inst.level,
|
||||
'province': inst.province,
|
||||
'city': inst.city,
|
||||
'banner_url': _build_banner_url(request, inst.banner_url),
|
||||
})
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='所属机构科室列表接口(不分页)',
|
||||
description='返回当前登录学生所属机构下的全部科室,不分页,供配置页选择执业科室。',
|
||||
responses={200: None},
|
||||
tags=['机构'],
|
||||
)
|
||||
@api_view(['GET'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def my_departments(request):
|
||||
"""所属机构科室列表接口 — 当前用户机构下全部科室、不分页"""
|
||||
inst = request.user.institution
|
||||
if inst is None:
|
||||
raise AppError('USER_INSTITUTION_NOT_FOUND', '当前账号未关联机构', status_code=404)
|
||||
|
||||
departments = Department.objects.filter(institution=inst).order_by('name')
|
||||
data = [
|
||||
{
|
||||
'id': dept.id,
|
||||
'name': dept.name,
|
||||
'category': dept.category,
|
||||
}
|
||||
for dept in departments
|
||||
]
|
||||
return Response(data)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='医学生信息配置接口',
|
||||
description='首次进入系统时录入学生信息:执业科室、专业职称、执业年限。'
|
||||
'机构在登录时已选定,此处不再修改。',
|
||||
request=StudentProfileConfigSerializer,
|
||||
responses={200: UserSerializer},
|
||||
tags=['用户'],
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def student_profile_config(request):
|
||||
"""医学生信息配置接口 — 录入科室、职称、执业年限"""
|
||||
serializer = StudentProfileConfigSerializer(
|
||||
data=request.data, context={'request': request}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
|
||||
user = request.user
|
||||
user.department = data['department']
|
||||
user.title_name = data['title_name']
|
||||
user.practice_years = data['practice_years']
|
||||
user.save(update_fields=['department', 'title_name', 'practice_years', 'updated_at'])
|
||||
|
||||
return Response({
|
||||
'message': '配置成功',
|
||||
'user': UserSerializer(user).data,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user