Django自定义模型字段映射两个数据库列实现DRF单参数传值需求
实现方案
这个方案不需要修改现有数据库结构,通过自定义非持久化模型字段+DRF序列化器适配即可实现需求:
第一步:实现自定义模型字段
自定义MyCustomDatetimeField,标记为非数据库映射字段,通过描述器实现赋值时自动拆分时间和偏移量到两个已有的数据库字段:
from django.db import models from django.utils.dateparse import parse_datetime from datetime import timezone, timedelta class MyCustomDatetimeField(models.Field): # 标记为非实体字段,不会映射到数据库列 concrete = False def __init__(self, *args, **kwargs): kwargs["null"] = True kwargs["editable"] = False super().__init__(*args, **kwargs) def contribute_to_class(self, cls, name): super().contribute_to_class(cls, name) # 绑定描述器控制字段读写逻辑 setattr(cls, name, self.DateTimeDescriptor(name)) class DateTimeDescriptor: def __init__(self, field_name): self.private_attr = f"_{field_name}" def __get__(self, instance, owner=None): if instance is None: return self # 读取时自动拼接回带时区的完整datetime对象 if instance.mixing_datetime and instance.mixing_datetime_utc_offset_seconds is not None: tz = timezone(timedelta(seconds=instance.mixing_datetime_utc_offset_seconds)) return instance.mixing_datetime.replace(tzinfo=tz) return getattr(instance, self.private_attr, None) def __set__(self, instance, value): setattr(instance, self.private_attr, value) if not value: instance.mixing_datetime = None instance.mixing_datetime_utc_offset_seconds = 0 return # 兼容字符串和datetime对象两种输入 dt = parse_datetime(value) if isinstance(value, str) else value # 计算时区偏移秒数 offset_seconds = int(dt.utcoffset().total_seconds()) # 赋值给两个实际存储的数据库字段 # 如果需要存储UTC时间,可改为 dt.astimezone(timezone.utc).replace(tzinfo=None) instance.mixing_datetime = dt.replace(tzinfo=None) instance.mixing_datetime_utc_offset_seconds = offset_seconds
之后就可以按照你期望的方式定义模型:
class MyModel(models.Model): mixing_datetime = models.DateTimeField() mixing_datetime_utc_offset_seconds = models.IntegerField() datetime = MyCustomDatetimeField()
第二步:适配DRF序列化器
在序列化器中新增datetime字段用于接收前端参数,不需要暴露底层的两个存储字段:
from rest_framework import serializers from .models import MyModel class MyModelSerializer(serializers.ModelSerializer): # 配置为DateTimeField自动完成输入校验和解析 datetime = serializers.DateTimeField() class Meta: model = MyModel fields = ["id", "datetime"] # 按需补充其他需要暴露的字段 # 不需要额外重写create/update方法,DRF赋值时会自动触发字段的拆分逻辑
效果验证
前端发起请求:
frontend_client.post('server_url', payload={'datetime': '2021-07-02T14:34:49+00:00'})
后台会自动解析并赋值:
mixing_datetime存储为2021-07-02 14:34:49mixing_datetime_utc_offset_seconds存储为0
注意事项
datetime字段为非数据库字段,不能直接用于ORM查询,过滤时请使用底层的mixing_datetime和mixing_datetime_utc_offset_seconds字段- 如果前端可能传入不带时区的datetime字符串,可以在序列化器字段中配置
default_timezone参数指定默认时区
内容的提问来源于stack exchange,提问作者Gonzalo
相关产品推荐
相关产品推荐

