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 appropriate place for the statement would be at the bottom of your models.py or immediately 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 and passing it as a second argument. 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

json

TYPE_JSON

csv

TYPE_CSV

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)>

The attribute type json allows to store them in JSON format, which internally use JSONField:

Attribute.objects.create(name='name_intl', datatype=Attribute.TYPE_JSON)

prod = Product.objects.create(sku='PRD00001', eav__name_intl={
    "es": "Escoba Verde",
    "en": "Green Broom",
    "it": "Scopa Verde"
})

prod2 = Product.objects.create(sku='PRD00002', eav__name_intl={
    "es": "Escoba Roja",
    "en": "Red Broom"
})

prod3 = Product.objects.create(sku='PRD00003', eav__name_intl={
    "es": "Escoba Azul",
    "it": "Scopa Blu"
})

prod.eav.name_intl
{'es': 'Escoba Verde', 'en': 'Green Broom', 'it': 'Scopa Verde'}

type(prod.eav.name_intl)
dict

Product.objects.filter(eav__name_intl__has_key="it")
<EavQuerySet [<Product: PRD00001>, <Product: PRD00003>]>

The attribute type csv allows to store Comma Separated Values, using “;” as a separator:

Attribute.objects.create(name='colors', datatype=Attribute.TYPE_CSV)

prod = Product.objects.create(sku='PRD00001', eav__colors="red;green;blue")

prod2 = Product.objects.create(sku='PRD00002', eav__colors="red;green")

prod3 = Product.objects.create(sku='PRD00003', eav__colors="red;blue")

prod4 = Product.objects.create(sku='PRD00004', eav__colors="")

prod.eav.colors
["red", "green", "blue"]

type(prod.eav.name_intl)
list

Product.objects.filter(eav__name_colors="green")
<EavQuerySet [<Product: PRD00001>, <Product: PRD00002>]>

Product.objects.filter(~Q(eav__name_colors__isnull=False))
<EavQuerySet [<Product: PRD00004>]>

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 works either by enum instance or by it's text representation as follows:
yes = EnumValue.objects.get(name='Yes')
Part.objects.filter(eav__is_available=yes)  # via EnumValue
Part.objects.filter(eav__is_available='yes)  # via EnumValue's value

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 seamlessly integrates with Django’s admin interface by providing dynamic attribute management directly within the admin panel. This feature provides the EAV Attributes as a separate fieldset, whether use the base fieldset or when providing your own.

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)

Customizing the EAV Fieldset

The Django EAV 2 integration allows you to customize the presentation of EAV attributes in the admin interface through the use of a dedicated fieldset. You can configure this fieldset by setting eav_fieldset_title and eav_fieldset_description within your admin class.