跳转到主要内容

Django表单和模型的枚举实现。

项目描述

https://travis-ci.org/ambv/dj.choices.png

这是一种更清晰的方式来指定模型和表单字段的选择。一个基本示例

>>> from dj.choices import Choices
>>> class Gender(Choices):
...   _ = Choices.Choice
...
...   male = _("Male")
...   female = _("Female")
...
>>> Gender()
[(1, u'Male'), (2, u'Female')]
>>> Gender.male
<Choice: male (id: 1)>
>>> Gender.female
<Choice: female (id: 2)>
>>> Gender.male.id
1
>>> Gender.male.desc
u'Male'
>>> Gender.male.raw
'Male'
>>> Gender.male.name
u'male'
>>> Gender.from_name("male")
<Choice: male (id: 1)>
>>> Gender.id_from_name("male")
1
>>> Gender.raw_from_name("male")
'Male'
>>> Gender.desc_from_name("male")
u'Male'
>>> Gender.name_from_id(2)
'female'
>>> Gender.name_from_id(3)
Traceback (most recent call last):
...
ValueError: Nothing found for '3'.
>>> Gender.from_name("perez")
Traceback (most recent call last):
...
ValueError: Nothing found for 'perez'.

您定义一个选择类,将每个选择指定为类属性。这些属性是 int 子类,自动从 1 开始编号。该类提供了一些支持 DRY 原则的功能

  • 从选择类实例化的对象基本上是 Django 直接消费的 (id, 本地化描述) 对对的列表

  • 可以直接从类中检索每个属性。

  • 每个属性的元数据(例如属性名称、原始和本地化描述、数字 ID)都是可访问的。

  • 为了避免与 Python 关键字冲突,选择后缀为 _ 的,在它们的 .name 属性中将自动删除此后缀

  • 提供查找函数以帮助获取属性或其元数据。

分组选择

选择的一个最严重的问题是其弱可扩展性。例如,一个应用程序定义了一组可能的选择,如下所示

>>> class License(Choices):
...   _ = Choices.Choice
...
...   gpl = _("GPL")
...   bsd = _("BSD")
...   proprietary = _("Proprietary")
...
>>> License()
[(1, u'GPL'), (2, u'BSD'), (3, u'Proprietary')]

一切顺利,直到应用程序上线后,一段时间后开发人员想要包括 LGPL。自然的选择是在 gpl 之后添加它,但当我们这样做时,索引会中断。另一方面,在定义的末尾添加新条目看起来很丑,并且会使 UI 中的组合框以反直觉的方式排序。分组允许我们通过在选择的类中显式定义结构来解决这个问题

>>> class License(Choices):
...   _ = Choices.Choice
...
...   COPYLEFT = Choices.Group(0)
...   gpl = _("GPL")
...
...   PUBLIC_DOMAIN = Choices.Group(100)
...   bsd = _("BSD")
...
...   OSS = Choices.Group(200)
...   apache2 = _("Apache 2")
...
...   COMMERCIAL = Choices.Group(300)
...   proprietary = _("Proprietary")
...
>>> License()
[(1, u'GPL'), (101, u'BSD'), (201, u'Apache 2'), (301, u'Proprietary')]

这使开发人员可以在以后添加更多每个组的许可证

>>> class License(Choices):
...   _ = Choices.Choice
...
...   COPYLEFT = Choices.Group(0)
...   gpl_any = _("GPL, any")
...   gpl2 = _("GPL 2")
...   gpl3 = _("GPL 3")
...   lgpl = _("LGPL")
...   agpl = _("Affero GPL")
...
...   PUBLIC_DOMAIN = Choices.Group(100)
...   bsd = _("BSD")
...   public_domain = _("Public domain")
...
...   OSS = Choices.Group(200)
...   apache2 = _("Apache 2")
...   mozilla = _("MPL")
...
...   COMMERCIAL = Choices.Group(300)
...   proprietary = _("Proprietary")
...
>>> License()
[(1, u'GPL, any'), (2, u'GPL 2'), (3, u'GPL 3'), (4, u'LGPL'),
 (5, u'Affero GPL'), (101, u'BSD'), (102, u'Public domain'),
 (201, u'Apache 2'), (202, u'MPL'), (301, u'Proprietary')]

