Reorganized dir

This commit is contained in:
David Gelvin 2010-09-27 16:30:16 +03:00
parent 0264919f88
commit 8744714574
19 changed files with 1 additions and 1612 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
*.orig
*.db
*.sqlite*
_build

View file

@ -1,35 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
VERSION = (0, 9, 1)
def get_version():
version = "%s.%s" % (VERSION[0], VERSION[1])
if VERSION[2] != 0:
version = "%s.%s" % (version, VERSION[2])
return version
__version__ = get_version()
def register(model_cls, config_cls=None):
from registry import Registry
Registry.register(model_cls, config_cls)
def unregister(model_cls):
from registry import Registry
Registry.unregister(model_cls)

105
admin.py
View file

@ -1,105 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
from django.contrib import admin
from django.contrib.admin.options import (
ModelAdmin, InlineModelAdmin, StackedInline
)
from django.forms.models import BaseInlineFormSet
from django.utils.safestring import mark_safe
from .models import Attribute, Value, EnumValue, EnumGroup
class BaseEntityAdmin(ModelAdmin):
def render_change_form(self, request, context, **kwargs):
"""
Wrapper for ModelAdmin.render_change_form. Replaces standard static
AdminForm with an EAV-friendly one. The point is that our form generates
fields dynamically and fieldsets must be inferred from a prepared and
validated form instance, not just the form class. Django does not seem
to provide hooks for this purpose, so we simply wrap the view and
substitute some data.
"""
form = context['adminform'].form
# infer correct data from the form
fieldsets = [(None, {'fields': form.fields.keys()})]
adminform = admin.helpers.AdminForm(form, fieldsets,
self.prepopulated_fields)
media = mark_safe(self.media + adminform.media)
context.update(adminform=adminform, media=media)
super_meth = super(BaseEntityAdmin, self).render_change_form
return super_meth(request, context, **kwargs)
class BaseEntityInlineFormSet(BaseInlineFormSet):
"""
An inline formset that correctly initializes EAV forms.
"""
def add_fields(self, form, index):
if self.instance:
setattr(form.instance, self.fk.name, self.instance)
form._build_dynamic_fields()
super(BaseEntityInlineFormSet, self).add_fields(form, index)
class BaseEntityInline(InlineModelAdmin):
"""
Inline model admin that works correctly with EAV attributes. You should mix
in the standard StackedInline or TabularInline classes in order to define
formset representation, e.g.::
class ItemInline(BaseEntityInline, StackedInline):
model = Item
form = forms.ItemForm
.. warning: TabularInline does *not* work out of the box. There is,
however, a patched template `admin/edit_inline/tabular.html` bundled
with EAV-Django. You can copy or symlink the `admin` directory to your
templates search path (see Django documentation).
"""
formset = BaseEntityInlineFormSet
def get_fieldsets(self, request, obj=None):
if self.declared_fieldsets:
return self.declared_fieldsets
formset = self.get_formset(request)
fk_name = self.fk_name or formset.fk.name
kw = {fk_name: obj} if obj else {}
instance = self.model(**kw)
form = formset.form(request.POST, instance=instance)
return [(None, {'fields': form.fields.keys()})]
class AttributeAdmin(ModelAdmin):
list_display = ('name', 'slug', 'datatype', 'description')
prepopulated_fields = {'slug': ('name',)}
admin.site.register(Attribute, AttributeAdmin)
admin.site.register(Value)
admin.site.register(EnumValue)
admin.site.register(EnumGroup)

0
docs/_static/.gitignore vendored Normal file
View file

0
docs/_templates/.gitignore vendored Normal file
View file

View file

@ -1,75 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
import uuid
import re
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
class EavSlugField(models.SlugField):
def validate(self, value, instance):
"""
Slugs are used to convert the Python attribute name to a database
lookup and vice versa. We need it to be a valid Python identifier.
We don't want it to start with a '_', underscore will be used
var variables we don't want to be saved in db.
"""
super(EavSlugField, self).validate(value, instance)
slug_regex = r'[a-z][a-z0-9_]*'
if not re.match(slug_regex, value):
raise ValidationError(_(u"Must be all lower case, "\
u"start with a letter, and contain "\
u"only letters, numbers, or underscores."))
@staticmethod
def create_slug_from_name(name):
'''
Creates a slug based on the name
'''
name = name.strip().lower()
# Change spaces to underscores
name = '_'.join(name.split())
# Remove non alphanumeric characters
return re.sub('[^\w]', '', name)
class EavDatatypeField(models.CharField):
"""
This holds checks for the attributes datatypes.
"""
def validate(self, value, instance):
"""
We don't want them to be able to change the attribute type
once it have been created.
"""
super(EavDatatypeField, self).validate(value, instance)
from .models import Attribute
if not instance.pk:
return
if instance.value_set.count():
raise ValidationError(_(u"You cannot change the datatype of an "
u"attribute that is already in use."))

121
forms.py
View file

