Move remaining docstrings to Google-stype format (#10)

This commit is contained in:
Iwo Herka 2018-07-27 13:13:42 +00:00
parent e0a76f86b2
commit 99254cd578
11 changed files with 232 additions and 251 deletions

View file

@ -2,4 +2,3 @@
omit =
*/migrations/*
eav/__init__.py
eav/utils.py

View file

@ -1,4 +1,4 @@
'''This module contains classes used for admin integration.'''
"""This module contains classes used for admin integration."""
from django.contrib import admin
from django.contrib.admin.options import InlineModelAdmin, ModelAdmin
@ -11,12 +11,12 @@ from .models import Attribute, EnumGroup, EnumValue, Value
class BaseEntityAdmin(ModelAdmin):
def render_change_form(self, request, context, *args, **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.
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.
"""
form = context['adminform'].form
@ -33,9 +33,9 @@ class BaseEntityAdmin(ModelAdmin):
class BaseEntityInlineFormSet(BaseInlineFormSet):
'''
"""
An inline formset that correctly initializes EAV forms.
'''
"""
def add_fields(self, form, index):
if self.instance:
setattr(form.instance, self.fk.name, self.instance)
@ -45,20 +45,20 @@ class BaseEntityInlineFormSet(BaseInlineFormSet):
class BaseEntityInline(InlineModelAdmin):
'''
"""
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.::
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).
'''
.. 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 = BaseEntityInlineFormSet
def get_fieldsets(self, request, obj=None):
@ -80,6 +80,6 @@ class AttributeAdmin(ModelAdmin):
admin.site.register(Attribute, AttributeAdmin)
admin.site.register(Value)
admin.site.register(EnumValue)
admin.site.register(EnumGroup)
admin.site.register(Value)

View file

@ -1,17 +1,17 @@
'''
"""
This module contains pure wrapper functions used as decorators.
Functions in this module should be simple and not involve complex logic.
'''
"""
def register_eav(**kwargs):
'''
"""
Registers the given model(s) classes and wrapped ``Model`` class with
Django EAV 2::
@register_eav
class Author(models.Model):
pass
'''
"""
from . import register
from django.db.models import Model

View file

@ -6,54 +6,58 @@ from django.utils.translation import ugettext_lazy as _
class EavSlugField(models.SlugField):
'''
"""
The slug field used by :class:`~eav.models.Attribute`
'''
"""
def validate(self, value, instance):
'''
"""
Slugs are used to convert the Python attribute name to a database
lookup and vice versa. We need it to be a valid Python identifier.
We don't want it to start with a '_', underscore will be used
var variables we don't want to be saved in the database.
'''
lookup and vice versa. We need it to be a valid Python identifier. We
don't want it to start with a '_', underscore will be used in
variables we don't want to be saved in the database.
"""
super(EavSlugField, self).validate(value, instance)
slug_regex = r'[a-z][a-z0-9_]*'
if not re.match(slug_regex, value):
raise ValidationError(_(u"Must be all lower case, " \
u"start with a letter, and contain " \
u"only letters, numbers, or underscores."))
raise ValidationError(_(
'Must be all lower case, start with a letter, and contain '
'only letters, numbers, or underscores.'
))
@staticmethod
def create_slug_from_name(name):
'''
Creates a slug based on the name
'''
"""Creates a slug based on the name."""
name = name.strip().lower()
# Change spaces to underscores
# Change spaces to underscores.
name = '_'.join(name.split())
# Remove non alphanumeric characters
# Remove non alphanumeric characters.
return re.sub('[^\w]', '', name)
class EavDatatypeField(models.CharField):
'''
The datatype field used by :class:`~eav.models.Attribute`
'''
"""
The datatype field used by :class:`~eav.models.Attribute`.
"""
def validate(self, value, instance):
'''
"""
Raise ``ValidationError`` if they try to change the datatype of an
:class:`~eav.models.Attribute` that is already used by
:class:`~eav.models.Value` objects.
'''
"""
super(EavDatatypeField, self).validate(value, instance)
if not instance.pk:
return
if type(instance).objects.get(pk=instance.pk).datatype == instance.datatype:
return
if instance.value_set.count():
raise ValidationError(_(u"You cannot change the datatype of an "
u"attribute that is already in use."))
raise ValidationError(_(
'You cannot change the datatype of an attribute that is already in use.'
))

View file

@ -1,4 +1,4 @@
'''This module contains forms used for admin integration.'''
"""This module contains forms used for admin integration."""
from copy import deepcopy
@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
class BaseDynamicEntityForm(ModelForm):
'''
"""
``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),
@ -29,7 +29,7 @@ class BaseDynamicEntityForm(ModelForm):
bool BooleanField
enum ChoiceField
===== =============
'''
"""
FIELD_CLASSES = {
'text': CharField,
'float': FloatField,
@ -46,7 +46,7 @@ class BaseDynamicEntityForm(ModelForm):
self._build_dynamic_fields()
def _build_dynamic_fields(self):
# reset form fields
# Reset form fields.
self.fields = deepcopy(self.base_fields)
for attribute in self.entity.get_all_attributes():
@ -60,13 +60,12 @@ class BaseDynamicEntityForm(ModelForm):
}
datatype = attribute.datatype
if datatype == attribute.TYPE_ENUM:
values = attribute.get_choices() \
.values_list('id', 'value')
values = attribute.get_choices().values_list('id', 'value')
choices = [('', '-----')] + list(values)
defaults.update({'choices': choices})
if value:
defaults.update({'initial': value.pk})
@ -78,21 +77,20 @@ class BaseDynamicEntityForm(ModelForm):
MappedField = self.FIELD_CLASSES[datatype]
self.fields[attribute.slug] = MappedField(**defaults)
# fill initial data (if attribute was already defined)
if value and not datatype == attribute.TYPE_ENUM: #enum done above
# Fill initial data (if attribute was already defined).
if value and not datatype == attribute.TYPE_ENUM:
self.initial[attribute.slug] = value
def save(self, commit=True):
"""
Saves this ``form``'s cleaned_data into model instance
``self.instance`` and related EAV attributes.
Returns ``instance``.
``self.instance`` and related EAV attributes. Returns ``instance``.
"""
if self.errors:
raise ValueError(_(u"The %s could not be saved because the data"
u"didn't validate.") % \
self.instance._meta.object_name)
raise ValueError(_(
'The %s could not be saved because the data'
'didn\'t validate.' % self.instance._meta.object_name
))
# Create entity instance, don't save yet.
instance = super(BaseDynamicEntityForm, self).save(commit=False)
@ -100,6 +98,7 @@ class BaseDynamicEntityForm(ModelForm):
# Assign attributes.
for attribute in self.entity.get_all_attributes():
value = self.cleaned_data.get(attribute.slug)
if attribute.datatype == attribute.TYPE_ENUM:
if value:
value = attribute.enum_group.values.get(pk=value)

View file

@ -1,34 +1,32 @@
# -*- coding: utf-8 -*-
'''
"""
This module contains the custom manager used by entities registered with eav.
'''
"""
from django.db import models
from .queryset import EavQuerySet
class EntityManager(models.Manager):
'''
"""
Our custom manager, overrides ``models.Manager``.
'''
"""
_queryset_class = EavQuerySet
def create(self, **kwargs):
'''
"""
Parse eav attributes out of *kwargs*, then try to create and save
the object, then assign and save it's eav attributes.
'''
"""
config_cls = getattr(self.model, '_eav_config_cls', None)
if not config_cls or config_cls.manager_only:
return super(EntityManager, self).create(**kwargs)
#attributes = config_cls.get_attributes()
prefix = '%s__' % config_cls.eav_attr
new_kwargs = {}
eav_kwargs = {}
for key, value in kwargs.items():
if key.startswith(prefix):
eav_kwargs.update({key[len(prefix):]: value})
@ -37,15 +35,17 @@ class EntityManager(models.Manager):
obj = self.model(**new_kwargs)
obj_eav = getattr(obj, config_cls.eav_attr)
for key, value in eav_kwargs.items():
setattr(obj_eav, key, value)
obj.save()
return obj
def get_or_create(self, **kwargs):
'''
"""
Reproduces the behavior of get_or_create, eav friendly.
'''
"""
try:
return self.get(**kwargs), False
except self.model.DoesNotExist:

View file

@ -1,4 +1,4 @@
'''
"""
This module defines the four concrete, non-abstract models:
* :class:`Value`
* :class:`Attribute`
@ -6,7 +6,8 @@ This module defines the four concrete, non-abstract models:
* :class:`EnumGroup`
Along with the :class:`Entity` helper class.
'''
"""
from copy import copy
from django.contrib.contenttypes import fields as generic
@ -22,11 +23,11 @@ from .validators import *
class EnumValue(models.Model):
'''
*EnumValue* objects are the value 'choices' to multiple choice
*TYPE_ENUM* :class:`Attribute` objects.
"""
*EnumValue* objects are the value 'choices' to multiple choice *TYPE_ENUM*
:class:`Attribute` objects. They have only one field, *value*, a
``CharField`` that must be unique.
They have only one field, *value*, a ``CharField`` that must be unique.
For example::
yes = EnumValue.objects.create(value='Yes') # doctest: SKIP
@ -36,17 +37,17 @@ class EnumValue(models.Model):
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.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*.
'''
*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*.
"""
value = models.CharField(_('value'), db_index=True, unique=True, max_length=50)
def __str__(self):
@ -54,13 +55,13 @@ class EnumValue(models.Model):
class EnumGroup(models.Model):
'''
"""
*EnumGroup* objects have two fields - a *name* ``CharField`` and *values*,
a ``ManyToManyField`` to :class:`EnumValue`. :class:`Attribute` classes
with datatype *TYPE_ENUM* have a ``ForeignKey`` field to *EnumGroup*.
See :class:`EnumValue` for an example.
'''
"""
name = models.CharField(_('name'), unique = True, max_length = 100)
values = models.ManyToManyField(EnumValue, verbose_name = _('enum group'))
@ -69,10 +70,10 @@ class EnumGroup(models.Model):
class Attribute(models.Model):
'''
"""
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...
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
@ -115,7 +116,7 @@ class Attribute(models.Model):
.. warning:: Once an Attribute has been used by an entity, you can not
change it's datatype.
'''
"""
class Meta:
ordering = ['name']
@ -151,11 +152,11 @@ class Attribute(models.Model):
help_text = _('User-friendly attribute name')
)
'''
"""
Main identifer for the attribute.
Upon creation, slug is autogenerated from the name.
(see :meth:`~eav.fields.EavSlugField.create_slug_from_name`).
'''
"""
slug = EavSlugField(
verbose_name = _('Slug'),
max_length = 50,
@ -164,12 +165,12 @@ class Attribute(models.Model):
help_text = _('Short unique attribute label')
)
'''
"""
.. 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.
'''
"""
required = models.BooleanField(verbose_name = _('Required'), default = False)
enum_group = models.ForeignKey(
@ -211,7 +212,7 @@ class Attribute(models.Model):
return self.description
def get_validators(self):
'''
"""
Returns the appropriate validator function from :mod:`~eav.validators`
as a list (of length one) for the datatype.
@ -219,7 +220,7 @@ class Attribute(models.Model):
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.
'''
"""
DATATYPE_VALIDATORS = {
'text': validate_text,
'float': validate_float,
@ -233,10 +234,10 @@ class Attribute(models.Model):
return [DATATYPE_VALIDATORS[self.datatype]]
def validate_value(self, value):
'''
"""
Check *value* against the validators returned by
:meth:`get_validators` for this attribute.
'''
"""
for validator in self.get_validators():
validator(value)
@ -248,10 +249,10 @@ class Attribute(models.Model):
)
def save(self, *args, **kwargs):
'''
"""
Saves the Attribute and auto-generates a slug field
if one wasn't provided.
'''
"""
if not self.slug:
self.slug = EavSlugField.create_slug_from_name(self.name)
@ -259,11 +260,11 @@ class Attribute(models.Model):
super(Attribute, self).save(*args, **kwargs)
def clean(self):
'''
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.
'''
"""
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.
"""
if self.datatype == self.TYPE_ENUM and not self.enum_group:
raise ValidationError(
_('You must set the choice group for multiple choice attributes')
@ -275,14 +276,14 @@ class Attribute(models.Model):
)
def get_choices(self):
'''
"""
Returns a query set of :class:`EnumValue` objects for this attribute.
Returns None if the datatype of this attribute is not *TYPE_ENUM*.
'''
"""
return self.enum_group.values.all() if self.datatype == Attribute.TYPE_ENUM else None
def save_value(self, entity, value):
'''
"""
Called with *entity*, any Django object registered with eav, and
*value*, the :class:`Value` this attribute for *entity* should
be set to.
@ -293,7 +294,7 @@ class Attribute(models.Model):
.. note::
If *value* is None and a :class:`Value` object exists for this
Attribute and *entity*, it will delete that :class:`Value` object.
'''
"""
ct = ContentType.objects.get_for_model(entity)
try:
@ -325,7 +326,7 @@ class Attribute(models.Model):
class Value(models.Model):
'''
"""
Putting the **V** in *EAV*. This model stores the value for one particular
:class:`Attribute` for some entity.
@ -344,7 +345,7 @@ class Value(models.Model):
Value.objects.create(entity = u, attribute = a, value_text = 'red bull')
# = <Value: crazy_dev_user - Fav Drink: "red bull">
'''
"""
entity_ct = models.ForeignKey(
ContentType,
@ -395,17 +396,17 @@ class Value(models.Model):
)
def save(self, *args, **kwargs):
'''
"""
Validate and save this value.
'''
"""
self.full_clean()
super(Value, self).save(*args, **kwargs)
def clean(self):
'''
"""
Raises ``ValidationError`` if this value's attribute is *TYPE_ENUM*
and value_enum is not a valid choice for this value's attribute.
'''
"""
if self.attribute.datatype == Attribute.TYPE_ENUM and self.value_enum:
if self.value_enum not in self.attribute.enum_group.values.all():
raise ValidationError(
@ -414,15 +415,15 @@ class Value(models.Model):
)
def _get_value(self):
'''
"""
Return the python object this value is holding
'''
"""
return getattr(self, 'value_%s' % self.attribute.datatype)
def _set_value(self, new_value):
'''
"""
Set the object this value is holding
'''
"""
setattr(self, 'value_%s' % self.attribute.datatype, new_value)
value = property(_get_value, _set_value)
@ -435,41 +436,41 @@ class Value(models.Model):
class Entity(object):
'''
"""
The helper class that will be attached to any entity
registered with eav.
'''
"""
@staticmethod
def pre_save_handler(sender, *args, **kwargs):
'''
"""
Pre save handler attached to self.instance. Called before the
model instance we are attached to is saved. This allows us to call
:meth:`validate_attributes` before the entity is saved.
'''
"""
instance = kwargs['instance']
entity = getattr(kwargs['instance'], instance._eav_config_cls.eav_attr)
entity.validate_attributes()
@staticmethod
def post_save_handler(sender, *args, **kwargs):
'''
"""
Post save handler attached to self.instance. Calls :meth:`save` when
the model instance we are attached to is saved.
'''
"""
instance = kwargs['instance']
entity = getattr(instance, instance._eav_config_cls.eav_attr)
entity.save()
def __init__(self, instance):
'''
"""
Set self.instance equal to the instance of the model that we're attached
to. Also, store the content type of that instance.
'''
"""
self.instance = instance
self.ct = ContentType.objects.get_for_model(instance)
def __getattr__(self, name):
'''
"""
Tha magic getattr helper. This is called whenever user invokes::
instance.<attribute>
@ -479,7 +480,7 @@ class Entity(object):
attribute slug. If there is one, it returns the value of the
class:`Value` object, otherwise it hasn't been set, so it returns
None.
'''
"""
if not name.startswith('_'):
try:
attribute = self.get_attribute_by_slug(name)
@ -497,43 +498,43 @@ class Entity(object):
return getattr(super(Entity, self), name)
def get_all_attributes(self):
'''
"""
Return a query set of all :class:`Attribute` objects that can be set
for this entity.
'''
"""
return self.instance._eav_config_cls.get_attributes().order_by('display_order')
def _hasattr(self, attribute_slug):
'''
"""
Since we override __getattr__ with a backdown to the database, this
exists as a way of checking whether a user has set a real attribute on
ourselves, without going to the db if not.
'''
"""
return attribute_slug in self.__dict__
def _getattr(self, attribute_slug):
'''
"""
Since we override __getattr__ with a backdown to the database, this
exists as a way of getting the value a user set for one of our
attributes, without going to the db to check.
'''
"""
return self.__dict__[attribute_slug]
def save(self):
'''
"""
Saves all the EAV values that have been set on this entity.
'''
"""
for attribute in self.get_all_attributes():
if self._hasattr(attribute.slug):
attribute_value = self._getattr(attribute.slug)
attribute.save_value(self.instance, attribute_value)
def validate_attributes(self):
'''
"""
Called before :meth:`save`, first validate all the entity values to
make sure they can be created / saved cleanly.
Raises ``ValidationError`` if they can't be.
'''
"""
values_dict = self.get_values_dict()
for attribute in self.get_all_attributes():
@ -574,43 +575,43 @@ class Entity(object):
return {v.attribute.slug: v.value for v in self.get_values()}
def get_values(self):
'''
"""
Get all set :class:`Value` objects for self.instance
'''
"""
return Value.objects.filter(
entity_ct = self.ct,
entity_id = self.instance.pk
).select_related()
def get_all_attribute_slugs(self):
'''
"""
Returns a list of slugs for all attributes available to this entity.
'''
"""
return set(self.get_all_attributes().values_list('slug', flat=True))
def get_attribute_by_slug(self, slug):
'''
"""
Returns a single :class:`Attribute` with *slug*.
'''
"""
return self.get_all_attributes().get(slug=slug)
def get_value_by_attribute(self, attribute):
'''
"""
Returns a single :class:`Value` for *attribute*.
'''
"""
return self.get_values().get(attribute=attribute)
def get_object_attributes(self):
'''
"""
Returns entity instance attributes, except for
``instance`` and ``ct`` which are used internally.
'''
"""
return set(copy(self.__dict__).keys()) - set(['instance', 'ct'])
def __iter__(self):
'''
"""
Iterate over set eav values. This would allow you to do::
for i in m.eav: print(i)
'''
"""
return iter(self.get_values())

View file

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
'''
"""
This module contains custom :class:`EavQuerySet` class used for overriding
relational operators and pure functions for rewriting Q-expressions.
Q-expressions need to be rewritten for two reasons:
@ -19,7 +18,7 @@ Q-expressions need to be rewritten for two reasons:
2. To ensure that Q-expression tree is compiled to valid SQL.
For details see: :func:`rewrite_q_expr`.
'''
"""
from functools import wraps
@ -31,7 +30,7 @@ from .models import Attribute, Value
def is_eav_and_leaf(expr, gr_name):
'''
"""
Checks whether Q-expression is an EAV AND leaf.
Args:
@ -40,14 +39,16 @@ def is_eav_and_leaf(expr, gr_name):
Returns:
bool
'''
return (getattr(expr, 'connector', None) == 'AND' and
len(expr.children) == 1 and
expr.children[0][0] in ['pk__in', '{}__in'.format(gr_name)])
"""
return (
getattr(expr, 'connector', None) == 'AND' and
len(expr.children) == 1 and
expr.children[0][0] in ['pk__in', '{}__in'.format(gr_name)]
)
def rewrite_q_expr(model_cls, expr):
'''
"""
Rewrites Q-expression to safe form, in order to ensure that
generated SQL is valid.
@ -90,7 +91,7 @@ IGNORE
Returns:
Union[Q, tuple]
'''
"""
# Node in a Q-expr can be a Q or an attribute-value tuple (leaf).
# We are only interested in Qs.
@ -153,11 +154,11 @@ IGNORE
def eav_filter(func):
'''
"""
Decorator used to wrap filter and exclude methods. Passes args through
:func:`expand_q_filters` and kwargs through :func:`expand_eav_filter`. Returns the
called function (filter or exclude).
'''
"""
@wraps(func)
def wrapper(self, *args, **kwargs):
nargs = []
@ -187,11 +188,11 @@ def eav_filter(func):
def expand_q_filters(q, root_cls):
'''
"""
Takes a Q object and a model class.
Recursively passes each filter / value in the Q object tree leaf nodes
through :func:`expand_eav_filter`.
'''
"""
new_children = []
for qi in q.children:
@ -209,7 +210,7 @@ def expand_q_filters(q, root_cls):
def expand_eav_filter(model_cls, key, value):
'''
"""
Accepts a model class and a key, value.
Recurisively replaces any eav filter with a subquery.
@ -222,7 +223,7 @@ def expand_eav_filter(model_cls, key, value):
key = 'eav_values__in'
value = Values.objects.filter(value_int=5, attribute__slug='height')
'''
"""
fields = key.split('__')
config_cls = getattr(model_cls, '_eav_config_cls', None)
@ -254,30 +255,30 @@ def expand_eav_filter(model_cls, key, value):
class EavQuerySet(QuerySet):
'''
"""
Overrides relational operators for EAV models.
'''
"""
@eav_filter
def filter(self, *args, **kwargs):
'''
"""
Pass *args* and *kwargs* through :func:`eav_filter`, then pass to
the ``Manager`` filter method.
'''
"""
return super(EavQuerySet, self).filter(*args, **kwargs)
@eav_filter
def exclude(self, *args, **kwargs):
'''
"""
Pass *args* and *kwargs* through :func:`eav_filter`, then pass to
the ``Manager`` exclude method.
'''
"""
return super(EavQuerySet, self).exclude(*args, **kwargs)
@eav_filter
def get(self, *args, **kwargs):
'''
"""
Pass *args* and *kwargs* through :func:`eav_filter`, then pass to
the ``Manager`` get method.
'''
"""
return super(EavQuerySet, self).get(*args, **kwargs)

View file

@ -1,4 +1,4 @@
'''This modules contains the registry classes.'''
"""This modules contains the registry classes."""
from django.contrib.contenttypes import fields as generic
from django.db.models.signals import post_init, post_save, pre_save
@ -8,7 +8,7 @@ from .models import Attribute, Entity, Value
class EavConfig(object):
'''
"""
The default ``EavConfig`` class used if it is not overriden on registration.
This is where all the default eav attribute names are defined.
@ -25,7 +25,7 @@ class EavConfig(object):
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.
'''
"""
manager_attr = 'objects'
manager_only = False
eav_attr = 'eav'
@ -34,28 +34,28 @@ class EavConfig(object):
@classmethod
def get_attributes(cls):
'''
"""
By default, all :class:`~eav.models.Attribute` object apply to an
entity, unless you provide a custom EavConfig class overriding this.
'''
"""
return Attribute.objects.all()
class Registry(object):
'''
"""
Handles registration through the
:meth:`register` and :meth:`unregister` methods.
'''
"""
@staticmethod
def register(model_cls, config_cls=None):
'''
"""
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.
'''
"""
if hasattr(model_cls, '_eav_config_cls'):
return
@ -71,12 +71,12 @@ class Registry(object):
@staticmethod
def unregister(model_cls):
'''
"""
Unregisters *model_cls* with eav.
.. note::
Unregistering a class not already registered is harmlessly ignored.
'''
"""
if not getattr(model_cls, '_eav_config_cls', None):
return
reg = Registry(model_cls)
@ -86,24 +86,24 @@ class Registry(object):
@staticmethod
def attach_eav_attr(sender, *args, **kwargs):
'''
"""
Attach EAV Entity toolkit to an instance after init.
'''
"""
instance = kwargs['instance']
config_cls = instance.__class__._eav_config_cls
setattr(instance, config_cls.eav_attr, Entity(instance))
def __init__(self, model_cls):
'''
"""
Set the *model_cls* and its *config_cls*
'''
"""
self.model_cls = model_cls
self.config_cls = model_cls._eav_config_cls
def _attach_manager(self):
'''
"""
Attach the manager to *manager_attr* specified in *config_cls*
'''
"""
# Save the old manager if the attribute name conflicts with the new one.
if hasattr(self.model_cls, self.config_cls.manager_attr):
mgr = getattr(self.model_cls, self.config_cls.manager_attr)
@ -116,9 +116,9 @@ class Registry(object):
mgr.contribute_to_class(self.model_cls, self.config_cls.manager_attr)
def _detach_manager(self):
'''
"""
Detach the manager and restore the previous one (if there was one).
'''
"""
mgr = getattr(self.model_cls, self.config_cls.manager_attr)
self.model_cls._meta.local_managers.remove(mgr)
self.model_cls._meta._expire_cache()
@ -130,28 +130,28 @@ class Registry(object):
self.config_cls.manager_attr)
def _attach_signals(self):
'''
"""
Attach pre- and post- save signals from model class
to Entity helper. This way, Entity instance will be
able to prepare and clean-up before and after creation /
update of the user's model class instance.
'''
"""
post_init.connect(Registry.attach_eav_attr, sender = self.model_cls)
pre_save.connect(Entity.pre_save_handler, sender = self.model_cls)
post_save.connect(Entity.post_save_handler, sender = self.model_cls)
def _detach_signals(self):
'''
"""
Detach all signals for eav.
'''
"""
post_init.disconnect(Registry.attach_eav_attr, sender = self.model_cls)
pre_save.disconnect(Entity.pre_save_handler, sender = self.model_cls)
post_save.disconnect(Entity.post_save_handler, sender = self.model_cls)
def _attach_generic_relation(self):
'''
"""
Set up the generic relation for the entity
'''
"""
rel_name = self.config_cls.generic_relation_related_name or \
self.model_cls.__name__
@ -164,9 +164,9 @@ class Registry(object):
generic_relation.contribute_to_class(self.model_cls, gr_name)
def _detach_generic_relation(self):
'''
"""
Remove the generic relation from the entity
'''
"""
gen_rel_field = self.config_cls.generic_relation_attr.lower()
for field in self.model_cls._meta.local_many_to_many:
if field.name == gen_rel_field:
@ -176,9 +176,9 @@ class Registry(object):
delattr(self.model_cls, gen_rel_field)
def _register_self(self):
'''
"""
Call the necessary registration methods
'''
"""
self._attach_manager()
if not self.config_cls.manager_only:
@ -186,9 +186,9 @@ class Registry(object):
self._attach_generic_relation()
def _unregister_self(self):
'''
"""
Call the necessary unregistration methods
'''
"""
self._detach_manager()
if not self.config_cls.manager_only:

View file

@ -1,24 +0,0 @@
'''This module contains non-essential helper methods.'''
import sys
from django.db.models import Q
def print_q_expr(expr, indent="", is_tail=True):
'''
Simple print method for debugging Q-expressions' trees.
'''
sys.stdout.write(indent)
sa, sb = (' └── ', ' ') if is_tail else (' ├── ', '')
if isinstance(expr, Q):
sys.stdout.write('{}{}\n'.format(sa, expr.connector))
for child in expr.children:
print_q_expr(child, indent + sb, expr.children[-1] == child)
else:
try:
queryset = ', '.join(repr(v) for v in expr[1])
except TypeError:
queryset = repr(expr[1])
sys.stdout.write(' └── {} {}\n'.format(expr[0], queryset))

View file

@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
'''
"""
This module contains a validator for each :class:`~eav.models.Attribute` datatype.
A validator is a callable that takes a value and raises a ``ValidationError``
if it doesnt meet some criteria (see `Django validators
if it doesn't meet some criteria (see `Django validators
<http://docs.djangoproject.com/en/dev/ref/validators/>`_).
These validators are called by the
:meth:`~eav.models.Attribute.validate_value` method in the
:class:`~eav.models.Attribute` model.
'''
"""
import datetime
@ -20,17 +18,17 @@ from django.utils.translation import ugettext_lazy as _
def validate_text(value):
'''
"""
Raises ``ValidationError`` unless *value* type is ``str`` or ``unicode``
'''
"""
if not isinstance(value, str):
raise ValidationError(_(u"Must be str or unicode"))
def validate_float(value):
'''
"""
Raises ``ValidationError`` unless *value* can be cast as a ``float``
'''
"""
try:
float(value)
except ValueError:
@ -38,9 +36,9 @@ def validate_float(value):
def validate_int(value):
'''
"""
Raises ``ValidationError`` unless *value* can be cast as an ``int``
'''
"""
try:
int(value)
except ValueError:
@ -48,40 +46,43 @@ def validate_int(value):
def validate_date(value):
'''
"""
Raises ``ValidationError`` unless *value* is an instance of ``datetime``
or ``date``
'''
"""
if not isinstance(value, datetime.datetime) and not isinstance(value, datetime.date):
raise ValidationError(_(u"Must be a date or datetime"))
def validate_bool(value):
'''
"""
Raises ``ValidationError`` unless *value* type is ``bool``
'''
"""
if not isinstance(value, bool):
raise ValidationError(_(u"Must be a boolean"))
def validate_object(value):
'''
"""
Raises ``ValidationError`` unless *value* is a saved
django model instance.
'''
"""
if not isinstance(value, models.Model):
raise ValidationError(_(u"Must be a django model object instance"))
if not value.pk:
raise ValidationError(_(u"Model has not been saved yet"))
def validate_enum(value):
'''
"""
Raises ``ValidationError`` unless *value* is a saved
:class:`~eav.models.EnumValue` model instance.
'''
"""
from .models import EnumValue
if not isinstance(value, EnumValue):
raise ValidationError(_(u"Must be an EnumValue model object instance"))
if not value.pk:
raise ValidationError(_(u"EnumValue has not been saved yet"))