Skip to content

Commit 05276f7

Browse files
committed
update readme with flag example
1 parent 38218c6 commit 05276f7

File tree

4 files changed

+106
-29
lines changed

4 files changed

+106
-29
lines changed

README.md

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,19 @@
2323

2424
Full and natural support for [enumerations](https://docs.python.org/3/library/enum.html#enum.Enum) as Django model fields.
2525

26-
Many packages aim to ease usage of Python enumerations as model fields. Most were made obsolete when Django provided ``TextChoices`` and ``IntegerChoices`` types. The motivation for [django-enum](https://django-enum.readthedocs.io) was to:
26+
Many packages aim to ease usage of Python enumerations as model fields. Most were superseded when Django provided ``TextChoices`` and ``IntegerChoices`` types. The motivation for [django-enum](https://django-enum.readthedocs.io) was to:
2727

2828
* Work with any Python PEP 435 Enum including those that do not derive from Django's TextChoices and IntegerChoices.
29-
* Always automatically coerce fields to instances of the Enum type.
29+
* Coerce fields to instances of the Enum type by default.
3030
* Allow strict adherence to Enum values to be disabled.
3131
* Handle migrations appropriately. (See [migrations](https://django-enum.readthedocs.io/en/latest/usage.html#migrations))
3232
* Integrate as fully as possible with [Django's](https://www.djangoproject.com) existing level of enum support.
33-
* Support [enum-properties](https://pypi.org/project/enum-properties) and dataclass enumerations to enable richer enumeration types.
33+
* Support [enum-properties](https://pypi.org/project/enum-properties) to enable richer enumeration types. (A less awkward alternative to dataclass enumerations with more features)
3434
* Represent enum fields with the smallest possible column type.
35-
* Provide bit mask functionality using standard Python Flag enumerations.
35+
* Support bit mask queries using standard Python Flag enumerations.
3636
* Be as simple and light-weight an extension to core [Django](https://www.djangoproject.com) as possible.
37-
* Optionally enforce enumeration value consistency at the database level using check constraints.
37+
* Enforce enumeration value consistency at the database level using check constraints by default.
38+
* (TODO) Support native database enumeration column types when available.
3839

3940
[django-enum](https://django-enum.readthedocs.io) works in concert with [Django's](https://www.djangoproject.com) built in ``TextChoices`` and ``IntegerChoices`` to provide a new model field type, ``EnumField``, that resolves the correct native [Django](https://www.djangoproject.com) field type for the given enumeration based on its value type and range. For example, ``IntegerChoices`` that contain values between 0 and 32767 become [PositiveSmallIntegerField](https://docs.djangoproject.com/en/stable/ref/models/fields/#positivesmallintegerfield).
4041

@@ -62,8 +63,8 @@ Many packages aim to ease usage of Python enumerations as model fields. Most wer
6263
txt_enum = EnumField(TextEnum, null=True, blank=True)
6364

6465
# this is equivalent to
65-
# PositiveSmallIntegerField(choices=IntEnum.choices)
66-
int_enum = EnumField(IntEnum)
66+
# PositiveSmallIntegerField(choices=IntEnum.choices, default=IntEnum.ONE.value)
67+
int_enum = EnumField(IntEnum, default=IntEnum.ONE)
6768
```
6869

6970
``EnumField`` **is more than just an alias. The fields are now assignable and accessible as their enumeration type rather than by-value:**
@@ -82,24 +83,29 @@ Many packages aim to ease usage of Python enumerations as model fields. Most wer
8283
assert instance.int_enum.value == 3
8384
```
8485

85-
[django-enum](https://django-enum.readthedocs.io) also provides ``IntegerChoices`` and ``TextChoices`` types that extend from [enum-properties](https://pypi.org/project/enum-properties) which makes possible very rich enumeration fields.
86+
## Complex Enumerations
87+
88+
[django-enum](https://django-enum.readthedocs.io) supports enum types that do not derive from Django's ``IntegerChoices`` and ``TextChoices``. This allows us to use other libs like [enum-properties](https://pypi.org/project/enum-properties) which makes possible very rich enumeration fields:
8689

8790
``?> pip install enum-properties``
8891

8992
```python
9093

91-
from enum_properties import s
92-
from django_enum import TextChoices # use instead of Django's TextChoices
94+
from enum_properties import StrEnumProperties
9395
from django.db import models
9496

9597
class TextChoicesExample(models.Model):
9698

97-
class Color(TextChoices, s('rgb'), s('hex', case_fold=True)):
99+
class Color(StrEnumProperties):
100+
101+
label: Annotated[str, Symmetric()]
102+
rgb: Annotated[t.Tuple[int, int, int], Symmetric()]
103+
hex: Annotated[str, Symmetric(case_fold=True)]
98104

99-
# name value label rgb hex
100-
RED = 'R', 'Red', (1, 0, 0), 'ff0000'
101-
GREEN = 'G', 'Green', (0, 1, 0), '00ff00'
102-
BLUE = 'B', 'Blue', (0, 0, 1), '0000ff'
105+
# name value label rgb hex
106+
RED = "R", "Red", (1, 0, 0), "ff0000"
107+
GREEN = "G", "Green", (0, 1, 0), "00ff00"
108+
BLUE = "B", "Blue", (0, 0, 1), "0000ff"
103109

104110
# any named s() values in the Enum's inheritance become properties on
105111
# each value, and the enumeration value may be instantiated from the
@@ -143,13 +149,29 @@ Many packages aim to ease usage of Python enumerations as model fields. Most wer
143149
assert TextChoicesExample.objects.filter(color='FF0000').first() == instance
144150
```
145151

146-
Consider using [django-render-static](https://pypi.org/project/django-render-static) to make your enumerations [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) across the full stack!
152+
## Flag Support
147153

148-
Please report bugs and discuss features on the [issues page](https://github.com/bckohan/django-enum/issues).
154+
``FlagEnum`` types are also seamlessly supported! This allows a database column to behave like a bit mask and is an alternative to multiple boolean columns. There are (mostly positive) performance implications for using a bit mask instead of booleans depending on the size of the bit mask and the types of queries you will run against it. For bit masks more than a few bits long the size reduction both speeds up queries and reduces the required storage space. See the documentation for [discussion and benchmarks]().
149155

150-
[Contributions](https://github.com/bckohan/django-enum/blob/main/CONTRIBUTING.md) are encouraged!
156+
```python
157+
158+
class Permissions(IntFlag):
159+
160+
READ = 0**2
161+
WRITE = 1**2
162+
EXECUTE = 2**3
163+
164+
165+
class FlagExample(models.Model):
166+
167+
permissions = EnumField(Permissions)
151168

152-
[Full documentation at read the docs.](https://django-enum.readthedocs.io)
169+
170+
FlagExample.objects.create(permissions=Permissions.READ | Permissions.WRITE)
171+
172+
# get all models with RW:
173+
FlagExample.objects.filter(permissions__all=Permissions.READ | Permissions.WRITE)
174+
```
153175

154176
## Installation
155177

@@ -176,3 +198,13 @@ Like with Django, Postgres is the preferred database for support. The full test
176198
**See the [latest test runs](https://github.com/bckohan/django-enum/actions/workflows/test.yml) for our current test matrix**
177199

178200
*For Oracle, only the latest version of the free database is tested against the minimum and maximum supported versions of Python, Django and the cx-Oracle driver.*
201+
202+
## Further Reading
203+
204+
Consider using [django-render-static](https://pypi.org/project/django-render-static) to make your enumerations [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) across the full stack!
205+
206+
Please report bugs and discuss features on the [issues page](https://github.com/bckohan/django-enum/issues).
207+
208+
[Contributions](https://github.com/bckohan/django-enum/blob/main/CONTRIBUTING.md) are encouraged!
209+
210+
[Full documentation at read the docs.](https://django-enum.readthedocs.io)

tests/enum_prop/models.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from django.db import models
22
from django.urls import reverse
3-
from enum_properties import s
3+
from enum_properties import StrEnumProperties, IntEnumProperties, Symmetric
4+
from typing_extensions import Annotated
5+
import typing as t
46

57
from django_enum import EnumField, TextChoices
68
from tests.enum_prop.enums import (
@@ -128,20 +130,28 @@ class Meta:
128130

129131

130132
class MyModel(models.Model):
131-
class TextEnum(models.TextChoices):
133+
class TextEnum(StrEnumProperties):
134+
label: str
135+
132136
VALUE0 = "V0", "Value 0"
133137
VALUE1 = "V1", "Value 1"
134138
VALUE2 = "V2", "Value 2"
135139

136-
class IntEnum(models.IntegerChoices):
140+
class IntEnum(IntEnumProperties):
141+
label: str
142+
137143
ONE = 1, "One"
138144
TWO = (
139145
2,
140146
"Two",
141147
)
142148
THREE = 3, "Three"
143149

144-
class Color(TextChoices, s("rgb"), s("hex", case_fold=True)):
150+
class Color(StrEnumProperties):
151+
label: Annotated[str, Symmetric()]
152+
rgb: Annotated[t.Tuple[int, int, int], Symmetric()]
153+
hex: Annotated[str, Symmetric(case_fold=True)]
154+
145155
# name value label rgb hex
146156
RED = "R", "Red", (1, 0, 0), "ff0000"
147157
GREEN = "G", "Green", (0, 1, 0), "00ff00"

tests/examples/models.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
from django.db import models
2-
from enum_properties import p, s
3-
2+
from enum import IntFlag
3+
from enum_properties import s, Symmetric
4+
import typing as t
5+
from typing_extensions import Annotated
46
from django_enum import EnumField, FlagChoices, IntegerChoices, TextChoices
57

68

79
class Map(models.Model):
810

9-
class MapBoxStyle(IntegerChoices, s("slug", case_fold=True), p("version")):
11+
class MapBoxStyle(IntegerChoices):
1012
"""
1113
https://docs.mapbox.com/api/maps/styles/
1214
"""
1315

16+
slug: Annotated[str, Symmetric(case_fold=True)]
17+
version: int
18+
1419
_symmetric_builtins_ = ["name", s("label", case_fold=True), "uri"]
1520

1621
# name value label slug version
@@ -68,7 +73,10 @@ class EnumType(TextChoices):
6873

6974
class TextChoicesExample(models.Model):
7075

71-
class Color(TextChoices, s("rgb"), s("hex", case_fold=True)):
76+
class Color(TextChoices):
77+
78+
rgb: Annotated[t.Tuple[int, int, int], Symmetric()]
79+
hex: Annotated[str, Symmetric(case_fold=True)]
7280

7381
# name value label rgb hex
7482
RED = "R", "Red", (1, 0, 0), "ff0000"
@@ -99,13 +107,22 @@ class IntEnum(models.IntegerChoices):
99107
)
100108
THREE = 3, "Three"
101109

110+
111+
class Permissions(IntFlag):
112+
113+
READ = 0**2
114+
WRITE = 1**2
115+
EXECUTE = 2**3
116+
102117
# this is equivalent to:
103118
# CharField(max_length=2, choices=TextEnum.choices, null=True, blank=True)
104119
txt_enum = EnumField(TextEnum, null=True, blank=True)
105120

106121
# this is equivalent to
107-
# PositiveSmallIntegerField(choices=IntEnum.choices)
108-
int_enum = EnumField(IntEnum)
122+
# PositiveSmallIntegerField(choices=IntEnum.choices, default=IntEnum.ONE.value)
123+
int_enum = EnumField(IntEnum, default=IntEnum.ONE)
124+
125+
permissions = EnumField(Permissions, null=True, blank=True)
109126

110127

111128
class BitFieldExample(models.Model):

tests/test_examples.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,21 @@ def test_no_coerce(self):
181181
self.assertTrue(obj.non_strict == "1")
182182
self.assertTrue(isinstance(obj.non_strict, str))
183183
self.assertFalse(isinstance(obj.non_strict, NoCoerceExample.EnumType))
184+
185+
def test_flag_readme_ex(self):
186+
from tests.examples.models import MyModel
187+
188+
MyModel.objects.create(
189+
permissions=MyModel.Permissions.READ | MyModel.Permissions.WRITE,
190+
)
191+
192+
MyModel.objects.create(
193+
permissions=MyModel.Permissions.READ | MyModel.Permissions.EXECUTE,
194+
)
195+
196+
self.assertEqual(
197+
MyModel.objects.filter(
198+
permissions__all=MyModel.Permissions.READ | MyModel.Permissions.WRITE
199+
).count(),
200+
1,
201+
)

0 commit comments

Comments
 (0)