@ -1,121 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
from copy import deepcopy
# django
from django.forms import BooleanField, CharField, DateTimeField, FloatField, \
IntegerField, ModelForm, ChoiceField, ValidationError
from django.contrib.admin.widgets import AdminSplitDateTime
from django.utils.translation import ugettext_lazy as _
class BaseDynamicEntityForm(ModelForm):
"""
ModelForm for entity with support for EAV attributes. Form fields are created
on the fly depending on Schema defined for given entity instance. If no schema
is defined (i.e. the entity instance has not been saved yet), only static
fields are used. However, on form validation the schema will be retrieved
and EAV fields dynamically added to the form, so when the validation is
actually done, all EAV fields are present in it (unless Rubric is not defined).
"""
FIELD_CLASSES = {
'text': CharField,
'float': FloatField,
'int': IntegerField,
'date': DateTimeField,
'bool': BooleanField,
'enum': ChoiceField,
}
def __init__(self, data=None, *args, **kwargs):
super(BaseDynamicEntityForm, self).__init__(data, *args, **kwargs)
config_cls = self.instance._eav_config_cls
self.entity = getattr(self.instance, config_cls.eav_attr)
self._build_dynamic_fields()
def _build_dynamic_fields(self):
# reset form fields
self.fields = deepcopy(self.base_fields)
for attribute in self.entity.get_all_attributes():
value = getattr(self.entity, attribute.slug)
defaults = {
'label': attribute.name.capitalize(),
'required': attribute.required,
'help_text': attribute.help_text,
'validators': attribute.get_validators()
}
datatype = attribute.datatype
if datatype == attribute.TYPE_ENUM:
enums = attribute.get_choices() \
.values_list('id', 'value')
choices = [('', '-----')] + list(enums)
defaults.update({'choices': choices})
if value:
defaults.update({'initial': value.pk})
elif datatype == attribute.TYPE_DATE:
defaults.update({'widget': AdminSplitDateTime})
elif datatype == attribute.TYPE_OBJECT:
continue
MappedField = self.FIELD_CLASSES[datatype]
self.fields[attribute.slug] = MappedField(**defaults)
# fill initial data (if attribute was already defined)
if value and not datatype == attribute.TYPE_ENUM: # m2m is already done above
self.initial[attribute.slug] = value
def save(self, commit=True):
"""
Saves this ``form``'s cleaned_data into model instance ``self.instance``
and related EAV attributes.
Returns ``instance``.
"""
if self.errors:
raise ValueError("The %s could not be saved because the data didn't"
" validate." % self.instance._meta.object_name)
# create entity instance, don't save yet
instance = super(BaseDynamicEntityForm, self).save(commit=False)
# assign attributes
for attribute in self.entity.get_all_attributes():
value = self.cleaned_data.get(attribute.slug)
if attribute.datatype == attribute.TYPE_ENUM:
if value:
value = attribute.enum_group.enums.get(pk=value)
else:
value = None
setattr(self.entity, attribute.slug, value)
# save entity and its attributes
if commit:
instance.save()
return instance

View file

@ -1,153 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
from functools import wraps
from django.db import models
from .models import Attribute, Value
def eav_filter(func):
'''
Decorator used to wrap filter and exlclude methods. Passes args through
expand_q_filters and kwargs through expand_eav_filter. Returns the
called function (filter or exclude) .distinct()
'''
@wraps(func)
def wrapper(self, *args, **kwargs):
new_args = []
for arg in args:
if isinstance(arg, models.Q):
# modify Q objects (warning: recursion ahead)
arg = expand_q_filters(arg, self.model)
new_args.append(arg)
new_kwargs = {}
for key, value in kwargs.items():
# modify kwargs (warning: recursion ahead)
new_key, new_value = expand_eav_filter(self.model, key, value)
new_kwargs.update({new_key: new_value})
return func(self, *new_args, **new_kwargs)
return wrapper
def expand_q_filters(q, root_cls):
'''
Takes a Q object and a model class.
Recursivley passes each filter / value in the Q object tree leaf nodes
through expand_eav_filter
'''
new_children = []
for qi in q.children:
if type(qi) is tuple:
# this child is a leaf node: in Q this is a 2-tuple of:
# (filter parameter, value)
key, value = expand_eav_filter(root_cls, *qi)
new_children.append(models.Q(**{key: value}))
else:
# this child is another Q node: recursify!
new_children.append(expand_q_filters(qi, root_cls))
q.children = new_children
return q
def expand_eav_filter(model_cls, key, value):
'''
Accepts a model class and a key, value.
Recurisively replaces any eav filter with a subquery.
For example:
key = 'eav__height'
value = 5
Would return:
key = 'eav_values__in'
value = Values.objects.filter(value_int=5, attribute__slug='height')
'''
fields = key.split('__')
config_cls = getattr(model_cls, '_eav_config_cls', None)
if len(fields) > 1 and config_cls and \
fields[0] == config_cls.eav_attr:
slug = fields[1]
gr_name = config_cls.generic_relation_attr
datatype = Attribute.objects.get(slug=slug).datatype
lookup = '__%s' % fields[2] if len(fields) > 2 else ''
kwargs = {'value_%s%s' % (datatype, lookup): value,
'attribute__slug': slug}
value = Value.objects.filter(**kwargs)
return '%s__in' % gr_name, value
try:
field, m, direct, m2m = model_cls._meta.get_field_by_name(fields[0])
except models.FieldDoesNotExist:
return key, value
if direct:
return key, value
else:
sub_key = '__'.join(fields[1:])
key, value = expand_eav_filter(field.model, sub_key, value)
return '%s__%s' % (fields[0], key), value
class EntityManager(models.Manager):
@eav_filter
def filter(self, *args, **kwargs):
return super(EntityManager, self).filter(*args, **kwargs).distinct()
@eav_filter
def exclude(self, *args, **kwargs):
return super(EntityManager, self).exclude(*args, **kwargs).distinct()
@eav_filter
def get(self, *args, **kwargs):
return super(EntityManager, self).get(*args, **kwargs)
def create(self, **kwargs):
config_cls = getattr(self.model, '_eav_config_cls', None)
if not config_cls or config_cls.manager_only:
return super(EntityManager, self).create(**kwargs)
attributes = config_cls.get_attributes()
prefix = '%s__' % config_cls.eav_attr
new_kwargs = {}
eav_kwargs = {}
for key, value in kwargs.iteritems():
if key.startswith(prefix):
eav_kwargs.update({key[len(prefix):]: value})
else:
new_kwargs.update({key: value})
obj = self.model(**new_kwargs)
obj_eav = getattr(obj, config_cls.eav_attr)
for key, value in eav_kwargs.iteritems():
setattr(obj_eav, key, value)
obj.save()
return obj
def get_or_create(self, **kwargs):
try:
return self.get(**kwargs)
except self.model.DoesNotExist:
return self.create(**kwargs)

