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
相关产品推荐
相关产品推荐

