API Reference

If you are looking for information on a specific function, class, or method, this part of the documentation is for you.

Admin

This module contains classes used for admin integration.

class BaseEntityAdmin(model, admin_site)[source]

Custom admin model to support dynamic EAV fieldsets.

Overrides the default rendering of the change form in the Django admin to dynamically integrate EAV fields into the form fieldsets. This approach allows EAV attributes to be rendered alongside standard model fields within the admin interface.

eav_fieldset_title

Title for the dynamically added EAV fieldset.

Type:

str

eav_fieldset_description

Optional description for the EAV fieldset.

Type:

str

render_change_form(request, context, *args, **kwargs)[source]

Dynamically modifies the admin form to include EAV fields.

Identifies EAV fields associated with the instance being edited and dynamically inserts them into the admin form’s fieldsets. This method ensures EAV fields are appropriately displayed in a dedicated fieldset and avoids field duplication.

Parameters:
  • request – HttpRequest object representing the current request.

  • context – Dictionary containing context data for the form template.

  • *args – Variable length argument list.

  • **kwargs – Arbitrary keyword arguments.

Returns:

HttpResponse object representing the rendered change form.

class BaseEntityInlineFormSet(data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs)[source]

An inline formset that correctly initializes EAV forms.

add_fields(form, index)[source]

Add a hidden field for the object’s primary key.

class BaseEntityInline(parent_model, admin_site)[source]

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

alias of BaseEntityInlineFormSet

get_fieldsets(request, obj=None)[source]

Hook for specifying fieldsets.

class AttributeAdmin(model, admin_site)[source]

Decorators

This module contains pure wrapper functions used as decorators. Functions in this module should be simple and not involve complex logic.

register_eav(**kwargs)[source]

Registers the given model(s) classes and wrapped Model class with Django EAV 2:

@register_eav
class Author(models.Model):
    pass

Fields

class EavDatatypeField(*args, db_collation=None, **kwargs)[source]

The datatype field used by Attribute.

validate(value, instance)[source]

Raise ValidationError if they try to change the datatype of an Attribute that is already used by Value objects.

class CSVField(separator=';', *args, **kwargs)[source]
deconstruct()[source]

Return enough information to recreate the field as a 4-tuple:

  • The name of the field on the model, if contribute_to_class() has been run.

  • The import path of the field, including the class, e.g. django.db.models.IntegerField. This should be the most portable version, so less specific may be better.

  • A list of positional arguments.

  • A dict of keyword arguments.

Note that the positional or keyword arguments must contain values of the following types (including inner values of collection types):

  • None, bool, str, int, float, complex, set, frozenset, list, tuple, dict

  • UUID

  • datetime.datetime (naive), datetime.date

  • top-level classes, top-level functions - will be referenced by their full import path

  • Storage instances - these have their own deconstruct() method

This is because the values here must be serialized into a text format (possibly new Python code, possibly JSON) and these are the only types with encoding handlers defined.

There’s no need to return the exact way the field was instantiated this time, just ensure that the resulting field is the same - prefer keyword arguments over positional ones, and omit parameters with their default values.

formfield(**kwargs)[source]

Return a django.forms.Field instance for this field.

to_python(value)[source]

Convert the input value into the expected Python data type, raising django.core.exceptions.ValidationError if the data can’t be converted. Return the converted value. Subclasses should override this.

get_prep_value(value)[source]

Perform preliminary non-db specific value checks and conversions.

value_to_string(obj)[source]

Return a string value of this field from the passed obj. This is used by the serialization framework.

Forms

This module contains forms used for admin integration.

class BaseDynamicEntityForm(data=None, *args, **kwargs)[source]

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

Mapping between attribute types and field classes is as follows:

Type

Field

text

CharField

float

IntegerField

int

DateTimeField

date

SplitDateTimeField

bool

BooleanField

enum

ChoiceField

json

JSONField

csv

CSVField

save(commit=True)[source]

Saves this form’s cleaned_data into model instance self.instance and related EAV attributes. Returns instance.