361
models.py
View file

@ -1,361 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
'''
.. automodule:: models
:members:
This is my models file
'''
import inspect
import re
from datetime import datetime
from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.conf import settings
from .validators import *
from .fields import EavSlugField, EavDatatypeField
class EnumValue(models.Model):
value = models.CharField(_(u"value"), db_index=True,
unique=True, max_length=50)
def __unicode__(self):
return self.value
class EnumGroup(models.Model):
name = models.CharField(_(u"name"), unique=True, max_length=100)
enums = models.ManyToManyField(EnumValue, verbose_name=_(u"enum group"))
def __unicode__(self):
return self.name
class Attribute(models.Model):
'''
The A model in E-A-V. This holds the 'concepts' along with the data type
something like:
>>> Attribute.objects.create(name='Height', datatype='float')
<Attribute: Height (Float)>
>>> Attribute.objects.create(name='Color', datatype='text', slug='color')
<Attribute: Color (Text)>
'''
class Meta:
ordering = ['name']
TYPE_TEXT = 'text'
TYPE_FLOAT = 'float'
TYPE_INT = 'int'
TYPE_DATE = 'date'
TYPE_BOOLEAN = 'bool'
TYPE_OBJECT = 'object'
TYPE_ENUM = 'enum'
DATATYPE_CHOICES = (
(TYPE_TEXT, _(u"Text")),
(TYPE_FLOAT, _(u"Float")),
(TYPE_INT, _(u"Integer")),
(TYPE_DATE, _(u"Date")),
(TYPE_BOOLEAN, _(u"True / False")),
(TYPE_OBJECT, _(u"Django Object")),
(TYPE_ENUM, _(u"Multiple Choice")),
)
name = models.CharField(_(u"name"), max_length=100,
help_text=_(u"User-friendly attribute name"))
slug = EavSlugField(_(u"slug"), max_length=50, db_index=True,
help_text=_(u"Short unique attribute label"),
unique=True)
description = models.CharField(_(u"description"), max_length=256,
blank=True, null=True,
help_text=_(u"Short description"))
enum_group = models.ForeignKey(EnumGroup, verbose_name=_(u"choice group"),
blank=True, null=True)
@property
def help_text(self):
return self.description
datatype = EavDatatypeField(_(u"data type"), max_length=6,
choices=DATATYPE_CHOICES)
created = models.DateTimeField(_(u"created"), default=datetime.now,
editable=False)
modified = models.DateTimeField(_(u"modified"), auto_now=True)
required = models.BooleanField(_(u"required"), default=False)
def get_validators(self):
DATATYPE_VALIDATORS = {
'text': validate_text,
'float': validate_float,
'int': validate_int,
'date': validate_date,
'bool': validate_bool,
'object': validate_object,
'enum': validate_enum,
}
validation_function = DATATYPE_VALIDATORS[self.datatype]
return [validation_function]
def validate_value(self, value):
for validator in self.get_validators():
validator(value)
if self.datatype == self.TYPE_ENUM:
if value not in self.enum_group.enums.all():
raise ValidationError(_(u"%(enum)s is not a valid choice "
u"for %(attr)s") % \
{'enum': value, 'attr': self})
def save(self, *args, **kwargs):
if not self.slug:
self.slug = EavSlugField.create_slug_from_name(self.name)
self.full_clean()
super(Attribute, self).save(*args, **kwargs)
def clean(self):
if self.datatype == self.TYPE_ENUM and not self.enum_group:
raise ValidationError(_(
u"You must set the choice group for multiple choice" \
u"attributes"))
if self.datatype != self.TYPE_ENUM and self.enum_group:
raise ValidationError(_(
u"You can only assign a choice group to multiple choice " \
u"attributes"))
def get_choices(self):
'''
Returns the avilable choices for enums.
'''
if not self.datatype == Attribute.TYPE_ENUM:
return None
return self.enum_group.enums.all()
def save_value(self, entity, value):
ct = ContentType.objects.get_for_model(entity)
try:
value_obj = self.value_set.get(entity_ct=ct,
entity_id=entity.pk,
attribute=self)
except Value.DoesNotExist:
if value == None or value == '':
return
value_obj = Value.objects.create(entity_ct=ct,
entity_id=entity.pk,
attribute=self)
if value == None or value == '':
value_obj.delete()
return
if value != value_obj.value:
value_obj.value = value
value_obj.save()
def __unicode__(self):
return u"%s (%s)" % (self.name, self.get_datatype_display())
class Value(models.Model):
'''
The V model in E-A-V. This holds the 'value' for an attribute and an
entity:
>>> from django.db import models
>>> from django.contrib.auth.models import User
>>> from .registry import Registry
>>> Registry.register(User)
>>> u = User.objects.create(username='crazy_dev_user')
>>> a = Attribute.objects.create(name='Favorite Drink', datatype='text',
... slug='fav_drink')
>>> Value.objects.create(entity=u, attribute=a, value_text='red bull')
<Value: crazy_dev_user - Favorite Drink: "red bull">
'''
class Meta:
unique_together = ('entity_ct', 'entity_id', 'attribute')
entity_ct = models.ForeignKey(ContentType, related_name='value_entities')
entity_id = models.IntegerField()
entity = generic.GenericForeignKey(ct_field='entity_ct',
fk_field='entity_id')
value_text = models.TextField(blank=True, null=True)
value_float = models.FloatField(blank=True, null=True)
value_int = models.IntegerField(blank=True, null=True)
value_date = models.DateTimeField(blank=True, null=True)
value_bool = models.NullBooleanField(blank=True, null=True)
value_enum = models.ForeignKey(EnumValue, blank=True, null=True,
related_name='eav_values')
generic_value_id = models.IntegerField(blank=True, null=True)
generic_value_ct = models.ForeignKey(ContentType, blank=True, null=True,
related_name='value_values')
value_object = generic.GenericForeignKey(ct_field='generic_value_ct',
fk_field='generic_value_id')
created = models.DateTimeField(_(u"created"), default=datetime.now)
modified = models.DateTimeField(_(u"modified"), auto_now=True)
attribute = models.ForeignKey(Attribute, db_index=True,
verbose_name=_(u"attribute"))
def save(self, *args, **kwargs):
self.full_clean()
super(Value, self).save(*args, **kwargs)
def clean(self):
if self.attribute.datatype == Attribute.TYPE_ENUM and \
self.value_enum:
if self.value_enum not in self.attribute.enum_group.enums.all():
raise ValidationError(_(u"%(choice)s is not a valid " \
u"choice for %s(attribute)") % \
{'choice': self.value_enum,
'attribute': self.attribute})
# TODO: Remove
def _blank(self):
"""
Set all the field to none
"""
for field in self._meta.fields:
if field.name.startswith('value_') and field.null == True:
setattr(self, field.name, None)
def _get_value(self):
"""
Get returns the Python object hold by this Value object.
"""
return getattr(self, 'value_%s' % self.attribute.datatype)
def _set_value(self, new_value):
self._blank()
setattr(self, 'value_%s' % self.attribute.datatype, new_value)
value = property(_get_value, _set_value)
def __unicode__(self):
return u"%s - %s: \"%s\"" % (self.entity, self.attribute.name, self.value)
class Entity(object):
def __init__(self, instance):
self.model = instance
self.ct = ContentType.objects.get_for_model(instance)
def __getattr__(self, name):
if not name.startswith('_'):
try:
attribute = self.get_attribute_by_slug(name)
except Attribute.DoesNotExist:
raise AttributeError(_(u"%(obj)s has no EAV attribute named " \
u"'%(attr)s'") % \
{'obj':self.model, 'attr':name})
try:
return self.get_value_by_attribute(attribute).value
except Value.DoesNotExist:
return None
return getattr(super(Entity, self), name)
def get_all_attributes(self):
return self.model._eav_config_cls.get_attributes()
def save(self):
for attribute in self.get_all_attributes():
if hasattr(self, attribute.slug):
attribute_value = getattr(self, attribute.slug)
attribute.save_value(self.model, attribute_value)
def validate_attributes(self):
for attribute in self.get_all_attributes():
value = getattr(self, attribute.slug, None)
if value is None:
if attribute.required:
raise ValidationError(_(u"%(attr)s EAV field cannot " \
u"be blank") % \
{'attr': attribute.slug})
else:
try:
attribute.validate_value(value)
except ValidationError, e:
raise ValidationError(_(u"%(attr)s EAV field %(err)s") % \
{'attr': attribute.slug,
'err': e})
def get_values(self):
'''
Get all set EAV Value objects for self.model
'''
return Value.objects.filter(entity_ct=self.ct,
entity_id=self.model.pk).select_related()
def get_all_attribute_slugs(self):
return self.get_all_attributes().values_list('slug', Flat=True)
def get_attribute_by_slug(self, slug):
return self.get_all_attributes().get(slug=slug)
def get_value_by_attribute(self, attribute):
return self.get_values().get(attribute=attribute)
def __iter__(self):
return iter(self.get_values())
@staticmethod
def post_save_handler(sender, *args, **kwargs):
instance = kwargs['instance']
entity = getattr(instance, instance._eav_config_cls.eav_attr)
entity.save()
@staticmethod
def pre_save_handler(sender, *args, **kwargs):
instance = kwargs['instance']
entity = getattr(kwargs['instance'], instance._eav_config_cls.eav_attr)
entity.validate_attributes()
if 'django_nose' in settings.INSTALLED_APPS:
'''
The django_nose test runner won't automatically create our Patient model
database table which is required for tests, unless we import it here.
Please, someone tell me a better way to do this.
'''
from .tests.models import Patient, Encounter

