Permalink
Browse files

initial

  • Loading branch information...
0 parents commit 0b30417ece9777d9723682d6e30854e0d046956c @hannseman hannseman committed Jan 31, 2013
@@ -0,0 +1,10 @@
+*.pyc
+*.egg
+*.egg-info
+/build/
+/dist/
+/.idea/
+.DS_Store
+/.coverage
+/htmlcov/
+/.tox/
@@ -0,0 +1,12 @@
+language: python
+
+python:
+ - 2.7
+
+install:
+ pip install tox flake8
+ - make install
+
+script:
+ - make flake8
+ - tox
@@ -0,0 +1 @@
+https://github.com/5monkeys/django-enum/contributors
@@ -0,0 +1,7 @@
+Copyright (C) 2012 5 Monkeys Agency AB
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
+include AUTHORS
+include LICENSE
+include README.rst
@@ -0,0 +1,14 @@
+test:
+ python setup.py test
+
+flake8:
+ flake8 --ignore=E501,E225,E128,W391,W404 --max-complexity 12 django_enumfield
+
+install:
+ python setup.py install
+
+develop:
+ python setup.py develop
+
+coverage:
+ coverage run --include=django_enumfield/* setup.py test
@@ -0,0 +1,3 @@
+Django Enumfield
+==============
+
@@ -0,0 +1,27 @@
+VERSION = (1, 0, 0, 'rc', 1)
+
+
+def get_version(version=None):
+ """Derives a PEP386-compliant version number from VERSION."""
+ if version is None:
+ version = VERSION
+ assert len(version) == 5
+ assert version[3] in ('alpha', 'beta', 'rc', 'final')
+
+ # Now build the two parts of the version number:
+ # main = X.Y[.Z]
+ # sub = .devN - for pre-alpha releases
+ # | {a|b|c}N - for alpha, beta and rc releases
+
+ parts = 2 if version[2] == 0 else 3
+ main = '.'.join(str(x) for x in version[:parts])
+
+ sub = ''
+ if version[3] != 'final':
+ mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
+ sub = mapping[version[3]] + str(version[4])
+
+ return main + sub
+
+
+__version__ = get_version()
No changes.
@@ -0,0 +1,77 @@
+from django.db import models
+from django import forms
+from django_enumfield import validators
+
+
+class EnumField(models.IntegerField):
+ """ 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)
+ """
+ __metaclass__ = models.SubfieldBase
+
+ def __init__(self, enum, *args, **kwargs):
+ default = kwargs.get('default')
+ db_index = kwargs.get('db_index')
+ kwargs['choices'] = enum.choices()
+ if not default:
+ kwargs['default'] = enum.default()
+ if not db_index:
+ kwargs['db_index'] = True
+ self.enum = enum
+ models.IntegerField.__init__(self, *args, **kwargs)
+
+ def contribute_to_class(self, cls, name):
+ super(EnumField, self).contribute_to_class(cls, name)
+ models.signals.class_prepared.connect(self._setup_validation, sender=cls)
+
+ def _setup_validation(self, sender, **kwargs):
+ """
+ User a customer setter for the field to validate new value against the old one.
+ The current value is set as '_enum_[att_name]' on the model instance.
+ """
+ att_name = self.get_attname()
+ private_att_name = '_enum_%s' % att_name
+ enum = self.enum
+
+ def set_enum(self, new_value):
+ if hasattr(self, private_att_name):
+ # Fetch previous value from private enum attribute.
+ old_value = getattr(self, private_att_name)
+ else:
+ # First setattr no previous value on instance.
+ old_value = new_value
+ # Update private enum attribute with new value
+ setattr(self, private_att_name, new_value)
+ # Run validation for new value.
+ validators.validate_valid_transition(enum, old_value, new_value)
+
+ def get_enum(self):
+ return getattr(self, private_att_name)
+
+ def delete_enum(self):
+ return setattr(self, private_att_name, None)
+
+ if not sender._meta.abstract:
+ setattr(sender, att_name, property(get_enum, set_enum, delete_enum))
+
+ def validate(self, value, model_instance):
+ super(EnumField, self).validate(value, model_instance)
+ validators.validate_valid_transition(self.enum, self.value_from_object(model_instance), value)
+
+ def formfield(self, **kwargs):
+ defaults = {'widget': forms.Select,
+ 'form_class': forms.TypedChoiceField,
+ 'coerce': int,
+ 'choices': self.enum.choices()}
+ defaults.update(kwargs)
+ return super(EnumField, self).formfield(**defaults)
+
+ def south_field_triple(self):
+ """Returns a suitable description of this field for South."""
+ # We'll just introspect ourselves, since we inherit.
+ from south.modelsinspector import introspector
+ field_class = "django.db.models.fields.IntegerField"
+ args, kwargs = introspector(self)
+ # That's our definition!
+ return field_class, args, kwargs
@@ -0,0 +1,141 @@
+import logging
+from django.utils.encoding import smart_unicode
+from django.utils.translation import gettext as _
+from django_enumfield.db.fields import EnumField
+
+
+def translate(name):
+ return smart_unicode(_(name.replace('_', ' ').lower())).title()
+
+logger = logging.getLogger(__name__)
+
+
+class EnumType(type):
+
+ def __new__(cls, *args, **kwargs):
+ """
+ Create enum values from all uppercase class attributes and store them in a dict on the Enum class.
+ """
+ enum = super(EnumType, cls).__new__(cls, *args, **kwargs)
+ attributes = filter(lambda (k, v): k.isupper(), enum.__dict__.iteritems())
+ enum.values = {}
+ for attribute in attributes:
+ enum.values[attribute[1]] = enum.Value(attribute[0], attribute[1], enum)
+ return enum
+
+
+class Enum(object):
+ """
+ A container for holding and restoring enum values.
+ Usage:
+ class BeerStyle(Enum):
+ LAGER = 0
+ STOUT = 1
+ WEISSBIER = 2
+ It can also validate enum value transitions by defining the _transitions variable as a dict with transitions.
+ """
+ __metaclass__ = EnumType
+
+ class Value(object):
+ """
+ A value represents a key value pair with a uppercase name and a integer value:
+ GENDER = 1
+ "name" is a upper case string representing the class attribute
+ "label" is a translatable human readable version of "name"
+ "enum_type" is the value defined for the class attribute
+ """
+ def __init__(self, name, value, enum_type):
+ self.name = name
+ self.label = translate(name)
+ self.value = value
+ self.enum_type = enum_type
+
+ def __unicode__(self):
+ return unicode(self.label)
+
+ def __str__(self):
+ return self.label
+
+ def __repr__(self):
+ return self.label
+
+ def __eq__(self, other):
+ if other and isinstance(other, Enum.Value):
+ return self.value == other.value
+ else:
+ raise TypeError('Can not compare Enum with %s' % other.__class__.__name__)
+
+ @classmethod
+ def choices(cls):
+ """
+ Returns a list of tuples with the value as first argument and the value container class as second argument.
+ """
+ return sorted(cls.values.iteritems(), key=lambda x: x[0])
+
+ @classmethod
+ def default(cls):
+ """
+ Returns default value, which is the first one by default.
+ Override this method if you need another default value.
+ Usage:
+ IntegerField(choices=my_enum.choices(), default=my_enum.default(), ...
+ """
+ return cls.choices()[0][0]
+
+ @classmethod
+ def field(cls, **kwargs):
+ """
+ A shortcut for
+ Usage:
+ class MyModelStatuses(Enum):
+ UNKNOWN = 0
+ class MyModel(Model):
+ status = MyModelStatuses.field()
+ """
+ return EnumField(cls, **kwargs)
+
+ @classmethod
+ def get(cls, name_or_numeric):
+ """
+ Will return a Enum.Value matching the value argument.
+ """
+ if isinstance(name_or_numeric, basestring):
+ name_or_numeric = getattr(cls, name_or_numeric.upper())
+
+ return cls.values.get(name_or_numeric)
+
+ @classmethod
+ def name(cls, numeric):
+ """
+ Will return the uppercase name for the matching Enum.Value.
+ """
+ return cls.get(numeric).name
+
+ @classmethod
+ def label(cls, numeric):
+ """
+ Will return the human readable label for the matching Enum.Value.
+ """
+ return translate(unicode(cls.get(numeric)))
+
+ @classmethod
+ def items(cls):
+ """
+ Will return a list of tuples consisting of every enum value in the form [('NAME', value), ...]
+ """
+ items = [(value.name, key) for key, value in cls.values.iteritems()]
+ return sorted(items, key=lambda x: x[1])
+
+ @classmethod
+ def is_valid_transition(cls, from_value, to_value):
+ """
+ Will check if to_value is a valid transition from from_value. Returns true if it is a valid transition.
+ """
+ return from_value == to_value or from_value in cls.transition_origins(to_value)
+
+ @classmethod
+ def transition_origins(cls, to_value):
+ """
+ Returns all values the to_value can make a transition from.
+ """
+ return cls._transitions[to_value]
@@ -0,0 +1,7 @@
+from django.core.exceptions import ValidationError
+
+
+class InvalidStatusOperationError(ValidationError):
+ pass
+
+
No changes.
@@ -0,0 +1 @@
+from django_enumfield.tests.test_enum import *
@@ -0,0 +1,51 @@
+from django.db import models
+from django_enumfield.db.fields import EnumField
+from django_enumfield.enum import Enum
+
+
+class LampState(Enum):
+ OFF = 0
+ ON = 1
+
+
+class Lamp(models.Model):
+ state = EnumField(LampState)
+
+
+class PersonStatus(Enum):
+ UNBORN = 0
+ ALIVE = 1
+ DEAD = 2
+ REANIMATED = 3
+
+ _transitions = {
+ UNBORN: (),
+ ALIVE: (UNBORN,),
+ DEAD: (UNBORN, ALIVE),
+ REANIMATED: (DEAD,)
+ }
+
+
+class Person(models.Model):
+ status = EnumField(PersonStatus, default=PersonStatus.ALIVE)
+
+ def save(self, *args, **kwargs):
+ super(Person, self).save(*args, **kwargs)
+ return 'Person.save'
+
+
+class BeerStyle(Enum):
+ LAGER = 0
+ STOUT = 1
+ WEISSBIER = 2
+
+
+class BeerState(Enum):
+ FIZZY = 0
+ STALE = 1
+ EMPTY = 2
+
+
+class Beer(models.Model):
+ style = EnumField(BeerStyle)
+ state = EnumField(BeerState, null=True, db_index=False)
Oops, something went wrong. Retry.

0 comments on commit 0b30417

Please sign in to comment.