property media

Return all media required to render the widgets on this form.

Managers

This module contains the custom manager used by entities registered with eav.

class EntityManager(*args, **kwargs)[source]

Our custom manager, overrides models.Manager.

create(**kwargs)[source]

Parse eav attributes out of kwargs, then try to create and save the object, then assign and save it’s eav attributes.

get_or_create(defaults=None, **kwargs)[source]

Reproduces the behavior of get_or_create, eav friendly.

Models

This module defines the four concrete, non-abstract models:

Along with the Entity helper class and EAVModelMeta optional metaclass for each eav model class.

class Attribute(*args, **kwargs)[source]

Putting the A in EAV. This holds the attributes, or concepts. Examples of possible Attributes: color, height, weight, number of children, number of patients, has fever?, etc…

Each attribute has a name, and a description, along with a slug that must be unique. If you don’t provide a slug, a default slug (derived from name), will be created.

The required field is a boolean that indicates whether this EAV attribute is required for entities to which it applies. It defaults to False.

Warning

Just like a normal model field that is required, you will not be able to save or create any entity object for which this attribute applies, without first setting this EAV attribute.

There are 7 possible values for datatype:

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

Examples:

Attribute.objects.create(name='Height', datatype=Attribute.TYPE_INT)
# = <Attribute: Height (Integer)>

Attribute.objects.create(name='Color', datatype=Attribute.TYPE_TEXT)
# = <Attribute: Color (Text)>

yes = EnumValue.objects.create(value='yes')
no = EnumValue.objects.create(value='no')
unknown = EnumValue.objects.create(value='unknown')
ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.values.add(yes, no, unknown)

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

Warning

Once an Attribute has been used by an entity, you can not change it’s datatype.

name

Main identifier for the attribute. Upon creation, slug is autogenerated from the name. (see create_slug_from_name()).

slug

Warning

This attribute should be used with caution. Setting this to True means that all entities that can have this attribute will be required to have a value for it.

entity_ct

This field allows you to specify a relationship with any number of content types. This would be useful, for example, if you wanted an attribute to apply only to a subset of entities. In that case, you could filter by content type in the get_attributes() method of that entity’s config.

natural_key() Tuple[str, str][source]

Retrieve the natural key for the Attribute instance.

The natural key for an Attribute is defined by its name and slug. This method returns a tuple containing these two attributes of the instance.

Returns:

tuple

Return type:

A tuple containing the name and slug of the Attribute instance.

get_validators()[source]

Returns the appropriate validator function from validators as a list (of length one) for the datatype.

Note

The reason it returns it as a list, is eventually we may want this method to look elsewhere for additional attribute specific validators to return as well as the default, built-in one.

validate_value(value)[source]

Check value against the validators returned by get_validators() for this attribute.

save(*args, **kwargs)[source]

Saves the Attribute and auto-generates a slug field if one wasn’t provided.

exception DoesNotExist
exception MultipleObjectsReturned
clean()[source]

Validates the attribute. Will raise ValidationError if the attribute’s datatype is TYPE_ENUM and enum_group is not set, or if the attribute is not TYPE_ENUM and the enum group is set.

get_choices()[source]

Returns a query set of EnumValue objects for this attribute. Returns None if the datatype of this attribute is not TYPE_ENUM.

save_value(entity, value)[source]

Called with entity, any Django object registered with eav, and value, the Value this attribute for entity should be set to.

If a Value object for this entity and attribute doesn’t exist, one will be created.

Note

If value is None and a Value object exists for this Attribute and entity, it will delete that Value object.

class EnumGroup(*args, **kwargs)[source]

EnumGroup objects have two fields - a name CharField and values, a ManyToManyField to EnumValue. Attribute classes with datatype TYPE_ENUM have a ForeignKey field to EnumGroup.

See EnumValue for an example.

natural_key() Tuple[str][source]

Retrieve the natural key for the EnumGroup instance.

