diff --git a/.gitignore b/.gitignore index bd692ad..83f0300 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.orig *.db *.sqlite* +_build diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 2c21a23..0000000 --- a/__init__.py +++ /dev/null @@ -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 -# -# 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 . -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) diff --git a/admin.py b/admin.py deleted file mode 100644 index 95cf511..0000000 --- a/admin.py +++ /dev/null @@ -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 -# -# 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 . - - -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) - diff --git a/docs/_static/.gitignore b/docs/_static/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/_templates/.gitignore b/docs/_templates/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/fields.py b/fields.py deleted file mode 100644 index d31052c..0000000 --- a/fields.py +++ /dev/null @@ -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 -# -# 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 . - -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.")) - diff --git a/forms.py b/forms.py deleted file mode 100644 index b5f2e0c..0000000 --- a/forms.py +++ /dev/null @@ -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 -# -# 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 . - -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 diff --git a/managers.py b/managers.py deleted file mode 100644 index 1836bf0..0000000 --- a/managers.py +++ /dev/null @@ -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 -# -# 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 . - -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) diff --git a/models.py b/models.py deleted file mode 100644 index 540b048..0000000 --- a/models.py +++ /dev/null @@ -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 -# -# 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 . -''' -.. 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.objects.create(name='Color', datatype='text', slug='color') - - ''' - 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') - - - ''' - - 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 diff --git a/registry.py b/registry.py deleted file mode 100644 index 6089977..0000000 --- a/registry.py +++ /dev/null @@ -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 -# -# 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 . - -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() diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index a44620f..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .registry import * -from .limiting_attributes import * -from .data_validation import * -from .misc_models import * -from .queries import * diff --git a/tests/data_validation.py b/tests/data_validation.py deleted file mode 100644 index 93755c8..0000000 --- a/tests/data_validation.py +++ /dev/null @@ -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) diff --git a/tests/limiting_attributes.py b/tests/limiting_attributes.py deleted file mode 100644 index be4af71..0000000 --- a/tests/limiting_attributes.py +++ /dev/null @@ -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')) diff --git a/tests/misc_models.py b/tests/misc_models.py deleted file mode 100644 index 2cbdd8f..0000000 --- a/tests/misc_models.py +++ /dev/null @@ -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) diff --git a/tests/models.py b/tests/models.py deleted file mode 100644 index 0c54584..0000000 --- a/tests/models.py +++ /dev/null @@ -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) - diff --git a/tests/queries.py b/tests/queries.py deleted file mode 100644 index 9e40d6c..0000000 --- a/tests/queries.py +++ /dev/null @@ -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') diff --git a/tests/registry.py b/tests/registry.py deleted file mode 100644 index 92bf115..0000000 --- a/tests/registry.py +++ /dev/null @@ -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) diff --git a/tests/set_and_get.py b/tests/set_and_get.py deleted file mode 100644 index 36fa372..0000000 --- a/tests/set_and_get.py +++ /dev/null @@ -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) diff --git a/validators.py b/validators.py deleted file mode 100644 index 3559ae9..0000000 --- a/validators.py +++ /dev/null @@ -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 -# -# 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 . -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"))