Django ORM跨库查询注解DateTimeField时出现'tzinfo'属性错误
跨数据库模型注解DateTime字段触发AttributeError错误
问题背景
在Django项目中配置了数据库路由,需对存储在另一数据库的关联模型查询集使用注解,采用字典作为临时处理方案。注解datetime字段时触发错误:AttributeError: 'str' object has no attribute 'tzinfo'。注:Deal模型的delivery_time字段已定义为DateTimeField。
错误堆栈
in localize(self, dt, is_dst) 229 def localize(self, dt, is_dst=False): 230 '''Convert naive time to local time''' --> 231 if dt.tzinfo is not None: 232 raise ValueError('Not naive datetime (tzinfo is already set)') 233 return dt.replace(tzinfo=self) AttributeError: 'str' object has no attribute 'tzinfo'
原代码示例
from app1.models import Deal from app2.models import Contract from django.utils import timezone as django_timezone from django.db.models import Case, DateTimeField, Value, When deal_qs = Deal.objects.filter(<filters relevant to the flow>).values('id', 'final_amount', 'delivery_time', 'status__name') deal_dict = {deal['id']: deal for deal in deal_qs} trade_object_ids = list(deal_dict.keys()) contracts = Contract.objects.filter(trade_object_id__in=trade_object_ids) # 注解定义部分 delivery_date_annotation = Case( *[When( trade_object_id=key, then=Value( django_timezone.make_aware( django_timezone.datetime.strptime( django_timezone.datetime.strftime( deal_dict.get(key, {}).get('delivery_time'), "%Y-%m-%d %H:%M:%S" ), "%Y-%m-%d %H:%M:%S" ), django_timezone.get_current_timezone() ) ) if deal_dict.get(key, {}).get('delivery_time') else None ) for key in trade_object_ids ], default=Value(None), output_field=DateTimeField() ) contracts = contracts.annotate(my_date_time_field=delivery_date_annotation)
问题分析
核心原因是:使用values()查询返回的字典中,DateTimeField类型的字段会被序列化为字符串(而非datetime对象)。原代码中对该字符串做了strftime再strptime的无效转换,最终传给django_timezone.make_aware()的仍然是字符串,而非datetime对象,导致触发tzinfo属性不存在的错误。
解决方案
方案1:修复字典处理逻辑
直接将values()返回的字符串格式时间转换为datetime对象,无需多余的格式化操作:
delivery_date_annotation = Case( *[When( trade_object_id=key, then=Value( # 直接解析字符串为带时区的datetime对象 django_timezone.parse(deal_dict[key]['delivery_time']) ) if deal_dict[key].get('delivery_time') else None ) for key in trade_object_ids], default=Value(None), output_field=DateTimeField() )
如果需要手动指定格式(避免解析错误),可以替换为:
django_timezone.make_aware( django_timezone.datetime.strptime(deal_dict[key]['delivery_time'], "%Y-%m-%d %H:%M:%S"), django_timezone.get_current_timezone() )
方案2:使用ORM跨数据库关联(更优雅)
如果数据库路由允许,通过定义跨数据库外键,结合Subquery实现注解,避免手动处理字典:
- 在Contract模型中添加跨数据库外键:
class Contract(models.Model): trade_object_id = models.IntegerField() # 跨数据库关联Deal模型,需指定数据库别名 deal = models.ForeignKey( 'app1.Deal', on_delete=models.DO_NOTHING, db_constraint=False, # 取消数据库级约束(跨数据库不支持) to_field='id', db='deal_db', # 替换为Deal模型所在的数据库别名 related_name='contracts', blank=True, null=True )
- 使用Subquery注解:
from django.db.models import Subquery, OuterRef # 子查询获取对应Deal的delivery_time deal_subquery = Deal.objects.filter(id=OuterRef('trade_object_id')).values('delivery_time')[:1] contracts = Contract.objects.filter(trade_object_id__in=trade_object_ids).annotate( my_date_time_field=Subquery(deal_subquery, output_field=DateTimeField()) )
内容的提问来源于stack exchange,提问作者abhigya shridhar
相关产品推荐
相关产品推荐