The natural key for an EnumGroup is defined by its name. This method returns the name of the instance as a single-element tuple.

Returns:

tuple

Return type:

A tuple containing the name of the EnumGroup instance.

exception DoesNotExist
exception MultipleObjectsReturned
class EnumValue(*args, **kwargs)[source]

EnumValue objects are the value ‘choices’ to multiple choice TYPE_ENUM Attribute objects. They have only one field, value, a CharField that must be unique.

For example:

yes = EnumValue.objects.create(value='Yes') # doctest: SKIP
no = EnumValue.objects.create(value='No')
unknown = EnumValue.objects.create(value='Unknown')

ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
ynu.values.add(yes, no, unknown)

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

Note

The same EnumValue objects should be reused within multiple EnumGroups. For example, if you have one EnumGroup called: Yes / No / Unknown and another called Yes / No / Not applicable, you should only have a total of four EnumValues objects, as you should have used the same Yes and No EnumValues for both EnumGroups.

natural_key() Tuple[str][source]

Retrieve the natural key for the EnumValue instance.

The natural key for an EnumValue is defined by its value. This method returns the value of the instance as a single-element tuple.

Returns:

tuple

Return type:

A tuple containing the value of the EnumValue instance.

exception DoesNotExist
exception MultipleObjectsReturned
class Value(*args, **kwargs)[source]

Putting the V in EAV.

This model stores the value for one particular Attribute for some entity.

As with most EAV implementations, most of the columns of this model will be blank, as onle one value_ field will be used.

Example:

import eav
from django.contrib.auth.models import User

eav.register(User)

u = User.objects.create(username='crazy_dev_user')
a = Attribute.objects.create(name='Fav Drink', datatype='text')

Value.objects.create(entity = u, attribute = a, value_text = 'red bull')
# = <Value: crazy_dev_user - Fav Drink: "red bull">
natural_key() Tuple[Tuple[str, str], int, str][source]

Retrieve the natural key for the Value instance.

The natural key for a Value is a combination of its attribute natural key, entity_id, and entity_uuid. This method returns a tuple containing these three elements.

Returns:

tuple – and entity UUID of the Value instance.

Return type:

A tuple containing the natural key of the attribute, entity ID,

save(*args, **kwargs)[source]

Validate and save this value.

property value

Return the python object this value is holding.

exception DoesNotExist
exception MultipleObjectsReturned
class Entity(instance)[source]

Helper class that will be attached to entities registered with eav.

static pre_save_handler(sender, *args, **kwargs)[source]

Pre save handler attached to self.instance. Called before the model instance we are attached to is saved. This allows us to call validate_attributes() before the entity is saved.

static post_save_handler(sender, *args, **kwargs)[source]

Post save handler attached to self.instance. Calls save() when the model instance we are attached to is saved.

get_all_attributes()[source]

Return a query set of all Attribute objects that can be set for this entity.

save()[source]

Saves all the EAV values that have been set on this entity.

validate_attributes()[source]

Called before save(), first validate all the entity values to make sure they can be created / saved cleanly. Raises ValidationError if they can’t be.

get_values()[source]

Get all set Value objects for self.instance.

get_all_attribute_slugs()[source]

Returns a list of slugs for all attributes available to this entity.

get_attribute_by_slug(slug)[source]

Returns a single Attribute with slug.

get_value_by_attribute(attribute)[source]

Returns a single Value for attribute.

get_object_attributes()[source]

Returns entity instance attributes, except for instance and ct which are used internally.

class EAVModelMeta(name, bases, namespace, **kwds)[source]

Queryset

This module contains custom EavQuerySet class used for overriding relational operators and pure functions for rewriting Q-expressions. Q-expressions need to be rewritten for two reasons:

  1. In order to hide implementation from the user and provide easy to use syntax sugar, i.e.:

    Supplier.objects.filter(eav__city__startswith='New')
    

    instead of:

    city_values = Value.objects.filter(value__text__startswith='New')
    Supplier.objects.filter(eav_values__in=city_values)
    

    For details see: eav_filter().

  2. To ensure that Q-expression tree is compiled to valid SQL. For details see: rewrite_q_expr().

