Custom Django field for using enumerations of named constants

Overview

django-enumfield

Provides an enumeration Django model field (using IntegerField) with reusable enums and transition validation.

Build Status PyPi Version License Python Versions Wheel Coveralls github

Installation

Currently, we test Django versions 1.11-3.1 and Python versions 2.7, 3.4-3.8.

Install django-enumfield in your Python environment:

$ pip install django-enumfield

Upgrading from django-enumfield 1.x? See the migration guide

For use with Django versions prior to 1.8 use version 1.2.1

For use with Django versions prior to 1.11 use version 1.5

Usage

Create an Enum-class and pass it as first argument to the Django model EnumField.

from django.db import models
from django_enumfield import enum


class BeerStyle(enum.Enum):
    LAGER = 0
    STOUT = 1
    WEISSBIER = 2


class Beer(models.Model):
    style = enum.EnumField(BeerStyle, default=BeerStyle.LAGER)


# Use .get to get enum values from either name or ints
print(BeerStyle.get("LAGER"))  # <BeerStyle.LAGER: 0>
print(BeerStyle.get(1))  # <BeerStyle.STOUT: 1>
print(BeerStyle.get(BeerStyle.WEISSBIER))  # <BeerStyle.WEISSBIER: 2>

# It's also possible to use the normal enum way to get the value
print(BeerStyle(1))  # <BeerStyle.STOUT: 1>
print(BeerStyle["LAGER"])  # <BeerStyle.LAGER: 0>

# The enum value has easy access to their value and name
print(BeerStyle.LAGER.value)  # 0
print(BeerStyle.LAGER.name)  # "LAGER"

For more information about Python 3 enums (which our Enum inherits, IntEnum to be specific) checkout the docs.

Setting the default value

You can also set default value on your enum class using __default__ attribute

from django.db import models
from django_enumfield import enum


class BeerStyle(enum.Enum):
    LAGER = 0
    STOUT = 1
    WEISSBIER = 2

    __default__ = LAGER


class BeerStyleNoDefault(enum.Enum):
    LAGER = 0


class Beer(models.Model):
    style_default_lager = enum.EnumField(BeerStyle)
    style_default_stout = enum.EnumField(BeerStyle, default=BeerStyle.STOUT)
    style_default_null = enum.EnumField(BeerStyleNoDefault, null=True, blank=True)


# When you set __default__ attribute, you can access default value via
# `.default()` method of your enum class
assert BeerStyle.default() == BeerStyle.LAGER

beer = Beer.objects.create()
assert beer.style_default_larger == BeerStyle.LAGER
assert beer.style_default_stout == BeerStyle.STOUT
assert beer.style_default_null is None

Labels

You can use your own labels for Enum items

from django.utils.translation import ugettext_lazy
from django_enumfield import enum


class Animals(enum.Enum):
    CAT = 1
    DOG = 2
    SHARK = 3

    __labels__ = {
        CAT: ugettext_lazy("Cat"),
        DOG: ugettext_lazy("Dog"),
    }


print(Animals.CAT.label)  # "Cat"
print(Animals.SHARK.label)  # "SHARK"

# There's also classmethods for getting the label
print(Animals.get_label(2))  # "Dog"
print(Animals.get_label("DOG"))  # "Dog"

Validate transitions

The Enum-class provides the possibility to use transition validation.

from django.db import models
from django_enumfield import enum
from django_enumfield.exceptions import InvalidStatusOperationError


class PersonStatus(enum.Enum):
    ALIVE = 1
    DEAD = 2
    REANIMATED = 3

    __transitions__ = {
        DEAD: (ALIVE,),  # Can go from ALIVE to DEAD
        REANIMATED: (DEAD,)  # Can go from DEAD to REANIMATED
    }


class Person(models.Model):
    status = enum.EnumField(PersonStatus)

# These transitions state that a PersonStatus can only go to DEAD from ALIVE and to REANIMATED from DEAD.
person = Person.objects.create(status=PersonStatus.ALIVE)
try:
    person.status = PersonStatus.REANIMATED
except InvalidStatusOperationError:
    print("Person status can not go from ALIVE to REANIMATED")
else:
    # All good
    person.save()

In forms

The Enum-class can also be used without the EnumField. This is very useful in Django form ChoiceFields.

from django import forms
from django_enumfield import enum
from django_enumfield.forms.fields import EnumChoiceField


class GenderEnum(enum.Enum):
    MALE = 1
    FEMALE = 2

    __labels__ = {
        MALE: "Male",
        FEMALE: "Female",
    }


class PersonForm(forms.Form):
    gender = EnumChoiceField(GenderEnum)

Rendering PersonForm in a template will generate a select-box with "Male" and "Female" as option labels for the gender field.

Local Development Environment

Make sure black and isort is installed in your env with pip install -e .[dev].

