优雅的Python方法为Django模型字段选择指定额外属性和方法
如何在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 个回答
我觉得在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
的想法),那又是另一回事了。)