注意行为

  • 开发人员重命名了 GPL 选择,但它的含义和 ID 仍然稳定

  • BSD、Apache 和专有选择的 ID 保持不变

  • 结果类是自描述的、可读的且可扩展的

作为额外的好处,显式指定的组可以在需要时使用

>>> License.COPYLEFT
<ChoiceGroup: COPYLEFT (id: 0)>
>>> License.gpl2 in License.COPYLEFT.choices
True
>>> [(c.id, c.desc) for c in License.COPYLEFT.choices]
[(1, u'GPL, any'), (2, u'GPL 2'), (3, u'GPL 3'), (4, u'LGPL'),
 (5, u'Affero GPL')]

ChoiceField

可以将选择与通用的 IntegerFieldCharField 实例一起使用。但是,当您这样做时,一些小的 API 缺陷会很快出现。首先,当您定义字段时,您必须实例化选择类,并且默认值必须显式转换为正确的类型

color = models.IntegerField(choices=Color(), default=Color.green.id)

其次,当从模型获取属性时,必须将其转换为选择实例才能对其进行任何有趣的操作

>>> obj = Model.objects.get(pk=3)
>>> obj.color
3
>>> Color.from_id(obj.color)
<Choice: Blue (id: 3, name: blue)>

为了克服这些问题,dj.choices.fields 包中提供了一个 ChoiceField。它在数据库级别基于整数,但 API 公开了 Choice 实例。这有助于定义方面

color = ChoiceField(choices=Color, default=Color.green)

以及访问方面

>>> obj = Model.objects.get(pk=3)
>>> obj.color
<Choice: Blue (id: 3, name: blue)>
>>> obj.color = Color.green
>>> obj.save()
>>> Model.objects.get(pk=3).color
<Choice: Green (id: 2, name: green)>

对于渲染表单,字段将强制转换为整数值。这也意味着在需要接受 Choice 实例的地方也可以接受整数。

高级功能

过滤

开发人员可以指定所有可能的选项以供将来使用,然后在创建选择时仅过滤出当前适用的值

>>> class Language(Choices):
...   _ = Choices.Choice
...
...   de = _("German")
...   en = _("English")
...   fr = _("French")
...   pl = _("Polish")
...
>>> Language()
[(1, u'German'), (2, u'English'), (3, u'French'), (4, u'Polish')]
>>> Language(filter=("en", "pl"))
[(2, u'English'), (4, u'Polish')]

这保持了ID和排序的完整性,具有很大的优势。

自定义项目格式

还可以通过提供工厂函数来改变对构建方式。例如,为了在settings.py文件中的LANGUAGES设置中使用上面定义的选择类,可以指定

>>> Language(item=lambda choice: (choice.name, choice.raw))
[(u'de', 'German'), (u'en', 'English'), (u'fr', 'French'),
 (u'pl', 'Polish')]

选择项的额外属性

每个选择项都可以使用extra()方法接收额外的参数

>>> class Python(Choices):
...   _ = Choices.Choice
...
...   cpython = _("CPython").extra(language='C')
...   pypy = _("PyPy").extra(language='Python')
...   jython = _("Jython").extra(language='Java')
...   iron_python = _("IronPython").extra(language='C#')

这给每个选择项添加了一个language属性,这样您就可以像这样获取它

>>> Python.jython.language
'Java'

这允许在以后使用模型或表单时进行多态属性访问。例如,假设您有一个简单的模型

>>> class Library(models.Model):
...   name = models.CharField(max_length=100)
...   python_kind = models.IntegerField(choices=Python(), default=Python.cpython.id)

在这种情况下,要获取实现语言,您可以这样做