View file

@ -1,146 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
from django.db.utils import DatabaseError
from django.db.models.signals import pre_init, post_init, pre_save, post_save
from django.contrib.contenttypes import generic
from .managers import EntityManager
from .models import Entity, Attribute, Value
class EavConfig(Entity):
manager_attr ='objects'
manager_only = False
eav_attr = 'eav'
generic_relation_attr = 'eav_values'
generic_relation_related_name = None
@classmethod
def get_attributes(cls):
"""
By default, all attributes apply to an entity,
unless otherwise specified.
"""
return Attribute.objects.all()
class Registry(object):
@staticmethod
def register(model_cls, config_cls=None):
if hasattr(model_cls, '_eav_config_cls'):
return
if config_cls is EavConfig or config_cls is None:
config_cls = type("%sConfig" % model_cls.__name__,
(EavConfig,), {})
# set _eav_config_cls on the model so we can access it there
setattr(model_cls, '_eav_config_cls', config_cls)
reg = Registry(model_cls)
reg._register_self()
@staticmethod
def unregister(model_cls):
if not getattr(model_cls, '_eav_config_cls', None):
return
reg = Registry(model_cls)
reg._unregister_self()
delattr(model_cls, '_eav_config_cls')
@staticmethod
def attach_eav_attr(sender, *args, **kwargs):
'''
Attache EAV Entity toolkit to an instance after init.
'''
instance = kwargs['instance']
config_cls = instance.__class__._eav_config_cls
setattr(instance, config_cls.eav_attr, Entity(instance))
def __init__(self, model_cls):
self.model_cls = model_cls
self.config_cls = model_cls._eav_config_cls
def _attach_manager(self):
# save the old manager if the attribute name conflict with the new one
if hasattr(self.model_cls, self.config_cls.manager_attr):
mgr = getattr(self.model_cls, self.config_cls.manager_attr)
self.config_cls.old_mgr = mgr
# attache the new manager to the model
mgr = EntityManager()
mgr.contribute_to_class(self.model_cls, self.config_cls.manager_attr)
def _detach_manager(self):
delattr(self.model_cls, self.config_cls.manager_attr)
if hasattr(self.config_cls, 'old_mgr'):
self.config_cls.old_mgr \
.contribute_to_class(self.model_cls,
self.config_cls.manager_attr)
def _attach_signals(self):
post_init.connect(Registry.attach_eav_attr, sender=self.model_cls)
pre_save.connect(Entity.pre_save_handler, sender=self.model_cls)
post_save.connect(Entity.post_save_handler, sender=self.model_cls)
def _detach_signals(self):
post_init.disconnect(Registry.attach_eav_attr, sender=self.model_cls)
pre_save.disconnect(Entity.pre_save_handler, sender=self.model_cls)
post_save.disconnect(Entity.post_save_handler, sender=self.model_cls)
def _attach_generic_relation(self):
rel_name = self.config_cls.generic_relation_related_name or \
self.model_cls.__name__
gr_name = self.config_cls.generic_relation_attr.lower()
generic_relation = \
generic.GenericRelation(Value,
object_id_field='entity_id',
content_type_field='entity_ct',
related_name=rel_name)
generic_relation.contribute_to_class(self.model_cls, gr_name)
def _detach_generic_relation(self):
gen_rel_field = self.config_cls.generic_relation_attr.lower()
for field in self.model_cls._meta.local_many_to_many:
if field.name == gen_rel_field:
self.model_cls._meta.local_many_to_many.remove(field)
break
delattr(self.model_cls, gen_rel_field)
def _register_self(self):
self._attach_manager()
if not self.config_cls.manager_only:
self._attach_signals()
self._attach_generic_relation()
def _unregister_self(self):
self._detach_manager()
if not self.config_cls.manager_only:
self._detach_signals()
self._detach_generic_relation()

