API Reference

Admin

class eav.admin.BaseEntityAdmin(model, admin_site)

Base class for entity admin classes.

render_change_form(request, context, **kwargs)

Wrapper for ModelAdmin.render_change_form. Replaces standard static AdminForm with an EAV-friendly one. The point is that our form generates fields dynamically and fieldsets must be inferred from a prepared and validated form instance, not just the form class. Django does not seem to provide hooks for this purpose, so we simply wrap the view and substitute some data.

class eav.admin.BaseSchemaAdmin(model, admin_site)

Base class for schema admin classes.

class eav.admin.BaseEntityInline(parent_model, admin_site)

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
formset

alias of BaseEntityInlineFormSet

Facets

class eav.facets.Facet(facet_set, schema=None, field=None, lookup_prefix='')

Base class for facets. Concrete facets must overload at least the field_class attribute.

attr_name

Returns attribute name for this facet

form_field

Returns appropriate form field.

get_lookups(value)

Returns dictionary of lookups for facet-specific query.

class eav.facets.TextFacet(*args, **kwargs)

Represents a text field or schema. Allows one choice at a time. Displays a set of radiobuttons. If there are many choices, the radiobuttons are replaced with a drop-down select box.

Inherits param from Facet. Provides these additional params:

Parameters:max_radio_choices – integer: if there are less choices than given number, then radiobuttons are used; if more, then drop-down select box is used.
class eav.facets.MultiTextFacet(*args, **kwargs)

Represents a text field or schema. Allows multiple choices.

class eav.facets.ManyToManyFacet(facet_set, schema=None, field=None, lookup_prefix='')

Represents a many-to-many field.

field_class

alias of ModelMultipleChoiceField

get_lookups(value)

Returns dictionary of lookups for facet-specific query.

class eav.facets.OneToManyFacet(facet_set, schema=None, field=None, lookup_prefix='')

Represents a one-to-many field.

field_class

alias of ModelChoiceField

get_lookups(value)

Returns dictionary of lookups for facet-specific query.

class eav.facets.IntegerFacet(facet_set, schema=None, field=None, lookup_prefix='')

Represents an integer field.

class eav.facets.RangeFacet(facet_set, schema=None, field=None, lookup_prefix='')

A simple range facet: two widgets, one attribute value (number).

field_class

alias of RangeField

class eav.facets.MultiRangeFacet(facet_set, schema=None, field=None, lookup_prefix='')

A complex range facet: two widgets, two attribute values (numbers).

field_class

alias of RangeField

class eav.facets.DateFacet(facet_set, schema=None, field=None, lookup_prefix='')

Represents a date field.

class eav.facets.BooleanFacet(facet_set, schema=None, field=None, lookup_prefix='')

Represents a boolean field.

field_class

alias of NullBooleanField

class eav.facets.BaseFacetSet(data)

Base class for facet sets. Concrete classes must overload at least the get_queryset attribute.

get_field_and_lookup(name)

Returns field instance and lookup prefix for given attribute name. Can be overloaded in subclasses to provide filtering across multiple models.

sort_by_attribute(qs, name)

A wrapper around standard order_by() method. Allows to sort by both normal fields and EAV attributes without thinking about implementation details. Usage:

qs = sort_by_attributes(qs, 'price', 'colour')

...where price is a FloatField, and colour is the name of an EAV attribute represented by Schema and Attr models.

Fields

class eav.fields.RangeField(*args, **kwargs)

A multi-value field which consists of tho float fields.

widget

alias of RangeWidget

Forms