Before committing run make format to apply black and isort to all files.

Comments
  • #15 Use enum34 class

    #15 Use enum34 class

    Ref #15

    Most tests are passing, but I had to drop some existing features:

    • The value of the field is an Enum, therefore self.assertTrue(isinstance(person.status, int)) fails
    • The custom Enum.label() method conflicts with the members' label property, so I had to pick one. I've picked the property
    opened by fcurella 17
  • enum.EnumField seems not to work in Django 1.10

    enum.EnumField seems not to work in Django 1.10

    In django 1.10 get_deferred_fields routine from django.db.models.base counts all enum.EnumField fields in a model as deferred fields. As a result it passes update_fields in save_base routine and raises "Cannot force an update in save() with no primary key" exception when I attempt to save new model instance.

    In django 1.9.9 the same model works okay. I am using django-enumfield==1.3b2 installed with pip

    opened by georgekpg 14
  • Added tests to show errors in django 1.10

    Added tests to show errors in django 1.10

    Adding this PR to show bug in django-enumfield in the newly released Django==1.10.

    Problem was (most probably) caused by this changeset in Django repository: https://github.com/django/django/commit/7f51876f99851fdc3fef63aecdfbcffa199c26b9, and especially by the change to get_deferred_fields.

    Let's consider this model:

    class Person(models.Model):
        example = models.CharField(max_length=100, default="foo")
        status = EnumField(PersonStatus, default=PersonStatus.ALIVE)
    

    EnumField users Python properties under the hood, that is: Person.status is a property, this causes check used by get_deferred_fields to always treat status as a deffered field (that is always "status" not in self.__dict__). This causes all kinds of fun, for example excludes status from fields that are updated.

    opened by jbzdak 9
  • Add an optional context processor that injects an 'enums' dict with all our enums

    Add an optional context processor that injects an 'enums' dict with all our enums

    Saves manually/tediously adding enum classes to view template context to check them. Invalid lookup values raise an error.

    Signed-off-by: Chris Lamb [email protected]

    opened by lamby 9
  • define a required field with no default

    define a required field with no default

    The code automatically sets the first choice as the default value: https://github.com/5monkeys/django-enumfield/blob/master/django_enumfield/db/fields.py#L16-L17

    I think this behavior is misleading because if someone defines a field with no default, he expects the field to have no default assigned so that an error is raised if the field is also required.

    Right now, it's impossible to have a required field (null=False) with no default that would work as expected in the admin.

    enum.EnumField(STATUSES, blank=True, null=False)
    

    Setting default=None obviously doesn't work as the whole code assumes there is a value.

    opened by YAmikep 9
  • Bump versions and move to GitHub actions

    Bump versions and move to GitHub actions

    • Remove testing with Django<2.2 and Python<3.7

      Newer versions of mypy also (incorrectly) don't accept overriding enum attributes when subclassing. Mitigated by temporarily removing attributes from Enum base class.

      Mypy ticket can be found here: https://github.com/python/mypy/issues/12132

    • Move to GitHub actions


    Based on https://github.com/5monkeys/django-enumfield/pull/66#issuecomment-1003998396

    I also tried including Django==3.2 but it failed, felt more reasonable to bump that in a separate PR.

    opened by flaeppe 6
  • New stable release

    New stable release

    We are encountering problems with the fact that the EnumField in version 1.5 dot not allow mandatory field because it set by default based on the default of Enum.

    We also noticed that you have already fixed it in the latest beta release.

    What's is blocking the new stable release?

    opened by ca-simone-chiorazzo 6
  • Advance sqlite version for testing

    Advance sqlite version for testing

    We're encountering a test failure due to sqlmigrate. The behavior change was introduced by [1].

    [1] https://code.djangoproject.com/ticket/30023

    EDIT: Sorry, I was testing with django-2.1 against django-enumfield-1.5 misreported the issue. These issues are not present with django-2.4.4 against django-enumfield-2.0.

    opened by adrianparvino 4
  • Django 1.7 support

    Django 1.7 support

    We need to make the field serializable for the new migration framework by adding a deconstruct-method to the field.

    https://docs.djangoproject.com/en/dev/releases/1.7/#deconstruct-and-serializability

    opened by hannseman 4
  • Import error in Django 4.0

    Import error in Django 4.0

      File "/srv/service/my_project/my_feature/models.py", line 8, in <module>
        from django_enumfield.enum import Enum
      File "/usr/local/lib/python3.9/site-packages/django_enumfield/enum.py", line 15, in <module>
        from django_enumfield.db.fields import EnumField
      File "/usr/local/lib/python3.9/site-packages/django_enumfield/db/fields.py", line 8, in <module>
        from django.utils.encoding import force_text
    ImportError: cannot import name 'force_text' from 'django.utils.encoding' (/usr/local/lib/python3.9/site-packages/django/utils/encoding.py)
    

    Features removed in 4.0

    opened by StefanBrand 3
  • Remove usage of deprecated functions not supported in Django 4.0

    Remove usage of deprecated functions not supported in Django 4.0

    This resolves usage of deprecated functions that have been removed in Django 4.0

    packages/django_enumfield/db/fields.py", line 8, in <module>
    71
        from django.utils.encoding import force_text
    72
    ImportError: cannot import name 'force_text' from 'django.utils.encoding' (
    

    @Swamii

    opened by jackton1 3
  • python 3.9.2 seems to break enumfields

    python 3.9.2 seems to break enumfields

    After upgrading to python 3.9.2 I am now unable to import from the enumfields package

    3.9.2 changed the way subclassing and multiple inheritance works so it might be related?

    Python 3.9.2 (default, Feb 20 2021, 20:59:40) 
    [GCC 5.4.0 20160609] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    
    >>> from enumfields import EnumIntegerField
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/__init__.py", line 1, in <module>
        from .enums import Enum, IntEnum
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/enums.py", line 28, in <module>
        class Enum(EnumMeta('Enum', (BaseEnum,), _EnumDict())):
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/enums.py", line 18, in __new__
        obj = BaseEnumMeta.__new__(mcs, name, bases, attrs)
      File "/usr/lib/python3.9/enum.py", line 212, in __new__
        classdict['__doc__'] = 'An enumeration.'
      File "/usr/lib/python3.9/enum.py", line 97, in __setitem__
        if _is_private(self._cls_name, key):
    AttributeError: '_EnumDict' object has no attribute '_cls_name'
    
    >>> from enumfields import EnumField
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/__init__.py", line 1, in <module>
        from .enums import Enum, IntEnum
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/enums.py", line 28, in <module>
        class Enum(EnumMeta('Enum', (BaseEnum,), _EnumDict())):
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/enums.py", line 18, in __new__
        obj = BaseEnumMeta.__new__(mcs, name, bases, attrs)
      File "/usr/lib/python3.9/enum.py", line 212, in __new__
        classdict['__doc__'] = 'An enumeration.'
      File "/usr/lib/python3.9/enum.py", line 97, in __setitem__
        if _is_private(self._cls_name, key):
    AttributeError: '_EnumDict' object has no attribute '_cls_name'
    
    >>> import enumfields
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/__init__.py", line 1, in <module>
        from .enums import Enum, IntEnum
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/enums.py", line 28, in <module>
        class Enum(EnumMeta('Enum', (BaseEnum,), _EnumDict())):
      File "/var/www/.venv/lib/python3.9/site-packages/enumfields/enums.py", line 18, in __new__
        obj = BaseEnumMeta.__new__(mcs, name, bases, attrs)
      File "/usr/lib/python3.9/enum.py", line 212, in __new__
        classdict['__doc__'] = 'An enumeration.'
      File "/usr/lib/python3.9/enum.py", line 97, in __setitem__
        if _is_private(self._cls_name, key):
    AttributeError: '_EnumDict' object has no attribute '_cls_name'
    
    opened by jadedarko 2
  • disabled field fills wrong initial value

    disabled field fills wrong initial value

    Hi, I have an enumfield that I have disabled as part of a form with disabled=True. however then the validation always validates the field as being wrong.

    After some debugging I found this is due to the fact that the value given to clean differs if the field is disabled or not. when it is not disabled it is a string containing a number if it is disabled it is a string containing the string name of the enum option.

    I have spend quite some time thinking about a solution myself but cant get it right. Does any of the maintainers have an idea? Thanks in advance!

    opened by fboerman 4
  • Any reason why IntegerField is the only type supported?

    Any reason why IntegerField is the only type supported?

    We have a lot of string tuples, while I agree that it would take infinitely less space in the db (space is cheap now a days); we have dba's that deal with the db directly and the strings are a lot more descriptive. I'm curious why the option is not available. Thank you.

    opened by kingbuzzman 1
  • _get_FIELD_display throws error if value is None

    _get_FIELD_display throws error if value is None

    In the 2.0 release, _get_FIELD_display throws an error if the value is None. I think the code needs to be changed to:

    def _get_FIELD_display(self, cls):
        value = getattr(cls, self.attname)
        if value is None:
             return value
        return force_text(value.label, strings_only=True)
    
    opened by twschiller 1
  • Consider moving project into jazzband?

    Consider moving project into jazzband?

    It looks like development and support of django-enumfield for Django 1.9 and 1.10 is stalling a little, so maybe it would help maintenance if the project was adopted by jazzband so more eyes and hands can get involved?

    I use a few other packages that were coasting before but moved to jazzband and now receive fixes and improvements again (hurray for community ownership).

    It is a bit of a process and there are rules and guidelines. I'm not even 100% clear if this project qualifies, but this is one of the main django + enum apps out there so I guess it would be?

    question 
    opened by Bartvds 2
Owner
5 Monkeys
5 Monkeys
A django model and form field for normalised phone numbers using python-phonenumbers

django-phonenumber-field A Django library which interfaces with python-phonenumbers to validate, pretty print and convert phone numbers. python-phonen

Stefan Foulis 1.3k Dec 31, 2022
A django model and form field for normalised phone numbers using python-phonenumbers

django-phonenumber-field A Django library which interfaces with python-phonenumbers to validate, pretty print and convert phone numbers. python-phonen

Stefan Foulis 1.3k Dec 31, 2022
A reusable Django model field for storing ad-hoc JSON data

jsonfield jsonfield is a reusable model field that allows you to store validated JSON, automatically handling serialization to and from the database.

Ryan P Kilby 1.1k Jan 3, 2023
A Django application that provides country choices for use with forms, flag icons static files, and a country field for models.

Django Countries A Django application that provides country choices for use with forms, flag icons static files, and a country field for models. Insta

Chris Beaven 1.2k Jan 7, 2023
A Django application that provides country choices for use with forms, flag icons static files, and a country field for models.

Django Countries A Django application that provides country choices for use with forms, flag icons static files, and a country field for models. Insta

Chris Beaven 1.2k Dec 31, 2022
Location field and widget for Django. It supports Google Maps, OpenStreetMap and Mapbox

django-location-field Let users pick locations using a map widget and store its latitude and longitude. Stable version: django-location-field==2.1.0 D

Caio Ariede 481 Dec 29, 2022
A pickled object field for Django

django-picklefield About django-picklefield provides an implementation of a pickled object field. Such fields can contain any picklable objects. The i

Gintautas Miliauskas 167 Oct 18, 2022
A drop-in replacement for django's ImageField that provides a flexible, intuitive and easily-extensible interface for quickly creating new images from the one assigned to the field.

django-versatileimagefield A drop-in replacement for django's ImageField that provides a flexible, intuitive and easily-extensible interface for creat

Jonathan Ellenberger 490 Dec 13, 2022
A pickled object field for Django

django-picklefield About django-picklefield provides an implementation of a pickled object field. Such fields can contain any picklable objects. The i

Gintautas Miliauskas 167 Oct 18, 2022
Django URL Shortener is a Django app to to include URL Shortening feature in your Django Project

Django URL Shortener Django URL Shortener is a Django app to to include URL Shortening feature in your Django Project Install this package to your Dja

Rishav Sinha 4 Nov 18, 2021
This is a repository for collecting global custom management extensions for the Django Framework.

Django Extensions Django Extensions is a collection of custom extensions for the Django Framework. Getting Started The easiest way to figure out what

Django Extensions 6k Dec 26, 2022
This Django app will be used to host Source.Python plugins, sub-plugins, and custom packages.

Source.Python Project Manager This Django app will be used to host Source.Python plugins, sub-plugins, and custom packages. Want to help develop this

null 2 Sep 24, 2022
Tweak the form field rendering in templates, not in python-level form definitions. CSS classes and HTML attributes can be altered.

django-widget-tweaks Tweak the form field rendering in templates, not in python-level form definitions. Altering CSS classes and HTML attributes is su

Jazzband 1.8k Jan 2, 2023
Resolve form field arguments dynamically when a form is instantiated

django-forms-dynamic Resolve form field arguments dynamically when a form is instantiated, not when it's declared. Tested against Django 2.2, 3.2 and

DabApps 108 Jan 3, 2023
A beginner django project and also my first Django project which involves shortening of a longer URL into a short one using a unique id.

Django-URL-Shortener A beginner django project and also my first Django project which involves shortening of a longer URL into a short one using a uni

Rohini Rao 3 Aug 8, 2021
DRF_commands is a Django package that helps you to create django rest framework endpoints faster using manage.py.

DRF_commands is a Django package that helps you to create django rest framework endpoints faster using manage.py.

Mokrani Yacine 2 Sep 28, 2022
A starter template for building a backend with Django and django-rest-framework using docker with PostgreSQL as the primary DB.

Django-Rest-Template! This is a basic starter template for a backend project with Django as the server and PostgreSQL as the database. About the templ

Akshat Sharma 11 Dec 6, 2022
Django-discord-bot - Framework for creating Discord bots using Django

django-discord-bot Framework for creating Discord bots using Django Uses ASGI fo

Jamie Bliss 1 Mar 4, 2022
Django-Text-to-HTML-converter - The simple Text to HTML Converter using Django framework

Django-Text-to-HTML-converter This is the simple Text to HTML Converter using Dj

Nikit Singh Kanyal 6 Oct 9, 2022