You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Django REST Framework中按嵌套模型日期字段分组(多对多关联)

问题描述

在Django REST Framework(DRF)项目中,需要对带有多对多through关联的嵌套模型里的schedule_date日期字段进行分组,实现按日期归类医生的可用时段,具体需求及现有代码如下:

models.py

class AvailableTimeSlotType(models.Model):
    id = models.UUIDField(primary_key = True,default = uuid.uuid4,editable = False)
    name = models.CharField(max_length=255, blank=True,null=True)
    image = models.ImageField(upload_to ='api/uploads/available_time_slot_types/%Y/%m/%d/',blank=True,null=True)
    description = models.TextField(blank=True,null=True)
    def __str__(self):
        return self.name

class AvailableTimeSlot(models.Model):
    id = models.UUIDField(primary_key = True,default = uuid.uuid4,editable = False)
    name = models.CharField(max_length=255, blank=True,null=True)
    time_field = models.TimeField(default=timezone.localtime)
    slot_type = models.ForeignKey(AvailableTimeSlotType,on_delete=models.CASCADE,blank=True,null=True)
    image = models.ImageField(upload_to ='api/uploads/available_time_slots/%Y/%m/%d/',blank=True,null=True)
    description = models.TextField(blank=True,null=True)
    def __str__(self):
        return self.name

class Doctor(models.Model):
    id = models.UUIDField(primary_key = True,default = uuid.uuid4,editable = False)
    user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='doctor',blank=True,null=True)
    public_name = models.CharField(max_length=255, blank=True,null=True)
    biography = models.TextField(blank=True,null=True)
    image = models.ImageField(upload_to='api/uploads/doctors/%Y/%m/%d/', null=True, blank=True)
    slug = AutoSlugField(populate_from='public_name',blank=True,null=True)
    available_timeslots = models.ManyToManyField(AvailableTimeSlot,through="DoctorAvailableTimeSlot",blank=True)
    verification_date = models.DateTimeField(default=datetime.now, blank=True)
    years_of_experience = models.IntegerField(default=1,blank=True)
    rating = models.DecimalField(max_digits=10,decimal_places=2,default=1,blank=True,null=True)
    
    def __str__(self):
        return self.public_name
    
class DoctorAvailableTimeSlot(models.Model):
    id = models.UUIDField(primary_key = True,default = uuid.uuid4,editable = False)
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE,blank=True,null=True,related_name='doctor_available_timeslots')
    available_timeslot = models.ForeignKey(AvailableTimeSlot, on_delete=models.CASCADE,blank=True,null=True)
    schedule_date = models.DateField(default=datetime.now, blank=True)
    
    def __str__(self):
        return self.doctor.public_name +' - '+self.available_timeslot.name

serializer.py

class AvailableTimeSlotTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = AvailableTimeSlotType
        fields = ("__all__")

class AvailableTimeSlotSerializer(serializers.ModelSerializer):
    slot_type = AvailableTimeSlotTypeSerializer()
    class Meta:
        model = AvailableTimeSlot
        fields = ("__all__")

class DoctorAvailableTimeSlotSerializerSerializer(serializers.ModelSerializer):
    available_timeslot = AvailableTimeSlotSerializer()
    class Meta:
        model = DoctorAvailableTimeSlot
        exclude = ("doctor",)

class DoctorSerializer(serializers.ModelSerializer):
    image = serializers.SerializerMethodField('get_image_url')
    user = UserSerializer()
    doctor_available_timeslots = DoctorAvailableTimeSlotSerializerSerializer(many=True)
    
    class Meta:
        model = Doctor
        fields = ("__all__")

当前JSON响应

{"doctor_available_timeslots": [
                {
                    "id": "7bdc5b57-6fd0-430e-b526-7f4697430fda",
                    "available_timeslot": {
                        "id": "569985d5-62e2-4ca2-845b-e919242c3869",
                        "slot_type": {
                            "id": "4f56e64f-f087-44ef-9865-2cc5356a59e6",
                            "name": "Dawn",
                            "image": null,
                            "description": null,
                        },
                        "name": "12:00 AM",
                        "time_field": "00:00:00",
                        "image": null,
                        "description": "",
                        "created_at": "2023-02-23T14:51:39Z",
                        "updated_at": "2023-03-14T13:17:05.636795Z"
                    },
                    "schedule_date": "2023-03-16"
                }
            ]
}

期望JSON响应

{
    "doctor_available_timeslots": [{
        "2023-03-16": [{
                "id": "7bdc5b57-6fd0-430e-b526-7f4697430fda",
                "available_timeslot": {
                    "id": "569985d5-62e2-4ca2-845b-e919242c3869",
                    "slot_type": {
                        "id": "4f56e64f-f087-44ef-9865-2cc5356a59e6",
                        "name": "Dawn",
                        "image": null,
                        "description": null,
                    },
                    "name": "12:00 AM",
                    "time_field": "00:00:00",
                    "image": null,
                    "description": ""
                }
            }
        ]
    }]
}
解决方案

通过SerializerMethodField自定义doctor_available_timeslots的序列化逻辑,即可实现按schedule_date分组的效果,具体修改如下:

  1. 保留原有的DoctorAvailableTimeSlotSerializerSerializer(建议重命名为DoctorAvailableTimeSlotSerializer更规范),用于序列化单个时段条目。
  2. 修改DoctorSerializer,将原有的doctor_available_timeslots字段替换为SerializerMethodField,在自定义方法中完成分组:
class DoctorSerializer(serializers.ModelSerializer):
    image = serializers.SerializerMethodField('get_image_url')
    user = UserSerializer()
    # 替换为自定义方法字段
    doctor_available_timeslots = serializers.SerializerMethodField()
    
    class Meta:
        model = Doctor
        fields = ("__all__")
    
    def get_doctor_available_timeslots(self, obj):
        # 获取当前医生的所有可用时段并按日期排序
        slots = obj.doctor_available_timeslots.all().order_by('schedule_date')
        # 初始化分组字典
        grouped_slots = {}
        
        for slot in slots:
            # 将日期转为字符串作为分组键
            date_key = slot.schedule_date.strftime('%Y-%m-%d')
            # 序列化单个时段并移除schedule_date字段
            serialized_slot = DoctorAvailableTimeSlotSerializerSerializer(slot).data
            del serialized_slot['schedule_date']
            
            if date_key not in grouped_slots:
                grouped_slots[date_key] = []
            grouped_slots[date_key].append(serialized_slot)
        
        # 转换为期望的列表格式
        return [grouped_slots]

说明

  • 先按schedule_date对医生的可用时段排序,保证同日期的条目归类在一起。
  • 遍历每个时段时,将日期转为字符串作为分组键,同时移除序列化结果中的schedule_date字段(已作为分组键使用)。
  • 最后将分组后的字典包装成列表,完全匹配期望的响应格式;若不需要外层列表,直接返回grouped_slots即可。

内容的提问来源于stack exchange,提问作者TEX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 16:57:04