class eav.forms.BaseSchemaForm(data=None, files=None, auto_id=u'id_%s', prefix=None, initial=None, error_class=<class 'django.forms.util.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None)

Base class for schema forms.

clean_name()

Avoid name clashes between static and dynamic attributes.

class eav.forms.BaseDynamicEntityForm(data=None, *args, **kwargs)

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

check_eav_allowed()

Returns True if dynamic attributes can be added to this form. If False is returned, only normal fields will be displayed.

save(commit=True)

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

Returns instance.

Object Managers

Models

class eav.models.BaseAttribute(*args, **kwargs)

Base class for choices. Concrete choice class must overload the schema and choice attributes.

class eav.models.BaseChoice(*args, **kwargs)

Base class for choices. Concrete choice class must overload the schema attribute.

class eav.models.BaseEntity(*args, **kwargs)

Entity, the “E” in EAV. This model is abstract and must be subclassed. See tests for examples.

check_eav_allowed()

Returns True if entity instance allows EAV attributes to be attached.

Can be useful if some external data is required to determine available schemata and that data may be missing. In such cases this method should be overloaded to check whether the data is available.

is_valid()

Returns True if attributes and their values conform with schema.

save(force_eav=False, **kwargs)

Saves entity instance and creates/updates related attribute instances.

Parameters:eav – if True (default), EAV attributes are saved along with entity.
class eav.models.BaseSchema(*args, **kwargs)

Metadata for an attribute.

get_attrs(entity)

Returns available attributes for given entity instance. Handles many-to-one relations transparently.

get_choices()

Returns a queryset of choice objects bound to this schema.

save_attr(entity, value)

Saves given EAV attribute with given value for given entity.

If schema is not a choice, the value is saved to the corresponding Attr instance (which is created or updated).

If schema is an cvhoice (one-to-one or many-to-one), the value is processed thusly:

  • if value is iterable, all Attr instances for corresponding managed choice schemata are updated (those with names from the value list are set to True, others to False). If a list item is not in available choices, ValueError is raised;
  • if the value is None, all corresponding Attr instances are reset to False;
  • if the value is neither a list nor None, it is wrapped into a list and processed as above (i.e. “foo” –> [“foo”]).

Tests

## ## basic EAV ##

>>> colour = Schema.objects.create(title='Colour', datatype=Schema.TYPE_TEXT)
>>> colour
<Schema: Colour (text)>
>>> colour.name              #  <-- automatically generated from title
u'colour'
>>> taste = Schema.objects.create(title='Taste', datatype=Schema.TYPE_TEXT)
>>> age = Schema.objects.create(title='Age', datatype=Schema.TYPE_FLOAT)
>>> can_haz = Schema.objects.create(title='I can haz it', datatype=Schema.TYPE_BOOLEAN)
>>> can_haz.name
u'i_can_haz_it'
>>> e = Entity.objects.create(title='Apple', colour='green')
>>> e.title
'Apple'
>>> e.colour
'green'
>>> e.attrs.all()
[<Attr: Apple: Colour "green">]
>>> e.taste = 'sweet'
>>> e.attrs.all()
[<Attr: Apple: Colour "green">]
>>> e.colour = 'yellow'
>>> e.save()
>>> e.attrs.all()
[<Attr: Apple: Colour "yellow">, <Attr: Apple: Taste "sweet">]
>>> [x for x in e]
[<Attr: Apple: Colour "yellow">, <Attr: Apple: Taste "sweet">]
>>> Entity.objects.filter(title='Apple')
[<Entity: Apple>]
>>> Entity.objects.filter(colour='yellow')
[<Entity: Apple>]
>>> Entity.objects.filter(colour='yellow', title='Apple')
[<Entity: Apple>]

## ## range ##

>>> weight_range = Schema.objects.create(name='weight_range',
...                                      title='Weight range',
...                                      datatype=Schema.TYPE_RANGE)
>>> e = Entity.objects.all()[0]    # reload schemata cache

# try setting wrong values

>>> e.weight_range = 1
>>> e.save()
Traceback (most recent call last):
...
TypeError: Range value must be an iterable, got "1".
>>> e.weight_range = 1, 2, 3
>>> e.save()
Traceback (most recent call last):
...
ValueError: Range value must consist of two elements, got 3.
>>> e.weight_range = 1, 'wrong type'
>>> e.save()
Traceback (most recent call last):
...
TypeError: Range value must consist of two numbers, got "1" and "wrong type" instead.
>>> e.weight_range = 3, 1
>>> e.save()
Traceback (most recent call last):
...
ValueError: Range must consist of min and max values (min <= max) but got "3" and "1" instead.

# okay, now set a correct value

>>> e.weight_range = 1, 3
>>> e.save()
>>> Attr.objects.all().order_by('schema', 'choice__id')
[<Attr: Apple: Colour "yellow">, <Attr: Apple: Taste "sweet">, <Attr: Apple: Weight range "(1.0, 3.0)">]

# check if queries work

>>> Entity.objects.filter(weight_range__overlaps=(1, 4))
[<Entity: Apple>]
>>> Entity.objects.filter(weight_range__overlaps=(0, None))
[<Entity: Apple>]
>>> Entity.objects.filter(weight_range__overlaps=(None, 5))
[<Entity: Apple>]
>>> Entity.objects.filter(weight_range__overlaps=(0, 0))
[]
>>> Entity.objects.filter(weight_range__overlaps=(None, 0))
[]
>>> Entity.objects.filter(weight_range__overlaps=(4, None))
[]
>>> Entity.objects.filter(weight_range__overlaps=(-5, 0))
[]
>>> Entity.objects.filter(weight_range__overlaps=(-5, 1))
[<Entity: Apple>]

## ## many-to-one ##

>>> size = Schema.objects.create(name='size', title='Size', datatype=Schema.TYPE_MANY)
>>> small  = size.choices.create(title='S')
>>> medium = size.choices.create(title='M')
>>> large  = size.choices.create(title='L')
>>> small
<Choice: S>
>>> large.schema
<Schema: Size (multiple choices)>
>>> e = Entity(title='T-shirt')
>>> e.size = small
>>> e.save()
>>> e2 = Entity.objects.get(pk=e.pk)
>>> e2.size
[<Choice: S>]
>>> e2.size = [medium, large]
>>> e2.save()
>>> e3 = Entity.objects.get(pk=e.pk)
>>> e3.size
[<Choice: M>, <Choice: L>]
>>> Attr.objects.all().order_by('schema', 'choice__id')
[<Attr: Apple: Colour "yellow">, <Attr: T-shirt: Size "M">, <Attr: T-shirt: Size "L">, <Attr: Apple: Taste "sweet">, <Attr: Apple: Weight range "(1.0, 3.0)">]
>>> e2.size = ['wrong choice']
>>> e2.save()
Traceback (most recent call last):
    ...
TypeError: Cannot assign "['wrong choice']": "Attr.choice" must be a BaseChoice instance.
>>> e2.size = [small, large]
>>> e2.save()
>>> e3 = Entity.objects.get(pk=e.pk)
>>> e3.size
[<Choice: S>, <Choice: L>]
>>> Attr.objects.all().order_by('schema', 'choice__id')
[<Attr: Apple: Colour "yellow">, <Attr: T-shirt: Size "S">, <Attr: T-shirt: Size "L">, <Attr: Apple: Taste "sweet">, <Attr: Apple: Weight range "(1.0, 3.0)">]

## ## one-to-one ##

>>> protein = Schema.objects.create(name='protein', title='Protein', datatype=Schema.TYPE_ONE)
>>> egg_albumen = protein.choices.create(title='Egg Albumen')
>>> gluten = protein.choices.create(title='Gluten')
>>> lean_meat = protein.choices.create(title='Lean Meat')
>>> egg_albumen
<Choice: Egg Albumen>
>>> gluten.schema
<Schema: Protein (choice)>
>>> e = Entity(title='Cane')
>>> e.protein = egg_albumen
>>> e.save()
>>> e2 = Entity.objects.get(pk=e.pk)
>>> e2.protein
<Choice: Egg Albumen>
>>> e2.protein = [gluten, lean_meat]
>>> e2.save()
Traceback (most recent call last):
    ...
TypeError: Cannot assign multiple values "[<Choice: Gluten>, <Choice: Lean Meat>]" to TYPE_ONE must be only one BaseChoice instance.
>>> e3 = Entity.objects.get(pk=e.pk)
>>> e3.protein
<Choice: Egg Albumen>
>>> e2.protein = ['wrong choice']
>>> e2.save()
Traceback (most recent call last):
    ...
TypeError: Cannot assign "['wrong choice']": "Attr.choice" must be a BaseChoice instance.

## ## combined ##

>>> Entity.objects.create(title='Orange', colour='orange', taste='sweet', size=medium)
<Entity: Orange>
>>> Entity.objects.create(title='Tangerine', colour='orange', taste='sweet', size=small)
<Entity: Tangerine>
>>> Entity.objects.create(title='Old Dog', colour='orange', taste='bitter', size=large)
<Entity: Old Dog>
>>> Entity.objects.filter(taste='sweet')
[<Entity: Apple>, <Entity: Orange>, <Entity: Tangerine>]
>>> Entity.objects.filter(colour='orange', size__in=[small, large])
[<Entity: Tangerine>, <Entity: Old Dog>]

# # exclude() fetches objects that either have given attribute(s) with other values # or don’t have any attributes for this schema at all: #

>>> Entity.objects.exclude(size=small)
[<Entity: Apple>, <Entity: Cane>, <Entity: Orange>, <Entity: Old Dog>]
>>> Entity.objects.exclude(taste='sweet')
[<Entity: T-shirt>, <Entity: Cane>, <Entity: Old Dog>]
>>> Entity.objects.filter(size=large) & Entity.objects.exclude(colour='orange')
[<Entity: T-shirt>]
>>> Entity.objects.filter(size=large) & Entity.objects.filter(colour='orange')
[<Entity: Old Dog>]

## ## facets ##

# make some schemata available for filtering entities by them

>>> Schema.objects.filter(name__in=['colour', 'size', 'taste']).update(filtered=True)
3
>>> fs = FacetSet({})
>>> fs.filterable_schemata
[<Schema: Colour (text)>, <Schema: Size (multiple choices)>, <Schema: Taste (text)>]
>>> fs.filterable_names
['price', 'colour', 'size', 'taste']
>>> fs.facets
[<TextFacet: Item price>, <TextFacet: Colour>, <ManyToManyFacet: Size>, <TextFacet: Taste>]
>>> [x for x in fs]
[<Entity: Apple>, <Entity: T-shirt>, <Entity: Cane>, <Entity: Orange>, <Entity: Tangerine>, <Entity: Old Dog>]
>>> [x for x in FacetSet({'colour': 'yellow'})]
[<Entity: Apple>]
>>> [x for x in FacetSet({'colour': 'orange'})]
[<Entity: Orange>, <Entity: Tangerine>, <Entity: Old Dog>]
>>> [x for x in FacetSet({'colour': 'orange', 'taste': 'sweet'})]
[<Entity: Orange>, <Entity: Tangerine>]
>>> [x for x in FacetSet({'size': [large.pk]})]
[<Entity: T-shirt>, <Entity: Old Dog>]

Entities used in the tests

class eav.tests.Attr(*args, **kwargs)

Attr(id, entity_type_id, entity_id, value_text, value_float, value_date, value_bool, value_range_min, value_range_max, schema_id, choice_id)

class eav.tests.Choice(*args, **kwargs)

Choice(id, title, schema_id)

class eav.tests.Entity(*args, **kwargs)

Entity(id, title, price)

class eav.tests.Schema(*args, **kwargs)

Schema(id, title, name, help_text, datatype, required, searched, filtered, sortable)

Widgets

class eav.widgets.RangeWidget(attrs=None)

Represents a range of numbers.