is_eav_and_leaf(expr, gr_name)[source]

Checks whether Q-expression is an EAV AND leaf.

Parameters:
  • expr (Union[Q, tuple]) – Q-expression to be checked.

  • gr_name (str) – Generic relation attribute name, by default ‘eav_values’

Returns:

bool

rewrite_q_expr(model_cls, expr)[source]

Rewrites Q-expression to safe form, in order to ensure that generated SQL is valid.

All EAV values are stored in a single table. Therefore, INNER JOIN generated for the AND-expression (1) will always fail, i.e. single row in a eav_values table cannot be both in two disjoint sets at the same time (and the whole point of using AND, usually, is two have two different sets). Therefore, we must paritially rewrite the expression so that the generated SQL is valid.

This is done by merging dangerous AND’s and substituting them with explicit pk__in filter, where pks are taken from evaluated Q-expr branch.

Args:
model_cls (TypeVar): model class used to construct QuerySet()

from leaf attribute-value expression. expr: (Q | tuple): Q-expression (or attr-val leaf) to be rewritten.

Returns:

Union[Q, tuple]

eav_filter(func)[source]

Decorator used to wrap filter and exclude methods. Passes args through expand_q_filters() and kwargs through expand_eav_filter(). Returns the called function (filter or exclude).

expand_q_filters(q, root_cls)[source]

Takes a Q object and a model class. Recursively passes each filter / value in the Q object tree leaf nodes through expand_eav_filter().

expand_eav_filter(model_cls, key, value)[source]

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')
class EavQuerySet(model=None, query=None, using=None, hints=None)[source]

Overrides relational operators for EAV models.

filter(*args, **kwargs)[source]

Pass args and kwargs through eav_filter(), then pass to the Manager filter method.

exclude(*args, **kwargs)[source]

Pass args and kwargs through eav_filter(), then pass to the Manager exclude method.

get(*args, **kwargs)[source]

Pass args and kwargs through eav_filter(), then pass to the Manager get method.

order_by(*fields)[source]

Return a new QuerySet instance with the ordering changed.

Registry

This modules contains the registry classes.

class EavConfig[source]

The default EavConfig class used if it is not overridden on registration. This is where all the default eav attribute names are defined.

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.

classmethod get_attributes(instance=None)[source]

By default, all Attribute object apply to an entity, unless you provide a custom EavConfig class overriding this.

class Registry(model_cls)[source]

Handles registration through the register() and unregister() methods.

static register(model_cls, config_cls=None)[source]

Registers model_cls with eav. You can pass an optional config_cls to override the EavConfig defaults.

Note

Multiple registrations for the same entity are harmlessly ignored.

static unregister(model_cls)[source]

Unregisters model_cls with eav.

Note

Unregistering a class not already registered is harmlessly ignored.

static attach_eav_attr(sender, *args, **kwargs)[source]

Attach EAV Entity toolkit to an instance after init.

Validators

This module contains a validator for each Attribute datatype.

A validator is a callable that takes a value and raises a ValidationError if it doesn’t meet some criteria (see Django validators).

These validators are called by the validate_value() method in the Attribute model.

validate_text(value)[source]

Raises ValidationError unless value type is str or unicode

validate_float(value)[source]

Raises ValidationError unless value can be cast as a float

validate_int(value)[source]

Raises ValidationError unless value can be cast as an int

validate_date(value)[source]

Raises ValidationError unless value is an instance of datetime or date

validate_bool(value)[source]

Raises ValidationError unless value type is bool

validate_object(value)[source]

Raises ValidationError unless value is a saved django model instance.

validate_enum(value)[source]

Raises ValidationError unless value is a saved EnumValue model instance.

validate_json(value)[source]

Raises ValidationError unless value can be cast as an json object (a dict)

validate_csv(value)[source]

Raises ValidationError unless value is a c-s-v value.