django-eav provides an Entity-Attribute-Value storage model for django apps.
For a decent explanation of what an Entity-Attribute-Value storage model is, check Wikipedia.
Note
This software was inspired / derived from the excellent eav-django written by Andrey Mikhaylenko.
There are a few notable differences between this implementation and the eav-django implementation.
You can install django-eav directly from guthub:
pip install -e git+git://github.com/mvpdev/django-eav.git#egg=django-eav
After installing, add eav to your INSTALLED_APPS in your project’s settings.py file.
In order to attach EAV attributes to a model, you first need to register it (just like you may register your models with django.contrib.admin).
Registering a model with eav does a few things:
If that all sounds too complicated, don’t worry, you really don’t need to think about it. Just thought you should know.
To register any model with EAV, you simply need to add the registration line somewhere that will be executed when django starts:
import eav
eav.register(MyModel)
Generally, the most appropriate place for this would be in your models.py immediately after your model definition.
Advanced registration is only required if:
Advanced registration is simple, and is performed the exact same way you override the django.contrib.admin registration defaults.
You just need to define your own config class that subclasses EavConfig and override the default class attributes and method.
There are five EavConfig class attributes you can override:
Class Attribute | Default | Description |
---|---|---|
manager_attr | 'objects' | The name of the eav manager |
manager_only | False | boolean Whether to only replace the manager, and do nothing else |
eav_attr | 'eav' | The attribute name of the entity helper |
generic_relation_attr | 'eav_values' | The attribute name of the generic relation helper |
generic_relation_related_name | The model’s __class__.__name__ | The related name of the related name of the generic relation to your model |
An example of just choosing a different name for the manager (and thus leaving objects intact):
class MyEavConfigClass(EavConfig):
manager_attr = 'eav_objects'
eav.register(MyModel, MyEavConfigClass)
Additionally, EavConfig defines a classmethod called get_attributes that, by default will return Attribute.objects.all() This method is used to determine which Attribute can be applied to the entity model you are registering. If you want to limit which attributes can be applied to your entity, you would need to override it.
For example:
class MyEavConfigClass(EavConfig):
@classmethod
def get_attributes(cls):
return Attribute.objects.filter(type='person')
eav.register(MyModel, MyEavConfigClass)
Once you’ve registered your model(s), you can begin to use them with EAV attributes. Let’s assume your model is called Person and it has one normal django CharField called name, but you want to be able to dynamically store other data about each Person.
First, let’s create some attributes:
>>> Attribute.objects.create(name='Weight', datatype=Attribute.TYPE_FLOAT)
>>> Attribute.objects.create(name='Height', datatype=Attribute.TYPE_INT)
>>> Attribute.objects.create(name='Is pregant?', datatype=Attribute.TYPE_BOOLEAN)
Now let’s create a patient, and set some of these attributes:
>>> p = Patient.objects.create(name='Bob')
>>> p.eav.height = 46
>>> p.eav.weight = 42.2
>>> p.eav.is_pregnant = False
>>> p.save()
>>> bob = Patient.objects.get(name='Bob')
>>> bob.eav.height
46
>>> bob.eav.weight
42.2
>>> bob.is_pregnant
False
Additionally, assuming we’re using the eav manager, we can also do:
>>> p = Patient.objects.create(name='Jen', eav__height=32, eav__pregnant=True)
eav attributes are filterable, using the same __ notation as django foreign keys:
Patient.objects.filter(eav__weight=42.2)
Patient.objects.filter(eav__weight__gt=42)
Patient.objects.filter(name='Bob', eav__weight__gt=42)
Patient.objects.exclude(eav__is_pregnant=False)
You can even use Q objects, however there are some known issues (see Q Object Filters) with Q object filters:
Patient.objects.filter(Q(name='Bob') | Q(eav__is_pregnant=False))
What about if you have a foreign key to a model that uses eav, but you want to filter from a model that doesn’t use eav? For example, let’s say you have a Patient model that doesn’t use eav, but it has a foreign key to Encounter that does use eav. You can even filter through eav across this relationship, but you need to use the eav manager for Patient.
Just register Patient with eav, but set manager_only = True see (see Advanced Registration). Then you can do:
Patient.objects.filter(encounter__eav__weight=2)
You can even have your eav attributes show up just like normal fields in your models admin pages. Just register using the eav admin class:
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)
Due to an unexplained Q object / generic relation issue, exclude filters with EAV Q objects, or EAV Q objects ANDed together may produce inaccurate results.