Usage

This part of the documentation will take you through all of library’s usage patterns. Before you can use EAV attributes, however, you need to register your models.

Simple Registration

Basic registration is very simple. You can do it with register() method:

import eav
eav.register(Parts)

or with decorators:

from eav.decorators import register_eav

@register_eav
class Supplier(models.Model):
    ...

Generally, if you chose the former, the most appriopriate place for the statement would be at the bottom of your models.py or immmediately after model definition.

Advanced Registration

Under the hood, registration does a couple of things:

  1. Attaches EntityManager to your class. By default, it replaces standard manager (objects). You can configure under which attribute it is accessible with EavConfig (see below).
  2. Binds your model’s post_init signal with attach_eav_attr() method. It is used to attach Entity helper object to each model instance. Entity, in turn, is used to retrieve, store and validate attribute values. By default, it’s accessible under eav attribute:
part.eav.weight = 0.56
part.save()
  1. Binds your model’s pre_save and post_save signals to pre_save_handler() and post_save_hander(), respectively. Those methods are responsible for validation and storage of attribute values.
  2. Setups up generic relation to Value set. By default, it’s accessed under eav_values:
patient.eav_values.all()
# = <QuerySet [has fever?: "True" (1), temperature: 37.7 (2)]>
  1. Sets _eav_config_cls attribute storing model of the config class used by Registry. Defaults to EavConfig; can be overridden (see below).

With that out of the way, almost every aspect of the registration can be customized. All available options are provided to registration via config class: EavConfig passed to register(). You can change them by overriding the class. Available options are as follows:

  1. manager_attr - Specifies manager name. Used to refer to the manager from Entity class, “objects” by default.
  2. manager_only - Specifies whether signals and generic relation should be setup for the registered model.
  3. eav_attr - Named of the Entity toolkit instance on the registered model instance. “eav” by default. See attach_eav_attr.
  4. generic_relation_attr - Name of the GenericRelation to Value objects. “eav_values” by default.
  5. generic_relation_related_name - Name of the related name for GenericRelation from Entity to Value. None by default. Therefore, if not overridden, it is not possible to query Values by Entities.

Example registration may look like:

class SupplierEavConfig(EavConfig):
    manager_attr = 'eav_objects'

eav.register(supplier, SupplierEavConfig)

Note

As of now, configurable registration is not supported via class decorator. You have to use explicit method call.

Additionally, EavConfig provides classmethod get_attributes() which is used to determine a set of attributes available to a given model. By default, it returns Attribute.objects.all(). As usual, it can be customized:

from eav.models import Attribute

class SomeModelEavConfig(EavConfig):
    @classmethod
    def get_attributes(cls):
        return Attribute.objects.filter(slug__startswith='a')

Attribute validation includes checks against illegal attribute value assignments. This means that value assignments for attributes which are excluded for the model are treated with IllegalAssignmentException. For example (extending previous one):

some_model.eav.beard = True
some_model.save()

will throw an exception.

Creating Attributes

Once your models are registered, you can starting creating attributes for them. Two most important attributes of Attribute class are slug and datatype. slug is a unique global identifier (there must be at most one Attribute instance with given slug) and must be a valid Python variable name, as it’s used to access values for that attribute from Entity helper:

from eav.models import Attribute

Attribute.objects.create(slug='color', datatype=Attribute.TYPE_TEXT)
flower.eav.color = 'red'

# Alternatively, assuming you're using default EntityManager:
Attribute.objects.create(slug='color', datatype=Attribute.TYPE_TEXT)
Flower.objects.create(name='rose', eav__color='red')

datatype determines type of attribute (and by extension type of value stored in Value). Available choices are:

Type Attribute Constant
int TYPE_INT
float TYPE_FLOAT
text TYPE_TEXT
date TYPE_DATE
bool TYPE_BOOLEAN
object TYPE_OBJECT
enum TYPE_ENUM

If you want to create an attribute with data-type enum, you need to provide it with enum_group:

from eav.models import EnumValue, EnumGroup, Attribute

true = EnumValue.objects.create(value='Yes')
false = EnumValue.objects.create(value='No')
bool_group = EnumGroup.objects.create(name='Yes / No')
bool_group.enums.add(true, false)

Attribute.objects.create(
    name='hungry?',
    datatype=Attribute.TYPE_ENUM,
    enum_group=bool_group
)
# = <Attribute: hungry? (Multiple Choice)>

Finally, attribute type object allows to relate Django model instances via generic foreign keys:

Attribute.objects.create(name='Supplier', datatype=Attribute.TYPE_OBJECT)

steve = Supplier.objects.create(name='Steve')
cog = Part.objects.create(name='Cog', eav__supplier=steve)

cog.eav.supplier
# = <Supplier: Steve (1)>

Filtering By Attributes

Once you’ve created your attributes and values for them, you can use them to filter Django models. Django EAV 2 is using the same notation as Django’s foreign-keys:

Part.objects.filter(eav__weight=10)
Part.objects.filter(eav__weight__gt=10)
Part.objects.filter(eav__code__startswith='A')

# Of course, you can mix them with regular queries:
Part.objects.filter(name='Cog', eav__height=7.8)

# Querying enums looks as follows:
yes = EnumValue.objects.get(name='Yes')
Part.objects.filter(eav__is_available=yes)

You can use Q expressions too:

Patient.objects.filter(
    Q(eav__sex='male', eav__fever=no) | Q(eav__city='Nice') & Q(eav__age__gt=32)
)

Admin Integration

Django EAV 2 includes integration for Django’s admin. As usual, you need to register your model first:

from django.contrib import admin
from eav.forms import BaseDynamicEntityForm
from eav.admin import BaseEntityAdmin

class PatientAdminForm(BaseDynamicEntityForm):
    model = Patient

class PatientAdmin(BaseEntityAdmin):
    form = PatientAdminForm

admin.site.register(Patient, PatientAdmin)