View file

@ -1,5 +0,0 @@
from .registry import *
from .limiting_attributes import *
from .data_validation import *
from .misc_models import *
from .queries import *

View file

@ -1,193 +0,0 @@
from datetime import datetime, date
from django.test import TestCase
from django.core.exceptions import ValidationError
from django.contrib.auth.models import User
import eav
from ..registry import EavConfig
from ..models import Attribute, Value, EnumValue, EnumGroup
from .models import Patient, Encounter
class DataValidation(TestCase):
def setUp(self):
eav.register(Patient)
Attribute.objects.create(name='Age', datatype=Attribute.TYPE_INT)
Attribute.objects.create(name='DoB', datatype=Attribute.TYPE_DATE)
Attribute.objects.create(name='Height', datatype=Attribute.TYPE_FLOAT)
Attribute.objects.create(name='City', datatype=Attribute.TYPE_TEXT)
Attribute.objects.create(name='Pregnant?', datatype=Attribute.TYPE_BOOLEAN)
Attribute.objects.create(name='User', datatype=Attribute.TYPE_OBJECT)
def tearDown(self):
eav.unregister(Patient)
def test_required_field(self):
p = Patient(name='Bob')
p.eav.age = 5
p.save()
Attribute.objects.create(name='Weight', datatype=Attribute.TYPE_INT, required=True)
p.eav.age = 6
self.assertRaises(ValidationError, p.save)
p = Patient.objects.get(name='Bob')
self.assertEqual(p.eav.age, 5)
p.eav.weight = 23
p.save()
p = Patient.objects.get(name='Bob')
self.assertEqual(p.eav.weight, 23)
def test_create_required_field(self):
Attribute.objects.create(name='Weight', datatype=Attribute.TYPE_INT, required=True)
self.assertRaises(ValidationError,
Patient.objects.create,
name='Joe', eav__age=5)
self.assertEqual(Patient.objects.count(), 0)
self.assertEqual(Value.objects.count(), 0)
p = Patient.objects.create(name='Joe', eav__weight=2, eav__age=5)
self.assertEqual(Patient.objects.count(), 1)
self.assertEqual(Value.objects.count(), 2)
def test_validation_error_create(self):
self.assertRaises(ValidationError,
Patient.objects.create,
name='Joe', eav__age='df')
self.assertEqual(Patient.objects.count(), 0)
self.assertEqual(Value.objects.count(), 0)
def test_bad_slug(self):
a = Attribute.objects.create(name='color', datatype=Attribute.TYPE_TEXT)
a.slug = 'Color'
self.assertRaises(ValidationError, a.save)
a.slug = '1st'
self.assertRaises(ValidationError, a.save)
a.slug = '_st'
self.assertRaises(ValidationError, a.save)
def test_changing_datatypes(self):
a = Attribute.objects.create(name='Color', datatype=Attribute.TYPE_INT)
a.datatype = Attribute.TYPE_TEXT
a.save()
Patient.objects.create(name='Bob', eav__color='brown')
a.datatype = Attribute.TYPE_INT
self.assertRaises(ValidationError, a.save)
def test_int_validation(self):
p = Patient.objects.create(name='Joe')
p.eav.age = 'bad'
self.assertRaises(ValidationError, p.save)
p.eav.age = 15
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.age, 15)
def test_date_validation(self):
p = Patient.objects.create(name='Joe')
p.eav.dob = 'bad'
self.assertRaises(ValidationError, p.save)
p.eav.dob = 15
self.assertRaises(ValidationError, p.save)
now = datetime.now()
now = datetime(year=now.year, month=now.month, day=now.day,
hour=now.hour, minute=now.minute, second=now.second)
p.eav.dob = now
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.dob, now)
today = date.today()
p.eav.dob = today
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.dob.date(), today)
def test_float_validation(self):
p = Patient.objects.create(name='Joe')
p.eav.height = 'bad'
self.assertRaises(ValidationError, p.save)
p.eav.height = 15
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.height, 15)
p.eav.height='2.3'
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.height, 2.3)
def test_text_validation(self):
p = Patient.objects.create(name='Joe')
p.eav.city = 5
self.assertRaises(ValidationError, p.save)
p.eav.city = 'El Dorado'
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.city, 'El Dorado')
def test_bool_validation(self):
p = Patient.objects.create(name='Joe')
p.eav.pregnant = 5
self.assertRaises(ValidationError, p.save)
p.eav.pregnant = True
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.pregnant, True)
def test_object_validation(self):
p = Patient.objects.create(name='Joe')
p.eav.user = 5
self.assertRaises(ValidationError, p.save)
p.eav.user = object
self.assertRaises(ValidationError, p.save)
p.eav.user = User(username='joe')
self.assertRaises(ValidationError, p.save)
u = User.objects.create(username='joe')
p.eav.user = u
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.user, u)
def test_enum_validation(self):
yes = EnumValue.objects.create(value='yes')
no = EnumValue.objects.create(value='no')
unkown = EnumValue.objects.create(value='unkown')
green = EnumValue.objects.create(value='green')
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.enums.add(yes)
ynu.enums.add(no)
ynu.enums.add(unkown)
Attribute.objects.create(name='Fever?', datatype=Attribute.TYPE_ENUM, enum_group=ynu)
p = Patient.objects.create(name='Joe')
p.eav.fever = 5
self.assertRaises(ValidationError, p.save)
p.eav.fever = object
self.assertRaises(ValidationError, p.save)
p.eav.fever = 'yes'
self.assertRaises(ValidationError, p.save)
p.eav.fever = green
self.assertRaises(ValidationError, p.save)
p.eav.fever = EnumValue(value='yes')
self.assertRaises(ValidationError, p.save)
p.eav.fever = no
p.save()
self.assertEqual(Patient.objects.get(pk=p.pk).eav.fever, no)
def test_enum_datatype_without_enum_group(self):
a = Attribute(name='Age Bracket', datatype=Attribute.TYPE_ENUM)
self.assertRaises(ValidationError, a.save)
yes = EnumValue.objects.create(value='yes')
no = EnumValue.objects.create(value='no')
unkown = EnumValue.objects.create(value='unkown')
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.enums.add(yes)
ynu.enums.add(no)
ynu.enums.add(unkown)
a = Attribute(name='Age Bracket', datatype=Attribute.TYPE_ENUM, enum_group=ynu)
a.save()
def test_enum_group_on_other_datatype(self):
yes = EnumValue.objects.create(value='yes')
no = EnumValue.objects.create(value='no')
unkown = EnumValue.objects.create(value='unkown')
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.enums.add(yes)
ynu.enums.add(no)
ynu.enums.add(unkown)
a = Attribute(name='color', datatype=Attribute.TYPE_TEXT, enum_group=ynu)
self.assertRaises(ValidationError, a.save)

