django-eav2/docs/source/usage.rst

314 lines
9.8 KiB
ReStructuredText

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 :func:`~eav.register` method:
.. code-block:: python
import eav
eav.register(Parts)
or with decorators:
.. code-block:: python
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 :class:`~eav.managers.EntityManager` to your class. By default,
it replaces standard manager (*objects*). You can configure under which
attribute it is accessible with :class:`~.eav.registry.EavConfig` (see below).
2. Binds your model's *post_init* signal with
:meth:`~eav.registry.Registry.attach_eav_attr` method. It is used to
attach :class:`~eav.models.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:
.. code-block:: python
part.eav.weight = 0.56
part.save()
3. Binds your model's *pre_save* and *post_save* signals to
:meth:`~eav.models.Entity.pre_save_handler` and
:meth:`~eav.models.Entity.post_save_hander`, respectively.
Those methods are responsible for validation and storage
of attribute values.
4. Setups up generic relation to :class:`~eav.models.Value` set.
By default, it's accessed under *eav_values*:
.. code-block:: python
patient.eav_values.all()
# = <QuerySet [has fever?: "True" (1), temperature: 37.7 (2)]>
5. Sets *_eav_config_cls* attribute storing model of the config class
used by :class:`~eav.registry.Registry`. Defaults
to :class:`~eav.registry.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: :class:`~eav.registry.EavConfig` passed to
:meth:`~eav.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:
.. code-block:: python
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, :class:`~eav.registry.EavConfig` provides *classmethod*
:meth:`~eav.registry.EavConfig.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:
.. code-block:: python
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
:class:`~eav.exceptions.IllegalAssignmentException`. For example (extending
previous one):
.. code-block:: python
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
:class:`~eav.models.Entity` helper:
.. code-block:: python
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 :class:`~eav.models.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``:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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.
.. code-block:: python
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.