Better choices library for Django web framework.
This library was written for Python 3.7+ and will not work in any earlier versions.
pip install django-better-choices
To start defining better choices, you need first to import the Choices
class.
from django_better_choices import Choices
The choices can be defined with overriding Choices
class.
class PageStatus(Choices):
CREATED = "Created"
PENDING = Choices.Value("Pending", help_text="This set status to pending")
ON_HOLD = Choices.Value("On Hold", value="custom_on_hold")
VALID = Choices.Subset("CREATED", "ON_HOLD")
INVISIBLE = Choices.Subset("PENDING", "ON_HOLD")
class InternalStatus(Choices):
REVIEW = _("On Review")
@classmethod
def get_help_text(cls):
return tuple(
value.help_text
for value in cls.values()
if hasattr(value, "help_text")
)
Choices class key can be any public identifier (i.e. not starting with underscore
_
). Overridden choices classes cannot be initialised to obtain a new instance, calling the instance will return a tuple of choice entries.
Alternatively, the choices can be defined dynamically by creating a new Choices
instance.
PageStatus = Choices("PageStatus", SUCCESS="Success", FAIL="Error", VALID=Choices.Subset("SUCCESS"))
The first
name
parameter ofChoices
constructor is optional and required only for better representation of the returned instance.
You can access choices values using dot notation and with getattr()
.
value_created = PageStatus.CREATED
value_review = PageStatus.InternalStatus.REVIEW
value_on_hold = getattr(PageStatus, "ON_HOLD")
Choices.Value
can hold any typing.Hashable
value and once compiled equals to this value. In addition to display
parameter, other optional parameters can be specified in Choices.Value
constructor (see class definition example).
print( PageStatus.CREATED ) # 'created'
print( PageStatus.ON_HOLD ) # 'custom_on_hold'
print( PageStatus.PENDING.display ) # 'Pending'
print( PageStatus.PENDING.help_text ) # 'This set status to pending'
PageStatus.ON_HOLD == "custom_on_hold" # True
PageStatus.CREATED == PageStatus.CREATED # True
class Rating(Choices):
VERY_POOR = Choices.Value("Very poor", value=1)
POOR = Choices.Value("Poor", value=2)
OKAY = Choices.Value("Okay", value=3, alt="Not great, not terrible")
GOOD = Choices.Value("Good", value=4)
VERY_GOOD = Choices.Value("Very good", value=5)
print( Rating.VERY_GOOD ) # 5
print( Rating.OKAY.alt ) # 'Not great, not terrible'
print( {4: "Alright"}[Rating.GOOD] ) # 'Alright'
Instance of
Choices.Value
class cannot be modified after initialisation. All native non-magic methods can be overridden inChoices.Value
custom parameters.
Search in choices is performed by value.
"created" in PageStatus # True
"custom_on_hold" in PageStatus # True
"on_hold" in PageStatus # False
value = PageStatus["custom_on_hold"] # ValueType('custom_on_hold')
value = PageStatus.get("on_hold", 123.45) # 123.45
key = PageStatus.get_key("created") # 'CREATED'
Subsets are used to group several values together (see class definition example) and search by a specific value.
"custom_on_hold" in PageStatus.VALID # True
PageStatus.CREATED in PageStatus.VALID # True
Choices.Subset
is a subclass oftuple
, which is compiled to inner choices class after its definition. All internal or custom choices class methods or properties will be available in a subset class (see "Custom methods" section).
Subsets of choices can be dynamically extracted with extract()
method.
PageStatus.extract("CREATED", "ON_HOLD") # Choices('PageStatus.Subset', CREATED, ON_HOLD)
PageStatus.VALID.extract("ON_HOLD") # Choices('PageStatus.VALID.Subset', ON_HOLD)
The opposite action to extract()
is exclude()
. It is used to exclude values from choices class or a subset and return remaining values as a new subset.
PageStatus.exclude("CREATED", "ON_HOLD") # Choices('PageStatus.Subset', PENDING)
PageStatus.VALID.exclude("ON_HOLD") # Choices('PageStatus.VALID.Subset', CREATED)
Choices class implements __iter__
magic method, hence choices are iterable that yield choice entries (i.e. (value, display)
). Methods items()
, keys()
and values()
can be used to return tuples of keys and values combinations.
for value, display in PageStatus: # can also be used as callable, i.e. PageStatus()
print( value, display )
for key, value in PageStatus.items():
print( key, value, value.display )
for key in PageStatus.keys():
print( key )
for value in PageStatus.values():
print( value, value.display, value.__choice_entry__ )
Additional displays()
method is provided for choices and subsets to extract values display strings.
for display in PageStatus.displays():
print( display )
for display in PageStatus.SUBSET.displays():
print( display )
Iteration methods
items()
,keys()
,values()
,displays()
, as well as class constructor can accept keyword arguments to filter collections based on custom parameters, e.g.PageStatus.values(help_text="Some", special=123)
.
Choices class and subsets support standard set operations: union (|
), intersection (&
), difference (-
), and symmetric difference (^
).
PageStatus.VALID | PageStatus.INVISIBLE # Choices(CREATED, ON_HOLD, PENDING)
PageStatus.VALID & PageStatus.INVISIBLE # Choices(ON_HOLD)
PageStatus.VALID - PageStatus.INVISIBLE # Choices(CREATED)
PageStatus.VALID ^ PageStatus.INVISIBLE # Choices(CREATED, PENDING)
All custom choices class methods or properties (non-values) will be available in all subsets.
PageStatus.get_help_text()
PageStatus.VALID.get_help_text()
PageStatus.extract("PENDING", "ON_HOLD").get_help_text()
PageStatus.VALID.extract("ON_HOLD").get_help_text()
Choices fully support class inheritance. All child choices classes have access to parent, grandparent, etc. values and custom methods.
class NewPageStatus(PageStatus):
ARCHIVED = "Archived"
ON_HOLD = Choices.Value("On Hold", value="on-hold") # override parent value
INACTIVE = Choices.Subset("ON_HOLD", "ARCHIVED")
print( NewPageStatus.CREATED ) # 'created'
print( NewPageStatus.ARCHIVED ) # 'archived'
print( NewPageStatus.ON_HOLD ) # 'on-hold'
Better choices are not different from the original Django choices in terms of usage in models.
class Page(models.Model):
status = models.CharField(choices=PageStatus, default=PageStatus.CREATED)
Better choices are fully supported by Django migrations and debug toolbar.
Better choices are compatible with standard Django models manipulation.
page = Page.objects.get(pk=1)
page.status = PageStatus.PENDING
page.save()
Run python tests.py
for testing.
Library is available under the MIT license. The included LICENSE file describes this in detail.