View file

@ -1,55 +0,0 @@
from django.test import TestCase
import eav
from ..registry import EavConfig
from ..models import Attribute, Value
from .models import Patient, Encounter
class LimittingAttributes(TestCase):
def setUp(self):
class EncounterEavConfig(EavConfig):
manager_attr = 'eav_objects'
eav_attr = 'eav_field'
generic_relation_attr = 'encounter_eav_values'
generic_relation_related_name = 'encounters'
@classmethod
def get_attributes(cls):
return Attribute.objects.filter(slug__contains='a')
eav.register(Encounter, EncounterEavConfig)
eav.register(Patient)
Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT)
Attribute.objects.create(name='height', datatype=Attribute.TYPE_FLOAT)
Attribute.objects.create(name='weight', datatype=Attribute.TYPE_FLOAT)
def tearDown(self):
eav.unregister(Encounter)
eav.unregister(Patient)
def test_get_attribute_querysets(self):
self.assertEqual(Patient._eav_config_cls \
.get_attributes().count(), 3)
self.assertEqual(Encounter._eav_config_cls \
.get_attributes().count(), 1)
def test_setting_attributes(self):
p = Patient.objects.create(name='Jon')
e = Encounter.objects.create(patient=p, num=1)
p.eav.age = 3
p.eav.height = 2.3
p.save()
e.eav_field.age = 4
e.eav_field.height = 4.5
e.save()
self.assertEqual(Value.objects.count(), 3)
p = Patient.objects.get(name='Jon')
self.assertEqual(p.eav.age, 3)
self.assertEqual(p.eav.height, 2.3)
e = Encounter.objects.get(num=1)
self.assertEqual(e.eav_field.age, 4)
self.assertFalse(hasattr(e.eav_field, 'height'))

View file