>>> library = Library.objects.get(name='dj.choices')
>>> Python.from_id(library.python_kind).language
'C'

这样,您的用户代码就不需要任何条件语句或依赖于选择类状态的字典。如果您要向其中添加另一个选择项,无需更改用户代码以支持它。这也支持DRY原则,因为选择类成为此类配置的唯一位置。

选择组上的额外属性

不出所料,选择组也可以有额外的属性。这些属性将被该组中的选择项继承,并在必要时可以覆盖。例如

>>> class ProfileChange(Choices):
...   _ = Choices.Choice
...
...   USER = Choices.Group(0).extra(icon='bookkeeping.png', is_public=True)
...   email = _("e-mail").extra(is_public=False)
...   first_name = _("first name")
...   last_name = _("last name")
...
...   BASIC_INFO = Choices.Group(10).extra(icon='bookkeeping.png', is_public=True)
...   birth_date = _("birth date").extra(icon='calendar.png')
...   gender = _("gender").extra(icon='male_female.png')
...   country = _("country")
...   city = _("city")
...
...   CONTACT_INFO = Choices.Group(20).extra(icon='contactbook.png', is_public=False)
...   skype = _("Skype ID")
...   icq = _("ICQ number")
...   msn = _("MSN login")
...   xfire = _("X-Fire login")
...   irc = _("IRC info").extra(is_public=True)

在这种情况下,发生适当的继承

>>> ProfileChange.first_name.is_public
True
>>> ProfileChange.email.is_public
False
>>> ProfileChange.country.icon
'bookkeeping.png'
>>> ProfileChange.birth_date.icon
'calendar.png'

预定义的选择

有几个选择类在Web应用中非常常见,因此已经提供:国家、性别和语言。

我该如何运行测试?

最简单的方法是运行

$ DJANGO_SETTINGS_MODULE="dj._choicestestproject.settings" django-admin.py test

变更日志

0.11.0

  • 放弃对Django < 1.4和Python 2.6的兼容性

  • 添加对Django 1.9和1.10的支持(使用from_db_value而不是SubfieldBase)

  • 添加对Python 3.5的支持

0.10.0

  • 在Django 1.6和Django 1.7上运行并测试通过;现在还测试了Python 3.4

0.9.2

  • 由于Carl van Tonder的支持,现在也支持Python 2.6

0.9.1

  • 迟来的Python 3支持(考虑为实验性)

0.9.0

  • 选择现在是int子类,因此您可以直接使用选择项而不是choice.id,并且int(choice)始终是安全的

  • unicode(choice)现在等同于choice.desc

  • 修复了具有ChoiceFields的模型上的get_FIELD_display()

0.8.6

  • 对于ChoiceField的定义之外的选择值,现在正确显示验证错误而不是抛出异常;修复了问题#2

  • ChoiceField可以具有default=None

  • 修复了0.8.5回归,其中__gt(e)和__lt(e)无法用于ChoiceField查询

  • 小改进

0.8.5

  • ChoiceField现在可以正确地使用South迁移

  • 具有ChoiceFields的模型现在可以使用__in、__range和__isnull查询;修复了问题#1

0.8.4

  • 如果底层IntegerField返回long而不是int,则正确支持ChoiceField

  • 对字节字符串的__unicode__进行了小修正

0.8.3

  • MANIFEST.in之前缺失,这使得源分发难以安装

0.8.2

  • ChoiceField 引入

  • 额外的属性注入 API 现已公开并有文档

0.8.1

  • 为向后兼容性暂时恢复了旧访问器(未记录,将在 1.0 中移除)

  • 较小的文档修正

0.8.0

  • 代码与 lck.django 分离

  • 访问器 API 符合 PEP8 规范

作者

Łukasz Langa 组装。Python 2.6 支持 Carl van Tonder

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源代码分发

dj.choices-0.11.0.tar.gz (28.4 kB 查看哈希值)

上传时间 源代码

支持