优雅的Python方法为Django模型字段选择指定额外属性和方法

1 投票
1 回答
998 浏览
提问于 2025-04-18 13:19

如何在Django模型中指定选项,使得这些“选项”不仅仅是数据库值和显示值(这也是Django的选项规范所要求的)?假设不同的选项有一些配置选项,我想在代码中设置这些选项,还有一些方法,而这些方法可能在不同的选项之间是不同的。这里有个例子:

class Reminder(models.Model):
    frequency = models.CharField(choices=SOME_CHOICES)
    next_reminder = models.DateTimeField()
    ...

我们应该如何为“每周”和“每月”的提醒指定SOME_CHOICES?到目前为止,我最好的解决方案是为每个频率选项写一个类,然后把类名存储在数据库中,每当我需要这些方法或配置数据时,就通过类名导入这个类。

理想情况下,我希望能在一个地方指定所有这些配置值,并为每个选项定义方法,而不是把逻辑分散在很多模型方法中,使用长长的if/elif/elif...else结构。Python是面向对象的……这些频率选项似乎有“数据”和“方法”,所以它们看起来很适合用类来表示……

class ReminderFrequency(object):
    pass

class Weekly(ReminderFrequency):
    display_value = "Every week"

    def get_next_reminder_time(self):
        return <now + 7 days>

class Monthly(ReminderFrequency):
    display_value = "Every month"

    def get_next_reminder_time(self):
        return <now + 1 month>

SOME_CHOICES = ((freq.__name__, freq.display_value) for freq in [Weekly, Monthly])

假设除了get_next_reminder_time,我还想指定类似first_reminder的内容(比如“每周”的第一次提醒是在三天后,而下一个是在七天后,但“每月”的第一次提醒是在七天后,下一个是在一个月后,等等)。还有5个其他的配置值或方法,这些都依赖于选项。

一个想法是把frequency设为指向另一个模型Frequency的外键,在那里我设置配置值,但这样不允许不同的选项有不同的方法或逻辑(就像每周和每月的例子)。所以这个想法不行。

我目前的方法感觉很繁琐,因为需要根据存储在数据库中的类名加载每个ReminderFrequency子类。还有其他想法吗?或者这就是“正确且好的Python方式”吗?

1 个回答

2

我觉得在Django中处理这个问题最自然的方法是创建一个自定义模型字段。你可以这样使用它:

class Reminder(models.Model):
    frequency = models.FrequencyField()
    next_reminder = models.DateTimeField()

reminder = Reminder.objects.get()
reminder_time = reminder.frequency.get_next_reminder_time()

要实现这个功能,可以查看相关文档。简单来说,你可能需要:

  • CharField继承
  • 在字段定义中提供choices选项
  • 实现get_prep_value()。你可以把值表示为实际的类名,像你上面提到的那样,或者用其他值并使用查找表。
  • 实现to_python()。在这里,你会把数据库中的表示转换成你类的实际Python实例。

这比想象中要稍微复杂一点,但也不算太难。

(以上假设你想在代码中定义行为。如果你需要通过提供配置值来配置行为(就像你上面提到的ForeignKey的想法),那又是另一回事了。)

撰写回答