@ -1,28 +0,0 @@
from django.test import TestCase
from ..models import EnumGroup, Attribute, Value
import eav
from .models import Patient
class MiscModels(TestCase):
def test_enumgroup_unicode(self):
name = 'Yes / No'
e = EnumGroup.objects.create(name=name)
self.assertEqual(unicode(e), name)
def test_attribute_help_text(self):
desc = 'Patient Age'
a = Attribute.objects.create(name='age', description=desc, datatype=Attribute.TYPE_INT)
self.assertEqual(a.help_text, desc)
def test_setting_to_none_deletes_value(self):
eav.register(Patient)
Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT)
p = Patient.objects.create(name='Bob', eav__age=5)
self.assertEqual(Value.objects.count(), 1)
p.eav.age = None
p.save()
self.assertEqual(Value.objects.count(), 0)

View file

@ -1,21 +0,0 @@
from django.db import models
class Patient(models.Model):
class Meta:
app_label = 'eav'
name = models.CharField(max_length=12)
def __unicode__(self):
return self.name
class Encounter(models.Model):
class Meta:
app_label = 'eav'
num = models.PositiveSmallIntegerField()
patient = models.ForeignKey(Patient)
def __unicode__(self):
return '%s: encounter num %d' % (self.patient, self.num)

View file

@ -1,112 +0,0 @@
from django.test import TestCase
from django.db.models import Q
from django.contrib.auth.models import User
from ..registry import EavConfig
from ..models import EnumValue, EnumGroup, Attribute, Value
import eav
from .models import Patient, Encounter
class Queries(TestCase):
def setUp(self):
eav.register(Encounter)
eav.register(Patient)
Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT)
Attribute.objects.create(name='height', datatype=Attribute.TYPE_FLOAT)
Attribute.objects.create(name='weight', datatype=Attribute.TYPE_FLOAT)
Attribute.objects.create(name='city', datatype=Attribute.TYPE_TEXT)
Attribute.objects.create(name='country', datatype=Attribute.TYPE_TEXT)
self.yes = EnumValue.objects.create(value='yes')
self.no = EnumValue.objects.create(value='no')
self.unkown = EnumValue.objects.create(value='unkown')
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.enums.add(self.yes)
ynu.enums.add(self.no)
ynu.enums.add(self.unkown)
Attribute.objects.create(name='fever', datatype=Attribute.TYPE_ENUM, enum_group=ynu)
def tearDown(self):
eav.unregister(Encounter)
eav.unregister(Patient)
def test_get_or_create_with_eav(self):
p = Patient.objects.get_or_create(name='Bob', eav__age=5)
self.assertEqual(Patient.objects.count(), 1)
self.assertEqual(Value.objects.count(), 1)
p = Patient.objects.get_or_create(name='Bob', eav__age=5)
self.assertEqual(Patient.objects.count(), 1)
self.assertEqual(Value.objects.count(), 1)
p = Patient.objects.get_or_create(name='Bob', eav__age=6)
self.assertEqual(Patient.objects.count(), 2)
self.assertEqual(Value.objects.count(), 2)
def test_get_with_eav(self):
p1 = Patient.objects.get_or_create(name='Bob', eav__age=6)
self.assertEqual(Patient.objects.get(eav__age=6), p1)
p2 = Patient.objects.get_or_create(name='Fred', eav__age=6)
self.assertRaises(Patient.MultipleObjectsReturned,
Patient.objects.get, eav__age=6)
def test_filtering_on_normal_and_eav_fields(self):
yes = self.yes
no = self.no
data = [
# Name Age Fever City Country
[ 'Bob', 12, no, 'New York', 'USA' ],
[ 'Fred', 15, no, 'Bamako', 'Mali' ],
[ 'Jose', 15, yes, 'Kisumu', 'Kenya' ],
[ 'Joe', 2, no, 'Nice', 'France'],
[ 'Beth', 21, yes, 'France', 'Nice' ]
]
for row in data:
Patient.objects.create(name=row[0], eav__age=row[1],
eav__fever=row[2], eav__city=row[3],
eav__country=row[4])
self.assertEqual(Patient.objects.count(), 5)
self.assertEqual(Value.objects.count(), 20)
self.assertEqual(Patient.objects.filter(eav__city__contains='Y').count(), 1)
self.assertEqual(Patient.objects.exclude(eav__city__contains='Y').count(), 4)
# Bob
self.assertEqual(Patient.objects.filter(Q(eav__city__contains='Y')).count(), 1)
# Everyone except Bob
#self.assertEqual(Patient.objects.exclude(Q(eav__city__contains='Y')).count(), 4)
# Bob, Fred, Joe
q1 = Q(eav__city__contains='Y') | Q(eav__fever=no)
self.assertEqual(Patient.objects.filter(q1).count(), 3)
# Joe
q2 = Q(eav__age=2)
self.assertEqual(Patient.objects.filter(q2).count(), 1)
# Joe
#self.assertEqual(Patient.objects.filter(q1 & q2).count(), 1)
# Jose
self.assertEqual(Patient.objects.filter(name__contains='J', eav__fever=yes).count(), 1)
def test_eav_through_foreign_key(self):
Patient.objects.create(name='Fred', eav__age=15)
p = Patient.objects.create(name='Jon', eav__age=15)
e = Encounter.objects.create(num=1, patient=p, eav__fever=self.yes)
self.assertEqual(Patient.objects.filter(eav__age=15, encounter__eav__fever=self.yes).count(), 1)
def test_manager_only_create(self):
class UserEavConfig(EavConfig):
manager_only = True
eav.register(User, UserEavConfig)
c = User.objects.create(username='joe')

View file

