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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 02:47:13