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

Django中使用字典作为models.Choices值报错的技术问询

问题根源

你遇到的核心矛盾是:Django的choices机制要求选项的实际值必须是可哈希类型,但字典是不可哈希的,直接用字典当选项值会触发TypeError: unhashable type: 'dict';你尝试的元组写法不符合models.Choices的规范——每个选项必须是(实际存储值, 可读名称)的二元组,当前写法把DAILY定义成了包含字典和字符串的元组,导致生成的choices格式错误,触发fields.E005。

解决方案

方案一:字符串标识映射字典(推荐)

用可哈希的字符串作为数据库存储的标识,通过映射关系获取对应的频率字典,既符合choices要求,又能无缝传给CustomFrequencyClass:

class CustomFrequencyClass:
    def __init__(self, frequency_dict):
        self.frequency = frequency_dict
        # 这里添加你的计算逻辑

class Report(models.Model):
    # 定义字符串类型的选项,符合TextChoices规范
    class ReportFrequency(models.TextChoices):
        DAILY = "daily", "Daily"
        WEEKLY = "weekly", "Weekly"

    # 建立字符串标识到频率字典的映射
    _FREQUENCY_MAP = {
        ReportFrequency.DAILY: {"days": 1},
        ReportFrequency.WEEKLY: {"days": 7},
    }

    # 用CharField存标识,支持Django内置的choices验证
    frequency_key = models.CharField(
        choices=ReportFrequency.choices,
        default=ReportFrequency.DAILY,
        max_length=20
    )

    @property
    def frequency(self):
        """直接获取对应的频率字典,传给CustomFrequencyClass"""
        return self._FREQUENCY_MAP[self.frequency_key]

    # 可选:如果需要把字典也存在数据库,添加JSONField并同步更新
    # frequency_data = models.JSONField(default=lambda: {"days": 1})

    # def save(self, *args, **kwargs):
    #     self.frequency_data = self._FREQUENCY_MAP[self.frequency_key]
    #     super().save(*args, **kwargs)

使用时直接调用report.frequency拿到字典,传给计算类:

freq_obj = CustomFrequencyClass(report.frequency)

方案二:自定义验证+表单选项(保留JSONField)

如果必须直接存储字典到JSONField,放弃Django内置的choices参数,自己实现验证逻辑和表单选择项:

from django.core.exceptions import ValidationError

class CustomFrequencyClass:
    def __init__(self, frequency_dict):
        self.frequency = frequency_dict

class Report(models.Model):
    # 定义允许的频率字典列表
    ALLOWED_FREQUENCIES = [{"days": 1}, {"days": 7}]
    # 映射字典到可读名称(用元组作为键,因为字典不可哈希)
    _FREQ_NAME_MAP = {
        tuple(freq.items()): name
        for freq, name in zip(ALLOWED_FREQUENCIES, ["Daily", "Weekly"])
    }

    frequency = models.JSONField(default=lambda: {"days": 1})

    def clean(self):
        super().clean()
        # 验证输入的字典是否在允许列表中
        if self.frequency not in self.ALLOWED_FREQUENCIES:
            raise ValidationError("无效的频率设置")

    @classmethod
    def get_freq_choices(cls):
        """生成表单用的选择项(将字典转为字符串作为表单值)"""
        return [
            (str(freq), cls._FREQ_NAME_MAP[tuple(freq.items())])
            for freq in cls.ALLOWED_FREQUENCIES
        ]

然后在表单中处理字符串到字典的转换:

from django import forms
import json
from .models import Report

class ReportForm(forms.ModelForm):
    frequency = forms.ChoiceField(choices=Report.get_freq_choices())

    class Meta:
        model = Report
        fields = ["frequency"]

    def clean_frequency(self):
        # 把表单提交的字符串转回字典
        return json.loads(self.cleaned_data["frequency"])

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 01:22:34