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:
Attaches
EntityManager
to your class. By default, it replaces standard manager (objects). You can configure under which attribute it is accessible withEavConfig
(see below).Binds your model’s post_init signal with
attach_eav_attr()
method. It is used to attachEntity
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()
Binds your model’s pre_save and post_save signals to
pre_save_handler()
andpost_save_hander()
, respectively. Those methods are responsible for validation and storage of attribute values.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)]>
Sets _eav_config_cls attribute storing model of the config class used by
Registry
. Defaults toEavConfig
; 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:
manager_attr
- Specifies manager name. Used to refer to the manager from Entity class, “objects” by default.manager_only
- Specifies whether signals and generic relation should be setup for the registered model.eav_attr
- Named of the Entity toolkit instance on the registered model instance. “eav” by default. See attach_eav_attr.generic_relation_attr
- Name of the GenericRelation to Value objects. “eav_values” by default.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 |
|
float |
|
text |
|
date |
|
bool |
|
object |
|
enum |
|
json |
|
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.