@ -1,73 +0,0 @@
from django.test import TestCase
import eav
from ..registry import Registry, EavConfig
from ..managers import EntityManager
from ..models import Attribute
from .models import Patient, Encounter
class RegistryTests(TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def register_encounter(self):
class EncounterEav(EavConfig):
manager_attr = 'eav_objects'
eav_attr = 'eav_field'
generic_relation_attr = 'encounter_eav_values'
generic_relation_related_name = 'encounters'
@classmethod
def get_attributes(cls):
return 'testing'
eav.register(Encounter, EncounterEav)
def test_registering_with_defaults(self):
eav.register(Patient)
self.assertTrue(hasattr(Patient, '_eav_config_cls'))
self.assertEqual(Patient._eav_config_cls.manager_attr, 'objects')
self.assertFalse(Patient._eav_config_cls.manager_only)
self.assertEqual(Patient._eav_config_cls.eav_attr, 'eav')
self.assertEqual(Patient._eav_config_cls.generic_relation_attr,
'eav_values')
self.assertEqual(Patient._eav_config_cls.generic_relation_related_name,
None)
eav.unregister(Patient)
def test_registering_overriding_defaults(self):
eav.register(Patient)
self.register_encounter()
self.assertTrue(hasattr(Patient, '_eav_config_cls'))
self.assertEqual(Patient._eav_config_cls.manager_attr, 'objects')
self.assertEqual(Patient._eav_config_cls.eav_attr, 'eav')
self.assertTrue(hasattr(Encounter, '_eav_config_cls'))
self.assertEqual(Encounter._eav_config_cls.get_attributes(), 'testing')
self.assertEqual(Encounter._eav_config_cls.manager_attr, 'eav_objects')
self.assertEqual(Encounter._eav_config_cls.eav_attr, 'eav_field')
eav.unregister(Patient)
eav.unregister(Encounter)
def test_unregistering(self):
old_mgr = Patient.objects
eav.register(Patient)
self.assertTrue(Patient.objects.__class__.__name__ == 'EntityManager')
eav.unregister(Patient)
self.assertFalse(Patient.objects.__class__.__name__ == 'EntityManager')
self.assertEqual(Patient.objects, old_mgr)
self.assertFalse(hasattr(Patient, '_eav_config_cls'))
def test_unregistering_unregistered_model_proceeds_silently(self):
eav.unregister(Patient)
def test_double_registering_model_is_harmless(self):
eav.register(Patient)
eav.register(Patient)

View file

@ -1,66 +0,0 @@
from django.test import TestCase
import eav
from ..registry import Registry, EavConfig
from ..managers import EntityManager
from .models import Patient, Encounter
class RegistryTests(TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def register_encounter(self):
class EncounterEav(EavConfig):
manager_attr = 'eav_objects'
eav_attr = 'eav_field'
generic_relation_attr = 'encounter_eav_values'
generic_relation_related_name = 'encounters'
eav.register(Encounter, EncounterEav)
def test_registering_with_defaults(self):
eav.register(Patient)
self.assertTrue(hasattr(Patient, '_eav_config_cls'))
self.assertEqual(Patient._eav_config_cls.manager_attr, 'objects')
self.assertFalse(Patient._eav_config_cls.manager_only)
self.assertEqual(Patient._eav_config_cls.eav_attr, 'eav')
self.assertEqual(Patient._eav_config_cls.generic_relation_attr,
'eav_values')
self.assertEqual(Patient._eav_config_cls.generic_relation_related_name,
None)
eav.unregister(Patient)
def test_registering_overriding_defaults(self):
eav.register(Patient)
self.register_encounter()
self.assertTrue(hasattr(Patient, '_eav_config_cls'))
self.assertEqual(Patient._eav_config_cls.manager_attr, 'objects')
self.assertEqual(Patient._eav_config_cls.eav_attr, 'eav')
self.assertTrue(hasattr(Encounter, '_eav_config_cls'))
self.assertEqual(Encounter._eav_config_cls.manager_attr, 'eav_objects')
self.assertEqual(Encounter._eav_config_cls.eav_attr, 'eav_field')
eav.unregister(Patient)
eav.unregister(Encounter)
def test_unregistering(self):
old_mgr = Patient.objects
eav.register(Patient)
self.assertTrue(Patient.objects.__class__.__name__ == 'EntityManager')
eav.unregister(Patient)
self.assertFalse(Patient.objects.__class__.__name__ == 'EntityManager')
self.assertEqual(Patient.objects, old_mgr)
self.assertFalse(hasattr(Patient, '_eav_config_cls'))
def test_unregistering_unregistered_model_proceeds_silently(self):
eav.unregister(Patient)
def test_double_registering_model_is_harmless(self):
eav.register(Patient)
eav.register(Patient)

View file

@ -1,63 +0,0 @@
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
#
# This software is derived from EAV-Django originally written and
# copyrighted by Andrey Mikhaylenko <http://pypi.python.org/pypi/eav-django>
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with EAV-Django. If not, see <http://gnu.org/licenses/>.
from datetime import datetime, date
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
def validate_text(value):
'''
Validates text
'''
if not (type(value) == unicode or type(value) == str):
raise ValidationError(_(u"Must be str or unicode"))
def validate_float(value):
try:
float(value)
except ValueError:
raise ValidationError(_(u"Must be a float"))
def validate_int(value):
try:
int(value)
except ValueError:
raise ValidationError(_(u"Must be an integer"))
def validate_date(value):
if not (isinstance(value, datetime) or isinstance(value, date)):
raise ValidationError(_(u"Must be a date or datetime"))
def validate_bool(value):
if not type(value) == bool:
raise ValidationError(_(u"Must be a boolean"))
def validate_object(value):
if not isinstance(value, models.Model):
raise ValidationError(_(u"Must be a django model object instance"))
if not value.pk:
raise ValidationError(_(u"Model has not been saved yet"))
def validate_enum(value):
from .models import EnumValue
if not isinstance(value, EnumValue):
raise ValidationError(_(u"Must be an EnumValue model object instance"))
if not value.pk:
raise ValidationError(_(u"EnumValue has not been saved yet"))