Django表单和模型的枚举实现。
项目描述
这是一种更清晰的方式来指定模型和表单字段的选择。一个基本示例
>>> 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
可以将选择与通用的 IntegerField 和 CharField 实例一起使用。但是,当您这样做时,一些小的 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 规范
项目详情
dj.choices-0.11.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 2c23f03ead45d5710512c52988f105ffed4b0cabb8a2cdcbf45108153fe25517 |
|
MD5 | c80bbd76e9918e79846ff8025c2e0db4 |
|
BLAKE2b-256 | fecfdd88a62c3bd0258b56476613831bbc040bf43b6d96fa5c91a0c4b556bb51 |