django-enum.IntFlag-field
This gives you a model field that takes a Python stdlib enum.IntFlag class, stores flag combinations as an IntegerField, and allows convenient bitwise containment predicates through the Django ORM.
In other words: a bitfield in your DB, with all the conveniences of enum.IntFlag.
What is a bitfield, even
What's a bitfield? A bitfield is a field of bits :-). Like 0110.
You can use it to represent a bunch of booleans that in turn represent presence or absence of flags, or anything you want — perhaps sandwich toppings!
Let's say these 4 field positions represent Butter, Gouda, Rocket and Tomato. Then 0110 means we have a sandwich without Butter, but with Gouda and Rocket, but no Tomato.
A bitfield is thus a string of bits. And it just so happens that an integer is also a string of bits! 0110 is the number 6, look:
>>> 0b0110
6
And it just so happens that both Python and databases have facilities to work with numbers-as-bitfields or bitfields-as-numbers. A database 64-bit BigIntegerField can accommodate a Python enum.IntFlag class with up to 63 members. The first member gets the bit position for the number 1 (2⁰, thus bit position 0), and since the maximum positive value of a 64-bit signed integer is 2⁶³-1, and since we need powers of 2 for our members... that leaves bits 0-62, and thus 63 members.
When you use this ModelField, a Django check will validate your associated enum.IntFlag class to make sure your values are within bounds.
Installing
pip install django-enum.IntFlag-field. Or equivalent.
There's no need to add anything to your settings.INSTALLED_APPS.
How do I even
Example model and parafernalia, inspired by the test models.py:
from enum import IntFlag, auto
from django.db import models
from enum_intflagfield import IntFlagField
class SandwichTopping(IntFlag):
BUTTER = auto() # becomes 1 (2**0)
GOUDA = auto() # becomes 2 (2**1)
ROCKET = auto() # becomes 4 (2**2)
TOMATO = auto() # becomes 8 (2**3)
HUMMUS = auto() # becomes 16 (2**4)
class Sandwich(models.Model):
class Meta:
constraints = [
models.CheckConstraint(
check=models.Q(toppings__contains_noneof=IntFlagField.complement(SandwichTopping)),
name='forbid_spurious_bits',
violation_error_message='Field contains spurious bits (bits not representing any Topping member)',
)
]
name = models.CharField(primary_key=True)
toppings = IntFlagField(choices=SandwichTopping, unique=True)
And then you'd use it like so — example adapted from the tests:
>>> Sandwich.objects.create(
name='Healthy',
toppings=SandwichTopping.ROCKET | SandwichTopping.TOMATO | SandwichTopping.HUMMUS,
)
<Sandwich: Sandwich object (Healthy)>
# gives you the sandwich you just saved — on readback, the integer value is neatly converted into an IntFlag combo.
# 28 is the numeric value that was actually stored in the database — as with all Enums, you can get at it with `.value`.
>>> Sandwich.objects.get(name='Healthy').toppings
<SandwichTopping.ROCKET|TOMATO|HUMMUS: 28>
# gives you all sandwiches that have *ONE OR MORE* of (Butter, Hummus) toppings
>>> Sandwich.objects.filter(toppings__contains_anyof=SandwichTopping.BUTTER | SandwichTopping.HUMMUS)
<QuerySet [<Sandwich: Sandwich object (Healthy)>]>
# gives you all sandwiches that have *AT LEAST ALL OF* (Rocket, Tomato) toppings
>>> Sandwich.objects.filter(toppings__contains=SandwichTopping.ROCKET | SandwichTopping.TOMATO)
<QuerySet [<Sandwich: Sandwich object (Healthy)>]>
# gives you all sandwiches that have *NONE OF* (Butter, Gouda) toppings
>>> Sandwich.objects.exclude(toppings__contains_anyof=SandwichTopping.BUTTER | SandwichTopping.GOUDA)
<QuerySet [<Sandwich: Sandwich object (Healthy)>]>
This is wrong and not even in 1NF
True! It's a field with a set of values. You can set indices on it, but it's quite an art to match index expressions to query patterns. And you'll need to manage your Enum class wisely; deleting a member means changing over from auto() to hardcoded powers-of-2 as you don't want your semantics to shift, presumably! There's advantages too, in particular CHECK constraints are straightforward.
Alternatives:
- add a boolean column for every flag. Cons: Lots of columns, thus lots of schema changes when your set-of-booleans definition change often. Performance & efficiency suffer. Pros: Much easier to design indices for.
- use a many2many design. Cons: performance & efficiency suffer. Some queries are harder.
CHECKconstraints won't allow you to express all you would be able to express on a bitfield-containing row. Pros: You'll be doing it by the book.
Yet sometimes, you really just want a blessed bitfield. I couldn't find any ergonomic ones on PyPI (2024), so I made this one.
Contributing
You may want to discuss your idea on the mailinglist first. Or just send a patch straight away, see https://git-send-email.io/ to learn how.
Metadata
Release files for django-enum.IntFlag-field 0.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_enum_intflag_field-0.0.4.tar.gz | 8.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_enum_intflag_field-0.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 16.9 kB
Release files / django_enum_intflag_field-0.0.4.tar.gz
| Download URL | django_enum_intflag_field-0.0.4.tar.gz |
|---|---|
| Size | 8.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b1c239ac121f5e4ddcab4b26767f152292e2c060069aa5a12c926abd41858d2
|
|
BLAKE2b-256 checksum How to use checksums |
4480c23bf996f4f96639ffab88c3cba78a0bced4e9ccb0926fca51597c2f5f33
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.5
|
Release files / django_enum_intflag_field-0.0.4-py3-none-any.whl
| Download URL | django_enum_intflag_field-0.0.4-py3-none-any.whl |
|---|---|
| Size | 8.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1e3ba870f54e1acb3af99da0aee5cfccb41d9504cb3e4ae8e5f75614ffec52f4
|
|
BLAKE2b-256 checksum How to use checksums |
0a2122d1e0dafc73407a8316fed36de340341f69255826beb886ba7124fbae27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.5
|