File tree 10 files changed +145
-3
lines changed
10 files changed +145
-3
lines changed Original file line number Diff line number Diff line change
1
+ .. include :: ../refs.rst
2
+
3
+ .. _admin :
4
+
5
+ ================
6
+ Use Django Admin
7
+ ================
8
+
9
+ :class: `~django_enum.fields.EnumField ` will mostly just work in the Django
10
+ :mod: `~django.contrib.admin `. There is
11
+ `one issue <https://github.com/django-commons/django-enum/issues/123 >`_ where :ref: `enums that are
12
+ not hash equivalent <hash_equivalency>` will not render value labels correctly in the
13
+ :class: `~django.contrib.admin.ModelAdmin ` :attr: `~django.contrib.admin.ModelAdmin.list_display `.
Original file line number Diff line number Diff line change @@ -29,3 +29,34 @@ The list of choice tuples for each field are:
29
29
values assigned depend on the order of declaration. This means that if the order changes
30
30
existing database values will no longer align with the enumeration values. When control over the
31
31
values is not certain it is a good idea to add integration tests that look for value changes.
32
+
33
+ .. _hash_equivalency :
34
+
35
+ Hash Equivalency
36
+ ----------------
37
+
38
+ .. tip ::
39
+
40
+ It is a good idea to make sure your enumeration instances are hash equivalent to their
41
+ primitive values. You can do this simply by inheriting from their primitive value
42
+ (e.g. ``class MyEnum(str, Enum): ``) or by using :class: `~enum.StrEnum ` and
43
+ :class: `~enum.IntEnum ` types. Any enumeration defined using :doc: `enum-properties:index `
44
+ will be hash equivalent to its values by default.
45
+
46
+ :class: `~django_enum.fields.EnumField ` automatically sets the choices tuple on the field. Django _
47
+ has logic in a number of places that handles fields with choices in a special way
48
+ (e.g. :ref: `in the admin <admin >`). For example, the choices may be converted to a dictionary
49
+ mapping values to labels. The values will be the primitive values of the enumeration not
50
+ enumeration instances and the current value of the field which may be an enumeration instance will
51
+ be searched for in the dictionary. This will fail if the enumeration instance is not hash
52
+ equivalent to its value.
53
+
54
+ To control the hashing behavior of an object, you must override its :meth: `~object.__hash__ ` and
55
+ :meth: `~object.__eq__ ` methods.
56
+
57
+ For example:
58
+
59
+ .. literalinclude :: ../../../tests/examples/models/hash_equivalency.py
60
+
61
+ .. literalinclude :: ../../../tests/examples/hash_equivalency_howto.py
62
+ :lines: 3-
Original file line number Diff line number Diff line change @@ -45,3 +45,4 @@ are possible with :class:`~django_enum.fields.EnumField`. See :ref:`enum_props`.
45
45
integrations
46
46
migrations
47
47
urls
48
+ admin
Original file line number Diff line number Diff line change 1
1
[project ]
2
2
name = " django-enum"
3
- version = " 2.2.2 "
3
+ version = " 2.2.3 "
4
4
description = " Full and natural support for enumerations as Django model fields."
5
5
requires-python = " >=3.9,<4.0"
6
6
authors = [
Original file line number Diff line number Diff line change 16
16
17
17
__all__ = ["EnumField" ]
18
18
19
- VERSION = (2 , 2 , 2 )
19
+ VERSION = (2 , 2 , 3 )
20
20
21
21
__title__ = "Django Enum"
22
22
__version__ = "." .join (str (i ) for i in VERSION )
Original file line number Diff line number Diff line change
1
+ from .models .hash_equivalency import HashEquivalencyExample
2
+
3
+
4
+ obj = HashEquivalencyExample .objects .create (
5
+ not_hash_eq = HashEquivalencyExample .NotHashEq .VALUE1 ,
6
+ hash_eq = HashEquivalencyExample .HashEq .VALUE1 ,
7
+ hash_eq_str = HashEquivalencyExample .HashEqStr .VALUE1
8
+ )
9
+
10
+ # direct comparisons to values do not work
11
+ assert obj .not_hash_eq != "V1"
12
+
13
+ # unless you have provided __eq__ or inherited from the primitive
14
+ assert obj .hash_eq == obj .hash_eq_str == "V1"
15
+
16
+ # here is the problem that can break some Django internals in rare instances:
17
+ assert dict (HashEquivalencyExample ._meta .get_field ("not_hash_eq" ).flatchoices ) == {
18
+ "V1" : "VALUE1" ,
19
+ "V2" : "VALUE2" ,
20
+ "V3" : "VALUE3"
21
+ }
22
+
23
+ try :
24
+ dict (HashEquivalencyExample ._meta .get_field ("not_hash_eq" ).flatchoices )[
25
+ HashEquivalencyExample .NotHashEq .VALUE1
26
+ ]
27
+ assert False
28
+ except KeyError :
29
+ assert True
30
+
31
+ # if we've made our enum hash equivalent though, this works:
32
+ assert dict (HashEquivalencyExample ._meta .get_field ("hash_eq" ).flatchoices )[
33
+ HashEquivalencyExample .HashEq .VALUE1
34
+ ] == "VALUE1"
35
+ assert dict (HashEquivalencyExample ._meta .get_field ("hash_eq_str" ).flatchoices )[
36
+ HashEquivalencyExample .HashEqStr .VALUE1
37
+ ] == "VALUE1"
Original file line number Diff line number Diff line change 11
11
from .gnss import GNSSReceiver , Constellation
12
12
from .gnss_vanilla import GNSSReceiverBasic
13
13
from .equivalency import EquivalencyExample
14
+ from .hash_equivalency import HashEquivalencyExample
14
15
from .extern import ExternalChoices
15
16
from .flag_howto import Group
16
17
from .text_choices import TextChoicesExample
37
38
"Constellation" ,
38
39
"GNSSReceiverBasic" ,
39
40
"EquivalencyExample" ,
41
+ "HashEquivalencyExample" ,
40
42
"ExternalChoices" ,
41
43
"Group" ,
42
44
"TextChoicesExample" ,
Original file line number Diff line number Diff line change
1
+ from enum import Enum
2
+ from django .db .models import Model
3
+ from django_enum import EnumField
4
+
5
+
6
+ class HashEquivalencyExample (Model ):
7
+ """
8
+ This example model defines three enum fields. The first uses an enum that
9
+ is not hash equivalent to its values. The second two are.
10
+ """
11
+
12
+ class NotHashEq (Enum ):
13
+ """
14
+ Enums that inherit only from :class:`~enum.Enum` are not hash equivalent
15
+ to their values by default.
16
+ """
17
+
18
+ VALUE1 = "V1"
19
+ VALUE2 = "V2"
20
+ VALUE3 = "V3"
21
+
22
+ class HashEq (Enum ):
23
+ """
24
+ We can force our Enum to be hash equivalent by overriding the necessary
25
+ dunder methods..
26
+ """
27
+
28
+ VALUE1 = "V1"
29
+ VALUE2 = "V2"
30
+ VALUE3 = "V3"
31
+
32
+ def __hash__ (self ):
33
+ return hash (self .value )
34
+
35
+ def __eq__ (self , value ) -> bool :
36
+ if isinstance (value , self .__class__ ):
37
+ return self .value == value .value
38
+ try :
39
+ return self .value == self .__class__ (value ).value
40
+ except (ValueError , TypeError ):
41
+ return False
42
+
43
+ class HashEqStr (str , Enum ): # or StrEnum on py 3.11+
44
+ """
45
+ Or we can inherit from the primitive value type.
46
+ """
47
+
48
+ VALUE1 = "V1"
49
+ VALUE2 = "V2"
50
+ VALUE3 = "V3"
51
+
52
+
53
+ not_hash_eq = EnumField (NotHashEq )
54
+ hash_eq = EnumField (HashEq )
55
+ hash_eq_str = EnumField (HashEqStr )
Original file line number Diff line number Diff line change @@ -304,6 +304,9 @@ def test_gnss_tutorial_vanilla(self):
304
304
def test_equivalency_howto (self ):
305
305
from tests .examples import equivalency_howto
306
306
307
+ def test_hash_equivalency_howto (self ):
308
+ from tests .examples import hash_equivalency_howto
309
+
307
310
def test_extern_howto (self ):
308
311
from tests .examples import extern_howto
309
312
You can’t perform that action at this time.
0 commit comments