https://github.com/mbourqui/django-echoices
Django model choices as Enum
https://github.com/mbourqui/django-echoices
django django-models enum python python3
Last synced: 12 months ago
JSON representation
Django model choices as Enum
- Host: GitHub
- URL: https://github.com/mbourqui/django-echoices
- Owner: mbourqui
- License: mit
- Created: 2017-04-21T13:01:14.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2021-08-18T17:33:03.000Z (almost 5 years ago)
- Last Synced: 2025-06-26T13:48:40.918Z (about 1 year ago)
- Topics: django, django-models, enum, python, python3
- Language: Python
- Size: 146 KB
- Stars: 3
- Watchers: 1
- Forks: 1
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
[](/)
[](/)
[](/LICENSE)
[](https://pypi.python.org/pypi/django-echoices)
[](https://travis-ci.org/mbourqui/django-echoices)
[](https://coveralls.io/github/mbourqui/django-echoices?branch=master)
# Django-EChoices
Choices for Django model fields as enumeration
## Features
* Specialized [enum types](#enum)
* Specialized [model fields](#modelfield)
* Accessible in [templates](#templates)
## Requirements
* [Python][] >= 3.5
* [Django][] >= 1.11.29
## Installation
### Using [PyPI](https://pypi.python.org/pypi/django-echoices)
1. Run `pip install django-echoices`.
### Using the source code
1. Make sure [`pandoc`](http://pandoc.org/index.html) is installed
1. Run `./pypi_packager.sh`
1. Run `pip install dist/django_echoices-x.y.z-[...].wheel`, where `x.y.z` must be replaced by the actual
version number and `[...]` depends on your packaging configuration
## Usage
### Enumeration
First, define your choices enumeration (in your `models.py` for example):
```
from echoices.enums import EChoice
class EStates(EChoice):
# format is: (value -> char or str or int, label -> str)
CREATED = ('c', 'Created')
SUBMITTED = ('s', 'Submitted')
```
### Model field
#### Regular model field
Then, either use a regular model field:
```
from django.db import models
class MyModel(models.Model):
state = models.CharField(max_length=EStates.max_value_length(),
choices=EStates.choices(),
default=EStates.CREATED.value)
```
**Note**: If your value is an `int`, you can use `models.IntegerField` instead.
#### Specialized field
You can also use specialized field. Using such a field, you will then only handle `Echoice` instances.
```
from django.db import models
from echoices.fields import make_echoicefield
class MyModel(models.Model):
# `max_length` is set automatically
state = make_echoicefield(EStates, default=EStates.CREATED)
```
**Note**: `MyModel.state` will be `Estates` instance stored in a `EStatesField` field. See [documentation](#modelfield)
for more details.
**WARNING**: requires special handling of migrations. Read more in the [documentation](#migrations).
### Derivation
You can add your own fields to the `value` and `label` ones. To do so, you have to override the __init__() and your
signature must look like: `self, value, label, *args` where you replace `*args` with your own positional arguments, as
you would do when defining a custom Enum. Do *not* call the super().__init__(), as `value` and `label` are already set
internally by `EChoice`.
As when dealing with a derived Enum, you can also add your own methods.
```
from echoices.enums import EChoice
class EMyChoices(EChoice):
"""Another variant of EChoice with additionnal content"""
MY_CHOICE = (1, 'First choice', 'my additional value')
def __init__(self, value, label, my_arg):
self.my_arg = my_arg
# Note: super().__init__() shall *not* be called!
def show_myarg(self):
"""Used as: EMyChoices.MY_CHOICE.show_myarg()"""
print(self.my_arg)
@classmethod
def show_all(cls):
"""Used as: EMyChoices.show_all()"""
print(", ".join([e.my_arg for e in list(cls)]))
```
### In templates
Assume a `Context(dict(estates=myapp.models.EStates))` is provided to the following templates.
* Fields of the `EChoice` can be accessed in the templates as:
```
{{ estates.CREATED.value }}
{{ estates.CREATED.label }}
```
* `EChoice` can also be enumerated:
```
{% for state in estates %}
{{ state.value }}
{{ state.label }}
{% endfor %}
```
## Short documentation
#### `enums.EChoice`
Base enum type. Each enum element is a tuple `(value, label)`, where [t]he first element
in each tuple is the actual value to be set on the model, and the second element is the human-readable name
[doc](https://docs.djangoproject.com/en/1.11/ref/models/fields/#choices). Values **must** be unique. Can be
derived for further customization.
#### `enums.EOrderedChoice`
Supports ordering of elements. `EOrderedChoice.choices()` takes an extra optional argument,
`order`, which supports three values: 'sorted', 'reverse' or 'natural' (default). If `sorted`, the choices are ordered
according to their value. If `reverse`, the choices are ordered according to their value as if each comparison were
reversed. If `natural`, the order is the one used when instantiating the enumeration.
#### `enums.EAutoChoice`
Generates auto-incremented numeric values. It's then used like:
```
from echoices.enums import EAutoChoice
class EStates(EAutoChoice):
# format is: label -> str
CREATED = 'Created'
SUBMITTED = 'Submitted'
```
#### API
##### Overriden EnumMeta methods
* `EChoice.__getitem__()`, such that you can retrieve an `EChoice` instance using `EChoice['my_value']`
##### Additional classmethods
* `choices()` generates the choices as expected by a Django model field
* `max_value_length()` returns the max length for the Django model field, if the values are strings
* `values()` returns a list of all the values
* `get(value, default=None)` returns the EChoice instance having that value, else returns the default
#### `fields.EChoiceField` via `fields.make_echoicefield()`
Deal directly with the enum instances instead of their DB storage value. The specialized field will be derived from a
`models.Field` subclass, the internal representation is deduced from the value type. So for example if the values are
strings, then the the `EChoiceField` will subclass `models.CharField`; and if the values are integers then it will be
`models.IntegerField`. Actually supports `str`, `int`, `float` and (non-null) `bool` as enum values.
`make_echoicefield()` will return an instance of `EChoiceField` which subclasses a field type from `models.CharField`.
The exact name of the field type will be `MyEnumNameField` in Django >= 1.9, note the suffixed 'Field'. For earlier
versions of Django, it will be `EChoiceField`.
Thus, `MyModel.my_echoice_field` will be an `EChoice` instance stored in an `EChoiceField` field.
##### Migrations
Since the field is generated with the help of a factory function, it does not exist as is as a field class in
`echoices.fields`. But, when generating a migration file, Django will set the class of the field as the resulting class
from `make_echoicefield()`, which does not exist in `echoices.fields`. This will cause the Django server to crash, as
an `AttributeError: module 'echoices.fields' has no attribute 'MyEnumNameField'` exception will be raised.
To prevent this, you have to edit the migration file and replace the instantiation of the non-existing class with a call
to `make_echoicefield()`, with the same parameters as when defining the field in your model.
For example, assume you have the following model defined in `models.py`:
```
from django.db import models
from echoices.fields import make_echoicefield
class MyModel(models.Model):
state = make_echoicefield(EStates, default=EStates.CREATED)
```
Then you would replace the generated field instantiation statement in `migrations/0001_initial.py`
```
migrations.CreateModel(
name='MyModel',
fields=[
# Replace the statement below
('state', echoices.fields.EStatesField(
echoices=app.models.EStates,
default=app.models.EStates(1))
),
],
```
with
```
('state', echoices.fields.make_echoicefield(
echoices=app.models.EStates,
default=app.models.EStates.CREATED)
),
```
#### `fields.MultipleEChoiceField`
Similar to previous fields, but supports multiple values to be selected.
[**Not yet implemented**][i03].
### Usage in templates
Assume a `Context(dict(estates=myapp.models.EStates))` is provided to the following templates.
* Fields of the `EChoice` can be accessed in the templates as:
```
{{ estates.CREATED.value }}
{{ estates.CREATED.label }}
```
* `EChoice` can also be enumerated:
```
{% for state in estates %}
{{ state.value }}
{{ state.label }}
{% endfor %}
```
[django]: https://www.djangoproject.com/ "Django"
[python]: https://www.python.org/ "Python"
[i03]: https://github.com/mbourqui/django-echoices/issues/3