如何在Strawberry GraphQL中实现Django Duration模型字段的标量支持?
在Strawberry GraphQL中暴露Django Duration字段的方案
核心结论
Strawberry GraphQL目前没有内置支持Django DurationField的ISO8601时长标量,需要自定义标量来处理。
实现步骤
1. 自定义ISO8601时长标量
首先创建一个自定义标量,负责在Python的timedelta(Django DurationField对应的Python类型)和ISO8601字符串之间转换:
from datetime import timedelta import strawberry from isodate import parse_duration, duration_isoformat @strawberry.scalar( serialize=lambda value: duration_isoformat(value) if value else None, parse_value=lambda value: parse_duration(value) if value else None ) class ISO8601Duration: """自定义标量,用于处理ISO8601格式的时长字符串与Python timedelta的转换"""
注意:这里用到了
isodate库来简化ISO8601的解析和格式化,可通过pip install isodate安装。如果不想依赖第三方库,也可以手动实现转换逻辑。
2. 在Strawberry类型中绑定自定义标量
定义对应Django模型的Strawberry类型时,将my_duration字段的类型指定为自定义的ISO8601Duration:
import strawberry from strawberry.django import auto from .models import MyModel @strawberry.django.type(MyModel) class MyModelType: id: auto name: auto my_duration: ISO8601Duration
3. API功能验证
现在你的GraphQL API可以正常处理ISO8601格式的时长:
- 查询时,
my_duration会返回PT1H30M这类标准ISO8601格式字符串 - 执行创建/更新mutation时,传入
PT1H30M格式的字符串,会自动解析为timedelta并保存到Django的DurationField中
手动实现转换逻辑(无第三方依赖)
如果不想引入外部库,可手动编写转换函数:
from datetime import timedelta import strawberry def timedelta_to_iso8601(delta: timedelta) -> str: days = delta.days seconds = delta.seconds hours = seconds // 3600 remaining_seconds = seconds % 3600 minutes = remaining_seconds // 60 seconds = remaining_seconds % 60 parts = [] if days > 0: parts.append(f"{days}D") if hours > 0 or minutes > 0 or seconds > 0: time_part = [] if hours > 0: time_part.append(f"{hours}H") if minutes > 0: time_part.append(f"{minutes}M") if seconds > 0: time_part.append(f"{seconds}S") parts.append(f"{'T'.join(time_part)}") return f"P{''.join(parts)}" if parts else "PT0S" def iso8601_to_timedelta(iso_str: str) -> timedelta: if not iso_str.startswith('P'): raise ValueError("Invalid ISO8601 duration format") iso_str = iso_str[1:] days = 0 hours = 0 minutes = 0 seconds = 0 if 'T' in iso_str: date_part, time_part = iso_str.split('T', 1) for part in date_part.split('D'): if part: days = int(part) for component in time_part.split(): if 'H' in component: hours = int(component.replace('H', '')) elif 'M' in component: minutes = int(component.replace('M', '')) elif 'S' in component: seconds = int(component.replace('S', '')) else: for part in iso_str.split('D'): if part: days = int(part) return timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds) @strawberry.scalar( serialize=lambda value: timedelta_to_iso8601(value) if value else None, parse_value=lambda value: iso8601_to_timedelta(value) if value else None ) class ISO8601Duration: """自定义标量,处理ISO8601时长与timedelta的转换"""
内容的提问来源于stack exchange,提问作者Moritz
相关产品推荐
相关产品推荐

