Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
sudo: false

language: python

python:
- 2.7
- 3.3
- 3.4
- 3.5

env:
- DJANGO=1.6
- DJANGO=1.7
- DJANGO=1.8

matrix:
exclude:
- python: 3.5
env: DJANGO=1.6
- python: 3.5
env: DJANGO=1.7
include:
- python: 3.4
env: DJANGO=1.8 COVERAGE=true COVERALLS_REPO_TOKEN=LdECqqwg7eelQx9w8gvooUZCFIaCqGZCv
allow_failures:
- env: COVERAGE=true

install:
- pip install tox flake8
- pip install flake8
- pip install -q "Django>=$DJANGO,<$DJANGO.99"
- make install

script:
- make flake8
- tox
- make test

after_script:
- if [ "$COVERAGE" == "true" ]; then
pip install --quiet python-coveralls;
make coverage;
coverage report;
coveralls --ignore-errors;
fi

notifications:
email:
- [email protected]

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ test:
python setup.py test

flake8:
flake8 --ignore=E501,F403 --max-complexity 12 django_enumfield
flake8 django_enumfield

install:
python setup.py install
Expand All @@ -11,4 +11,4 @@ develop:
python setup.py develop

coverage:
coverage run --include=django_enumfield/* setup.py test
coverage run --source django_enumfield setup.py test
37 changes: 16 additions & 21 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,11 @@ django-enumfield

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

.. image:: https://travis-ci.org/5monkeys/django-enumfield.png?branch=master
.. image:: https://travis-ci.org/5monkeys/django-enumfield.svg?branch=master
:target: http://travis-ci.org/5monkeys/django-enumfield

.. image:: https://pypip.in/d/django-enumfield/badge.png
:target: https://pypi.python.org/pypi/django-enumfield/

.. image:: https://pypip.in/v/django-enumfield/badge.png
:target: https://pypi.python.org/pypi/django-enumfield/

.. image:: https://pypip.in/egg/django-enumfield/badge.png
:target: https://pypi.python.org/pypi/django-enumfield/

.. image:: https://pypip.in/wheel/django-enumfield/badge.png
:target: https://pypi.python.org/pypi/django-enumfield/

.. image:: https://pypip.in/format/django-enumfield/badge.png
:target: https://pypi.python.org/pypi/django-enumfield/

.. image:: https://pypip.in/license/django-enumfield/badge.png
:target: https://pypi.python.org/pypi/django-enumfield/
.. image:: https://coveralls.io/repos/5monkeys/django-enumfield/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/5monkeys/django-enumfield


Installation
Expand Down Expand Up @@ -65,7 +50,7 @@ You can use your own labels for Enum items
CAT = 1
DOG = 2

labels = {
__labels__ = {
CAT: 'Cat',
DOG: 'Dog'
}
Expand All @@ -82,7 +67,7 @@ The Enum-class provides the possibility to use transition validation.
DEAD = 2
REANIMATED = 3

_transitions = {
__transitions__ = {
DEAD: (ALIVE,),
REANIMATED: (DEAD,)
}
Expand Down Expand Up @@ -112,7 +97,7 @@ The Enum-class can also be used without the EnumField. This is very useful in Dj
MALE = 1
FEMALE = 2

labels = {
__labels__ = {
MALE: 'Male',
FEMALE: 'Female',
}
Expand All @@ -121,3 +106,13 @@ The Enum-class can also be used without the EnumField. This is very useful in Dj
gender = forms.TypedChoiceField(choices=GenderEnum.choices(), coerce=int)

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

Changelog
---------

v2.0.0
~~~~~~

* The ``enumfield.enum.Enum`` class is now a subclass of the native ``IntEnum`` shipped with Python 3.4 (uses the ``enum34`` package on previous versions of Python)
* Renamed ``_labels`` to ``__labels__``
* Renamed ``_transitions`` to ``__transitions__``
61 changes: 54 additions & 7 deletions django_enumfield/db/fields.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,73 @@
from enum import Enum

from django.db import models
from django import forms
from django.utils.functional import curry
from django.utils.encoding import force_text
from django.utils import six
from django import forms
import django

from .. import validators

from django_enumfield import validators

if django.VERSION < (1, 8):
base = six.with_metaclass(models.SubfieldBase, models.Field)
else:
base = models.Field

class EnumField(six.with_metaclass(models.SubfieldBase, models.IntegerField)):

class EnumField(base):
""" EnumField is a convenience field to automatically handle validation of transitions
between Enum values and set field choices from the enum.
EnumField(MyEnum, default=MyEnum.INITIAL)
"""
default_error_messages = models.IntegerField.default_error_messages

def __init__(self, enum, *args, **kwargs):
kwargs['choices'] = enum.choices()
if 'default' not in kwargs:
kwargs['default'] = enum.default()
self.enum = enum
models.IntegerField.__init__(self, *args, **kwargs)
super(EnumField, self).__init__(self, *args, **kwargs)

def get_default(self):
if callable(self.default):
return self.default()
return self.default

def get_internal_type(self):
return "IntegerField"

def contribute_to_class(self, cls, name, virtual_only=False):
super(EnumField, self).contribute_to_class(cls, name)
if self.choices:
setattr(cls, 'get_%s_display' % self.name,
curry(self._get_FIELD_display))
models.signals.class_prepared.connect(self._setup_validation, sender=cls)

def _get_FIELD_display(self, cls):
value = getattr(cls, self.attname)
return force_text(value.label, strings_only=True)

def get_prep_value(self, value):
value = super(EnumField, self).get_prep_value(value)
if value is None:
return value

if isinstance(value, Enum):
return value.value
return int(value)

def from_db_value(self, value, expression, connection, context):
if value is not None:
return self.enum.get(value)

return value

def to_python(self, value):
if value is not None:
return self.enum.get(value)

def _setup_validation(self, sender, **kwargs):
"""
User a customer setter for the field to validate new value against the old one.
Expand All @@ -39,6 +85,8 @@ def set_enum(self, new_value):
# First setattr no previous value on instance.
old_value = new_value
# Update private enum attribute with new value
if new_value is not None and not isinstance(new_value, Enum):
new_value = enum.get(new_value)
setattr(self, private_att_name, new_value)
# Run validation for new value.
validators.validate_valid_transition(enum, old_value, new_value)
Expand Down Expand Up @@ -76,7 +124,6 @@ def south_field_triple(self):

def deconstruct(self):
name, path, args, kwargs = super(EnumField, self).deconstruct()
path = "django.db.models.fields.IntegerField"
if 'choices' in kwargs:
del kwargs['choices']
kwargs['enum'] = self.enum
del kwargs['verbose_name']
return name, path, args, kwargs
Loading