在 Django 中指定一组相关字段时公认的最佳实践是什么?

What's the accepted best practice when specifying a set of related fields in Django?

假设我在 Django 中为 RPG 创建了一个 'Character' 模型,我希望所有属性(力量、敏捷、耐力等)都以类似的方式定义,并且能够以类似的方式处理。

显然我可以这样做:

intelligence = IntegerRangeField(min_value=1, max_value=10)
wits= IntegerRangeField(min_value=1, max_value=10)
resolve = IntegerRangeField(min_value=1, max_value=10)
strength = IntegerRangeField(min_value=1, max_value=10)
dexterity = IntegerRangeField(min_value=1, max_value=10)
stamina = IntegerRangeField(min_value=1, max_value=10)
presence = IntegerRangeField(min_value=1, max_value=10)
manipulation = IntegerRangeField(min_value=1, max_value=10)
composure = IntegerRangeField(min_value=1, max_value=10)

整数范围字段definition

但这并不是特别 DRY。这也意味着当我添加 ~30 种相似的技能时,我将变得更加干练,并且容易出错。

这些似乎不值得通过外键创建(这是解决方案 here),因为我需要为每个属性等指定 9 个外键。

是否已经有 DRY 的解决方案?还是我需要自己动手?

编辑:
我对属性和技能的主要用途是为技能检查提供总数,例如,如果我想知道要为 Programming 掷多少骰子,我可能会指定它使用字符 intelligence+computers 属性和技能,并且例如,从 'Tasks' 列表(例如属性和技能的其他组合)中显示该角色可以执行的前五项。


编辑:

看起来遵循 M2M 路线导致我过度规范化。有人能suggest improvements吗?

你为什么不和 ManyToManyField 一起工作?

这样您就可以轻松地添加和管理这些属性。也更易于管理,因为在您的情况下,您必须为每个属性添加一列。

对于需要重复字段的这种情况(如@RonaldOldenburger 建议),您可以使用多对多字段关系。例如:

class Skill(models.Model):
    name = models.CharField(max_length=255)
    value = IntegerRangeField(min_value=1, max_value=10)

class Character(models.Model):
    name = models.CharField(max_length=255)
    skill = models.ManyToManyField(Skill)

并且如果您使用 modelform 创建一个 Character 模型实例,那么您可以使用 model formset factory 添加 Skill 到它。

这取决于您将如何使用这些统计数据。

  • 例如,如果您需要根据玩家的智力获取玩家列表;有一个字段是 easier/better (您的解决方案)。该解决方案还涉及更少的代码(至少最初是这样)。

  • 如果您需要列出统计数据,查看每个统计数据的最佳字符等,您最好使用您链接的解决方案。问问你自己,"intelligence" 应该是一个独立的 "entity"(带有 slug、描述等)吗?

  • 这里还有一个选项;假设对于大多数查询,您只需要一个全局 "level" 值,并且统计信息的详细信息仅在检查玩家时有用:

-

from jsonfield.fields import JSONField


class User(…):
    statistics = JSONField(help_text="Intelligence, Wits, etc.")
    level = models.PositiveIntegerField(default=0)

这样您就可以将 30 多个统计数据打包到一个字段中,并且仍然可以快速查询用户 level/experience。 您还可以扩展 JSONField。

好吧,我会被怪死的,但还有 MonkeyPatch 技术(我喜欢并使用但会导致可读性差)或者你可以做一个简单的快捷方式。

这里是猴子补丁,最好的部分是如果你在某处有技能索引,你可以使用它:

SKILLS = ["skill_1", "skill_2", "skill_3"]
for skill in SKILLS:
    IntegerRangeField(
        min_value=min_value, max_value=max_value
    ).contribute_to_class(MyModel, skill)

注意:当您将其保存在正确的应用程序模型文件中时,它可以很好地处理迁移

这里是快捷方式:

def new_skill(min_value=1, max_value=10, **kwargs):
    return IntegerRangeField(
        min_value=min_value, max_value=max_value, **kwargs)

class MyModel(models.Model):
    skill_1 = new_skill()
    skill_2 = new_skill()
    skill_3 = new_skill(verbose_name=_("translatable skill name"))

希望对您有所帮助。

编辑:您建议拆分重复的丑陋部分,列出其他文件中的所有字段,这样新的可能性,隐藏在抽象 class 中的巨大而不是枯燥的代码(或拆分 models.py 但更难解释)。

class RpgSkills(models.Model):
    skill_1 = new_skill()
    skill_2 = new_skill()
    skill_3 = new_skill(verbose_name=_("translatable skill name"))

    class Meta:
        abstract = True # means will not have any data table by it's own

然后在你的 models.py:

from skills_utils import RpgSkills

class Character(RpgSkills):
    name = CharField(max_length=128)

这里有更多关于抽象 classes 的信息:https://docs.djangoproject.com/en/1.7/topics/db/models/#abstract-base-classes

或者如果你是一个有趣的人,你可以把所有的东西组合在一起:

这里是工具:

def skill_fields(*skills):
    class MyAbstractModel(models.Model):
        class Meta:
            abstract = True

    for skill in skills:
        new_skill().contribute_to_class(MyAbstractModel, skill)
    return MyAbstractModel

这里的用法:

from skills_utils import skill_fields

class Character(skill_fields("skill_1", "skill_2", *settings.MY_APP_SKILL_LIST)):
    name = CharField(max_length=128)

但这最后一个只适合那些最幽默的人;-)

PS:如果我是你,我会选择快捷方式,如果你想拥有一些范围更广或更小的特殊技能,我会选择保留更多自定义的快捷方式,并且它对于任何可以接受的人来说更具可读性在你之后的项目。