Package: django-better-choices
Django Better ChoicesBetterchoiceslibrary for Django web framework.RequirementsThis library was written for Python 3.7+ and will not work in any earlier versions.Installpip install django-better-choicesUsageTo start defining better choices, you need first to import theChoicesclass.fromdjango_better_choicesimportChoicesClass definitionThe choices can be defined with overridingChoicesclass.classPageStatus(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")classInternalStatus(Choices):REVIEW=_("On Review")@classmethoddefget_help_text(cls):returntuple(value.help_textforvalueincls.values()ifhasattr(value,"help_text"))Choices class key can be anypublicidentifier (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.Inline definitionAlternatively, the choices can be defined dynamically by creating a newChoicesinstance.PageStatus=Choices("PageStatus",SUCCESS="Success",FAIL="Error",VALID=Choices.Subset("SUCCESS"))The firstnameparameter ofChoicesconstructor is optional and required only for better representation of the returned instance.Value accessorsYou can access choices values using dot notation and withgetattr().value_created=PageStatus.CREATEDvalue_review=PageStatus.InternalStatus.REVIEWvalue_on_hold=getattr(PageStatus,"ON_HOLD")Values and value parametersChoices.Valuecan hold anytyping.Hashablevalue and once compiled equals to this value. In addition todisplayparameter, other optional parameters can be specified inChoices.Valueconstructor (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"# TruePageStatus.CREATED==PageStatus.CREATED# TrueclassRating(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)# 5print(Rating.OKAY.alt)# 'Not great, not terrible'print({4:"Alright"}[Rating.GOOD])# 'Alright'Instance ofChoices.Valueclass cannot be modified after initialisation. All native non-magic methods can be overridden inChoices.Valuecustom parameters.Search in choicesSearch in choices is performed by value."created"inPageStatus# True"custom_on_hold"inPageStatus# True"on_hold"inPageStatus# Falsevalue=PageStatus["custom_on_hold"]# ValueType('custom_on_hold')value=PageStatus.get("on_hold",123.45)# 123.45key=PageStatus.get_key("created")# 'CREATED'Search in subsetsSubsets are used to group several values together (see class definition example) and search by a specific value."custom_on_hold"inPageStatus.VALID# TruePageStatus.CREATEDinPageStatus.VALID# TrueChoices.Subsetis 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).Extract subsetSubsets of choices can be dynamically extracted withextract()method.PageStatus.extract("CREATED","ON_HOLD")# Choices('PageStatus.Subset', CREATED, ON_HOLD)PageStatus.VALID.extract("ON_HOLD")# Choices('PageStatus.VALID.Subset', ON_HOLD)Exclude valuesThe opposite action toextract()isexclude(). 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 iterationChoices class implements__iter__magic method, hence choices are iterable that yield choice entries (i.e.(value, display)). Methodsitems(),keys()andvalues()can be used to return tuples of keys and values combinations.forvalue,displayinPageStatus:# can also be used as callable, i.e. PageStatus()print(value,display)forkey,valueinPageStatus.items():print(key,value,value.display)forkeyinPageStatus.keys():print(key)forvalueinPageStatus.values():print(value,value.display,value.__choice_entry__)Additionaldisplays()method is provided for choices and subsets to extract values display strings.fordisplayinPageStatus.displays():print(display)fordisplayinPageStatus.SUBSET.displays():print(display)Iteration methodsitems(),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).Set operationsChoices class and subsets support standard set operations:union(|),intersection(&),difference(-), andsymmetric 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)Custom methodsAll 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()InheritanceChoices fully support class inheritance. All child choices classes have access to parent, grandparent, etc. values and custom methods.classNewPageStatus(PageStatus):ARCHIVED="Archived"ON_HOLD=Choices.Value("On Hold",value="on-hold")# override parent valueINACTIVE=Choices.Subset("ON_HOLD","ARCHIVED")print(NewPageStatus.CREATED)# 'created'print(NewPageStatus.ARCHIVED)# 'archived'print(NewPageStatus.ON_HOLD)# 'on-hold'Django model fieldsBetter choices are not different from the original Django choices in terms of usage in models.classPage(models.Model):status=models.CharField(choices=PageStatus,default=PageStatus.CREATED)Better choices are fully supported by Django migrations and debug toolbar.Saving choices on modelsBetter choices are compatible with standard Django models manipulation.page=Page.objects.get(pk=1)page.status=PageStatus.PENDINGpage.save()TestsRunpython tests.pyfor testing.LicenseLibrary is available under the MIT license. The included LICENSE file describes this in detail.