mirror of
https://github.com/jazzband/django-eav2.git
synced 2026-03-16 22:40:26 +00:00
style: run black formatter
This commit is contained in:
parent
e35258dc31
commit
b1badf8dc5
16 changed files with 445 additions and 268 deletions
|
|
@ -18,16 +18,16 @@ sys.path.insert(0, os.path.abspath('../../'))
|
|||
|
||||
# Pass settings into configure.
|
||||
settings.configure(
|
||||
INSTALLED_APPS = [
|
||||
INSTALLED_APPS=[
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'eav'
|
||||
'eav',
|
||||
],
|
||||
SECRET_KEY=os.environ.get("DJANGO_SECRET_KEY", "this-is-not-s3cur3")
|
||||
SECRET_KEY=os.environ.get("DJANGO_SECRET_KEY", "this-is-not-s3cur3"),
|
||||
)
|
||||
|
||||
# Call django.setup to load installed apps and other stuff.
|
||||
|
|
@ -56,10 +56,7 @@ extensions = [
|
|||
]
|
||||
|
||||
html_theme_options = dict(
|
||||
show_powered_by = False,
|
||||
show_related = True,
|
||||
fixed_sidebar = True,
|
||||
font_family = 'Roboto'
|
||||
show_powered_by=False, show_related=True, fixed_sidebar=True, font_family='Roboto'
|
||||
)
|
||||
|
||||
templates_path = ['_templates']
|
||||
|
|
@ -111,17 +108,8 @@ def setup(app):
|
|||
|
||||
|
||||
html_sidebars = {
|
||||
'index': [
|
||||
'sidebarintro.html',
|
||||
'localtoc.html'
|
||||
],
|
||||
'**': [
|
||||
'sidebarintro.html',
|
||||
'localtoc.html',
|
||||
|
||||
'relations.html',
|
||||
'searchbox.html'
|
||||
]
|
||||
'index': ['sidebarintro.html', 'localtoc.html'],
|
||||
'**': ['sidebarintro.html', 'localtoc.html', 'relations.html', 'searchbox.html'],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -137,15 +125,12 @@ latex_elements = {
|
|||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
|
|
@ -155,8 +140,7 @@ latex_elements = {
|
|||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'DjangoEAV2.tex', 'Django EAV 2 Documentation',
|
||||
'-', 'manual'),
|
||||
(master_doc, 'DjangoEAV2.tex', 'Django EAV 2 Documentation', '-', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -164,10 +148,7 @@ latex_documents = [
|
|||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'djangoeav2', 'Django EAV 2 Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, 'djangoeav2', 'Django EAV 2 Documentation', [author], 1)]
|
||||
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
|
@ -176,9 +157,15 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'DjangoEAV2', 'Django EAV 2 Documentation',
|
||||
author, 'DjangoEAV2', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
'DjangoEAV2',
|
||||
'Django EAV 2 Documentation',
|
||||
author,
|
||||
'DjangoEAV2',
|
||||
'One line description of project.',
|
||||
'Miscellaneous',
|
||||
),
|
||||
]
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
def register(model_cls, config_cls=None):
|
||||
from .registry import Registry
|
||||
|
||||
Registry.register(model_cls, config_cls)
|
||||
|
||||
|
||||
def unregister(model_cls):
|
||||
from .registry import Registry
|
||||
|
||||
Registry.unregister(model_cls)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ 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)
|
||||
|
|
@ -59,6 +60,7 @@ class BaseEntityInline(InlineModelAdmin):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
class IllegalAssignmentException(Exception):
|
||||
pass
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from .forms import CSVFormField
|
||||
|
||||
|
||||
class EavSlugField(models.SlugField):
|
||||
"""
|
||||
The slug field used by :class:`~eav.models.Attribute`
|
||||
|
|
@ -22,10 +23,12 @@ class EavSlugField(models.SlugField):
|
|||
slug_regex = r'[a-z][a-z0-9_]*'
|
||||
|
||||
if not re.match(slug_regex, value):
|
||||
raise ValidationError(_(
|
||||
'Must be all lower case, start with a letter, and contain '
|
||||
'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):
|
||||
|
|
@ -59,12 +62,14 @@ class EavDatatypeField(models.CharField):
|
|||
return
|
||||
|
||||
if instance.value_set.count():
|
||||
raise ValidationError(_(
|
||||
'You cannot change the datatype of an attribute that is already in use.'
|
||||
))
|
||||
raise ValidationError(
|
||||
_(
|
||||
'You cannot change the datatype of an attribute that is already in use.'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CSVField(models.TextField): # (models.Field):
|
||||
class CSVField(models.TextField): # (models.Field):
|
||||
description = _("A Comma-Separated-Value field.")
|
||||
default_separator = ";"
|
||||
|
||||
|
|
|
|||
36
eav/forms.py
36
eav/forms.py
|
|
@ -4,8 +4,15 @@ from copy import deepcopy
|
|||
|
||||
from django import forms
|
||||
from django.contrib.admin.widgets import AdminSplitDateTime
|
||||
from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField,
|
||||
FloatField, IntegerField, ModelForm)
|
||||
from django.forms import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
ChoiceField,
|
||||
DateTimeField,
|
||||
FloatField,
|
||||
IntegerField,
|
||||
ModelForm,
|
||||
)
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
|
@ -64,15 +71,16 @@ class BaseDynamicEntityForm(ModelForm):
|
|||
csv CSVField
|
||||
===== =============
|
||||
"""
|
||||
|
||||
FIELD_CLASSES = {
|
||||
'text': CharField,
|
||||
'text': CharField,
|
||||
'float': FloatField,
|
||||
'int': IntegerField,
|
||||
'date': DateTimeField,
|
||||
'bool': BooleanField,
|
||||
'enum': ChoiceField,
|
||||
'json': JSONField,
|
||||
'csv': CSVFormField,
|
||||
'int': IntegerField,
|
||||
'date': DateTimeField,
|
||||
'bool': BooleanField,
|
||||
'enum': ChoiceField,
|
||||
'json': JSONField,
|
||||
'csv': CSVFormField,
|
||||
}
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
|
|
@ -123,10 +131,12 @@ class BaseDynamicEntityForm(ModelForm):
|
|||
``self.instance`` and related EAV attributes. Returns ``instance``.
|
||||
"""
|
||||
if self.errors:
|
||||
raise ValueError(_(
|
||||
'The %s could not be saved because the data'
|
||||
'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)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ class EntityManager(models.Manager):
|
|||
"""
|
||||
Our custom manager, overrides ``models.Manager``.
|
||||
"""
|
||||
|
||||
_queryset_class = EavQuerySet
|
||||
|
||||
def create(self, **kwargs):
|
||||
|
|
@ -29,7 +30,7 @@ class EntityManager(models.Manager):
|
|||
|
||||
for key, value in kwargs.items():
|
||||
if key.startswith(prefix):
|
||||
eav_kwargs.update({key[len(prefix):]: value})
|
||||
eav_kwargs.update({key[len(prefix) :]: value})
|
||||
else:
|
||||
new_kwargs.update({key: value})
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,79 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Attribute',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='User-friendly attribute name', max_length=100, verbose_name='Name')),
|
||||
('slug', eav.fields.EavSlugField(help_text='Short unique attribute label', unique=True, verbose_name='Slug')),
|
||||
('description', models.CharField(blank=True, help_text='Short description', max_length=256, null=True, verbose_name='Description')),
|
||||
('datatype', eav.fields.EavDatatypeField(choices=[('text', 'Text'), ('date', 'Date'), ('float', 'Float'), ('int', 'Integer'), ('bool', 'True / False'), ('object', 'Django Object'), ('enum', 'Multiple Choice')], max_length=6, verbose_name='Data Type')),
|
||||
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Created')),
|
||||
('modified', models.DateTimeField(auto_now=True, verbose_name='Modified')),
|
||||
('required', models.BooleanField(default=False, verbose_name='Required')),
|
||||
('display_order', models.PositiveIntegerField(default=1, verbose_name='Display order')),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'name',
|
||||
models.CharField(
|
||||
help_text='User-friendly attribute name',
|
||||
max_length=100,
|
||||
verbose_name='Name',
|
||||
),
|
||||
),
|
||||
(
|
||||
'slug',
|
||||
eav.fields.EavSlugField(
|
||||
help_text='Short unique attribute label',
|
||||
unique=True,
|
||||
verbose_name='Slug',
|
||||
),
|
||||
),
|
||||
(
|
||||
'description',
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text='Short description',
|
||||
max_length=256,
|
||||
null=True,
|
||||
verbose_name='Description',
|
||||
),
|
||||
),
|
||||
(
|
||||
'datatype',
|
||||
eav.fields.EavDatatypeField(
|
||||
choices=[
|
||||
('text', 'Text'),
|
||||
('date', 'Date'),
|
||||
('float', 'Float'),
|
||||
('int', 'Integer'),
|
||||
('bool', 'True / False'),
|
||||
('object', 'Django Object'),
|
||||
('enum', 'Multiple Choice'),
|
||||
],
|
||||
max_length=6,
|
||||
verbose_name='Data Type',
|
||||
),
|
||||
),
|
||||
(
|
||||
'created',
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now,
|
||||
editable=False,
|
||||
verbose_name='Created',
|
||||
),
|
||||
),
|
||||
(
|
||||
'modified',
|
||||
models.DateTimeField(auto_now=True, verbose_name='Modified'),
|
||||
),
|
||||
(
|
||||
'required',
|
||||
models.BooleanField(default=False, verbose_name='Required'),
|
||||
),
|
||||
(
|
||||
'display_order',
|
||||
models.PositiveIntegerField(
|
||||
default=1, verbose_name='Display order'
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
|
|
@ -36,21 +100,53 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='EnumGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True, verbose_name='Name')),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'name',
|
||||
models.CharField(max_length=100, unique=True, verbose_name='Name'),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EnumValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(db_index=True, max_length=50, unique=True, verbose_name='Value')),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
(
|
||||
'value',
|
||||
models.CharField(
|
||||
db_index=True, max_length=50, unique=True, verbose_name='Value'
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Value',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name='ID',
|
||||
),
|
||||
),
|
||||
('entity_id', models.IntegerField()),
|
||||
('value_text', models.TextField(blank=True, null=True)),
|
||||
('value_float', models.FloatField(blank=True, null=True)),
|
||||
|
|
@ -58,12 +154,52 @@ class Migration(migrations.Migration):
|
|||
('value_date', models.DateTimeField(blank=True, null=True)),
|
||||
('value_bool', models.NullBooleanField()),
|
||||
('generic_value_id', models.IntegerField(blank=True, null=True)),
|
||||
('created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Created')),
|
||||
('modified', models.DateTimeField(auto_now=True, verbose_name='Modified')),
|
||||
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='eav.Attribute', verbose_name='Attribute')),
|
||||
('entity_ct', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='value_entities', to='contenttypes.ContentType')),
|
||||
('generic_value_ct', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='value_values', to='contenttypes.ContentType')),
|
||||
('value_enum', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='eav_values', to='eav.EnumValue')),
|
||||
(
|
||||
'created',
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name='Created'
|
||||
),
|
||||
),
|
||||
(
|
||||
'modified',
|
||||
models.DateTimeField(auto_now=True, verbose_name='Modified'),
|
||||
),
|
||||
(
|
||||
'attribute',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to='eav.Attribute',
|
||||
verbose_name='Attribute',
|
||||
),
|
||||
),
|
||||
(
|
||||
'entity_ct',
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='value_entities',
|
||||
to='contenttypes.ContentType',
|
||||
),
|
||||
),
|
||||
(
|
||||
'generic_value_ct',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='value_values',
|
||||
to='contenttypes.ContentType',
|
||||
),
|
||||
),
|
||||
(
|
||||
'value_enum',
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='eav_values',
|
||||
to='eav.EnumValue',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
|
|
@ -74,6 +210,12 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='attribute',
|
||||
name='enum_group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='eav.EnumGroup', verbose_name='Choice Group'),
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to='eav.EnumGroup',
|
||||
verbose_name='Choice Group',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
from django.db import migrations
|
||||
import eav.fields
|
||||
import django.core.serializers.json
|
||||
|
||||
try:
|
||||
from django.db.models import JSONField
|
||||
except ImportError:
|
||||
|
|
@ -19,11 +20,29 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='value',
|
||||
name='value_json',
|
||||
field=JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
|
||||
field=JSONField(
|
||||
blank=True,
|
||||
default=dict,
|
||||
encoder=django.core.serializers.json.DjangoJSONEncoder,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='attribute',
|
||||
name='datatype',
|
||||
field=eav.fields.EavDatatypeField(choices=[('text', 'Text'), ('date', 'Date'), ('float', 'Float'), ('int', 'Integer'), ('bool', 'True / False'), ('object', 'Django Object'), ('enum', 'Multiple Choice'), ('json', 'JSON Object')], max_length=6, verbose_name='Data Type'),
|
||||
field=eav.fields.EavDatatypeField(
|
||||
choices=[
|
||||
('text', 'Text'),
|
||||
('date', 'Date'),
|
||||
('float', 'Float'),
|
||||
('int', 'Integer'),
|
||||
('bool', 'True / False'),
|
||||
('object', 'Django Object'),
|
||||
('enum', 'Multiple Choice'),
|
||||
('json', 'JSON Object'),
|
||||
],
|
||||
max_length=6,
|
||||
verbose_name='Data Type',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,6 +19,20 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='attribute',
|
||||
name='datatype',
|
||||
field=eav.fields.EavDatatypeField(choices=[('text', 'Text'), ('date', 'Date'), ('float', 'Float'), ('int', 'Integer'), ('bool', 'True / False'), ('object', 'Django Object'), ('enum', 'Multiple Choice'), ('json', 'JSON Object'), ('csv', 'Comma-Separated-Value')], max_length=6, verbose_name='Data Type'),
|
||||
field=eav.fields.EavDatatypeField(
|
||||
choices=[
|
||||
('text', 'Text'),
|
||||
('date', 'Date'),
|
||||
('float', 'Float'),
|
||||
('int', 'Integer'),
|
||||
('bool', 'True / False'),
|
||||
('object', 'Django Object'),
|
||||
('enum', 'Multiple Choice'),
|
||||
('json', 'JSON Object'),
|
||||
('csv', 'Comma-Separated-Value'),
|
||||
],
|
||||
max_length=6,
|
||||
verbose_name='Data Type',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
195
eav/models.py
195
eav/models.py
|
|
@ -67,6 +67,7 @@ class EnumValue(models.Model):
|
|||
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):
|
||||
|
|
@ -81,8 +82,9 @@ class EnumGroup(models.Model):
|
|||
|
||||
See :class:`EnumValue` for an example.
|
||||
"""
|
||||
name = models.CharField(_('Name'), unique = True, max_length = 100)
|
||||
values = models.ManyToManyField(EnumValue, verbose_name = _('Enum group'))
|
||||
|
||||
name = models.CharField(_('Name'), unique=True, max_length=100)
|
||||
values = models.ManyToManyField(EnumValue, verbose_name=_('Enum group'))
|
||||
|
||||
def __str__(self):
|
||||
return '<EnumGroup {}>'.format(self.name)
|
||||
|
|
@ -139,43 +141,42 @@ 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']
|
||||
|
||||
TYPE_TEXT = 'text'
|
||||
TYPE_FLOAT = 'float'
|
||||
TYPE_INT = 'int'
|
||||
TYPE_DATE = 'date'
|
||||
TYPE_TEXT = 'text'
|
||||
TYPE_FLOAT = 'float'
|
||||
TYPE_INT = 'int'
|
||||
TYPE_DATE = 'date'
|
||||
TYPE_BOOLEAN = 'bool'
|
||||
TYPE_OBJECT = 'object'
|
||||
TYPE_ENUM = 'enum'
|
||||
TYPE_JSON = 'json'
|
||||
TYPE_CSV = 'csv'
|
||||
TYPE_OBJECT = 'object'
|
||||
TYPE_ENUM = 'enum'
|
||||
TYPE_JSON = 'json'
|
||||
TYPE_CSV = 'csv'
|
||||
|
||||
DATATYPE_CHOICES = (
|
||||
(TYPE_TEXT, _('Text')),
|
||||
(TYPE_DATE, _('Date')),
|
||||
(TYPE_FLOAT, _('Float')),
|
||||
(TYPE_INT, _('Integer')),
|
||||
(TYPE_TEXT, _('Text')),
|
||||
(TYPE_DATE, _('Date')),
|
||||
(TYPE_FLOAT, _('Float')),
|
||||
(TYPE_INT, _('Integer')),
|
||||
(TYPE_BOOLEAN, _('True / False')),
|
||||
(TYPE_OBJECT, _('Django Object')),
|
||||
(TYPE_ENUM, _('Multiple Choice')),
|
||||
(TYPE_JSON, _('JSON Object')),
|
||||
(TYPE_CSV, _('Comma-Separated-Value')),
|
||||
(TYPE_OBJECT, _('Django Object')),
|
||||
(TYPE_ENUM, _('Multiple Choice')),
|
||||
(TYPE_JSON, _('JSON Object')),
|
||||
(TYPE_CSV, _('Comma-Separated-Value')),
|
||||
)
|
||||
|
||||
# Core attributes
|
||||
|
||||
datatype = EavDatatypeField(
|
||||
verbose_name = _('Data Type'),
|
||||
choices = DATATYPE_CHOICES,
|
||||
max_length = 6
|
||||
verbose_name=_('Data Type'), choices=DATATYPE_CHOICES, max_length=6
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
verbose_name = _('Name'),
|
||||
max_length = 100,
|
||||
help_text = _('User-friendly attribute name')
|
||||
verbose_name=_('Name'),
|
||||
max_length=100,
|
||||
help_text=_('User-friendly attribute name'),
|
||||
)
|
||||
|
||||
"""
|
||||
|
|
@ -184,11 +185,11 @@ class Attribute(models.Model):
|
|||
(see :meth:`~eav.fields.EavSlugField.create_slug_from_name`).
|
||||
"""
|
||||
slug = EavSlugField(
|
||||
verbose_name = _('Slug'),
|
||||
max_length = 50,
|
||||
db_index = True,
|
||||
unique = True,
|
||||
help_text = _('Short unique attribute label')
|
||||
verbose_name=_('Slug'),
|
||||
max_length=50,
|
||||
db_index=True,
|
||||
unique=True,
|
||||
help_text=_('Short unique attribute label'),
|
||||
)
|
||||
|
||||
"""
|
||||
|
|
@ -197,7 +198,7 @@ class Attribute(models.Model):
|
|||
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)
|
||||
required = models.BooleanField(verbose_name=_('Required'), default=False)
|
||||
|
||||
entity_ct = models.ManyToManyField(ContentType, blank=True)
|
||||
"""
|
||||
|
|
@ -209,36 +210,30 @@ class Attribute(models.Model):
|
|||
|
||||
enum_group = models.ForeignKey(
|
||||
EnumGroup,
|
||||
verbose_name = _('Choice Group'),
|
||||
on_delete = models.PROTECT,
|
||||
blank = True,
|
||||
null = True
|
||||
verbose_name=_('Choice Group'),
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
description = models.CharField(
|
||||
verbose_name = _('Description'),
|
||||
max_length = 256,
|
||||
blank = True,
|
||||
null = True,
|
||||
help_text = _('Short description')
|
||||
verbose_name=_('Description'),
|
||||
max_length=256,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_('Short description'),
|
||||
)
|
||||
|
||||
# Useful meta-information
|
||||
|
||||
display_order = models.PositiveIntegerField(
|
||||
verbose_name = _('Display order'),
|
||||
default = 1
|
||||
verbose_name=_('Display order'), default=1
|
||||
)
|
||||
|
||||
modified = models.DateTimeField(
|
||||
verbose_name = _('Modified'),
|
||||
auto_now = True
|
||||
)
|
||||
modified = models.DateTimeField(verbose_name=_('Modified'), auto_now=True)
|
||||
|
||||
created = models.DateTimeField(
|
||||
verbose_name = _('Created'),
|
||||
default = timezone.now,
|
||||
editable = False
|
||||
verbose_name=_('Created'), default=timezone.now, editable=False
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
@ -256,15 +251,15 @@ class Attribute(models.Model):
|
|||
validators to return as well as the default, built-in one.
|
||||
"""
|
||||
DATATYPE_VALIDATORS = {
|
||||
'text': validate_text,
|
||||
'float': validate_float,
|
||||
'int': validate_int,
|
||||
'date': validate_date,
|
||||
'bool': validate_bool,
|
||||
'text': validate_text,
|
||||
'float': validate_float,
|
||||
'int': validate_int,
|
||||
'date': validate_date,
|
||||
'bool': validate_bool,
|
||||
'object': validate_object,
|
||||
'enum': validate_enum,
|
||||
'json': validate_json,
|
||||
'csv': validate_csv,
|
||||
'enum': validate_enum,
|
||||
'json': validate_json,
|
||||
'csv': validate_csv,
|
||||
}
|
||||
|
||||
return [DATATYPE_VALIDATORS[self.datatype]]
|
||||
|
|
@ -283,7 +278,7 @@ class Attribute(models.Model):
|
|||
if not self.enum_group.values.filter(value=value).exists():
|
||||
raise ValidationError(
|
||||
_('%(val)s is not a valid choice for %(attr)s')
|
||||
% dict(val = value, attr = self)
|
||||
% dict(val=value, attr=self)
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
|
@ -318,7 +313,11 @@ class Attribute(models.Model):
|
|||
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
|
||||
return (
|
||||
self.enum_group.values.all()
|
||||
if self.datatype == Attribute.TYPE_ENUM
|
||||
else None
|
||||
)
|
||||
|
||||
def save_value(self, entity, value):
|
||||
"""
|
||||
|
|
@ -337,18 +336,14 @@ class Attribute(models.Model):
|
|||
|
||||
try:
|
||||
value_obj = self.value_set.get(
|
||||
entity_ct = ct,
|
||||
entity_id = entity.pk,
|
||||
attribute = self
|
||||
entity_ct=ct, entity_id=entity.pk, attribute=self
|
||||
)
|
||||
except Value.DoesNotExist:
|
||||
if value == None or value == '':
|
||||
return
|
||||
|
||||
value_obj = Value.objects.create(
|
||||
entity_ct = ct,
|
||||
entity_id = entity.pk,
|
||||
attribute = self
|
||||
entity_ct=ct, entity_id=entity.pk, attribute=self
|
||||
)
|
||||
|
||||
if value == None or value == '':
|
||||
|
|
@ -386,53 +381,49 @@ class Value(models.Model):
|
|||
"""
|
||||
|
||||
entity_ct = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete = models.PROTECT,
|
||||
related_name = 'value_entities'
|
||||
ContentType, on_delete=models.PROTECT, related_name='value_entities'
|
||||
)
|
||||
|
||||
entity_id = models.IntegerField()
|
||||
entity = generic.GenericForeignKey(ct_field = 'entity_ct', fk_field = 'entity_id')
|
||||
entity = generic.GenericForeignKey(ct_field='entity_ct', fk_field='entity_id')
|
||||
|
||||
value_text = models.TextField(blank = True, null = True)
|
||||
value_float = models.FloatField(blank = True, null = True)
|
||||
value_int = models.IntegerField(blank = True, null = True)
|
||||
value_date = models.DateTimeField(blank = True, null = True)
|
||||
value_bool = models.BooleanField(blank = True, null = True)
|
||||
value_json = JSONField(default=dict, encoder=DjangoJSONEncoder, blank = True, null = True)
|
||||
value_csv = CSVField(blank = True, null = True)
|
||||
value_text = models.TextField(blank=True, null=True)
|
||||
value_float = models.FloatField(blank=True, null=True)
|
||||
value_int = models.IntegerField(blank=True, null=True)
|
||||
value_date = models.DateTimeField(blank=True, null=True)
|
||||
value_bool = models.BooleanField(blank=True, null=True)
|
||||
value_json = JSONField(
|
||||
default=dict, encoder=DjangoJSONEncoder, blank=True, null=True
|
||||
)
|
||||
value_csv = CSVField(blank=True, null=True)
|
||||
|
||||
value_enum = models.ForeignKey(
|
||||
value_enum = models.ForeignKey(
|
||||
EnumValue,
|
||||
blank = True,
|
||||
null = True,
|
||||
on_delete = models.PROTECT,
|
||||
related_name = 'eav_values'
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='eav_values',
|
||||
)
|
||||
|
||||
generic_value_id = models.IntegerField(blank=True, null=True)
|
||||
|
||||
generic_value_ct = models.ForeignKey(
|
||||
ContentType,
|
||||
blank = True,
|
||||
null = True,
|
||||
on_delete = models.PROTECT,
|
||||
related_name ='value_values'
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='value_values',
|
||||
)
|
||||
|
||||
value_object = generic.GenericForeignKey(
|
||||
ct_field = 'generic_value_ct',
|
||||
fk_field = 'generic_value_id'
|
||||
ct_field='generic_value_ct', fk_field='generic_value_id'
|
||||
)
|
||||
|
||||
created = models.DateTimeField(_('Created'), default = timezone.now)
|
||||
modified = models.DateTimeField(_('Modified'), auto_now = True)
|
||||
created = models.DateTimeField(_('Created'), default=timezone.now)
|
||||
modified = models.DateTimeField(_('Modified'), auto_now=True)
|
||||
|
||||
attribute = models.ForeignKey(
|
||||
Attribute,
|
||||
db_index = True,
|
||||
on_delete = models.PROTECT,
|
||||
verbose_name = _('Attribute')
|
||||
Attribute, db_index=True, on_delete=models.PROTECT, verbose_name=_('Attribute')
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
|
@ -468,6 +459,7 @@ class Entity(object):
|
|||
The helper class that will be attached to any entity
|
||||
registered with eav.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def pre_save_handler(sender, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -515,7 +507,7 @@ class Entity(object):
|
|||
except Attribute.DoesNotExist:
|
||||
raise AttributeError(
|
||||
_('%(obj)s has no EAV attribute named %(attr)s')
|
||||
% dict(obj = self.instance, attr = name)
|
||||
% dict(obj=self.instance, attr=name)
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -557,7 +549,9 @@ class Entity(object):
|
|||
for attribute in self.get_all_attributes():
|
||||
if self._hasattr(attribute.slug):
|
||||
attribute_value = self._getattr(attribute.slug)
|
||||
if attribute.datatype == Attribute.TYPE_ENUM and not isinstance(attribute_value, EnumValue):
|
||||
if attribute.datatype == Attribute.TYPE_ENUM and not isinstance(
|
||||
attribute_value, EnumValue
|
||||
):
|
||||
if attribute_value is not None:
|
||||
attribute_value = EnumValue.objects.get(value=attribute_value)
|
||||
attribute.save_value(self.instance, attribute_value)
|
||||
|
|
@ -592,16 +586,18 @@ class Entity(object):
|
|||
except ValidationError as e:
|
||||
raise ValidationError(
|
||||
_('%(attr)s EAV field %(err)s')
|
||||
% dict(attr = attribute.slug, err = e)
|
||||
% dict(attr=attribute.slug, err=e)
|
||||
)
|
||||
|
||||
illegal = values_dict or (
|
||||
self.get_object_attributes() - self.get_all_attribute_slugs())
|
||||
self.get_object_attributes() - self.get_all_attribute_slugs()
|
||||
)
|
||||
|
||||
if illegal:
|
||||
raise IllegalAssignmentException(
|
||||
'Instance of the class {} cannot have values for attributes: {}.'
|
||||
.format(self.instance.__class__, ', '.join(illegal))
|
||||
'Instance of the class {} cannot have values for attributes: {}.'.format(
|
||||
self.instance.__class__, ', '.join(illegal)
|
||||
)
|
||||
)
|
||||
|
||||
def get_values_dict(self):
|
||||
|
|
@ -612,8 +608,7 @@ class Entity(object):
|
|||
Get all set :class:`Value` objects for self.instance
|
||||
"""
|
||||
return Value.objects.filter(
|
||||
entity_ct = self.ct,
|
||||
entity_id = self.instance.pk
|
||||
entity_ct=self.ct, entity_id=self.instance.pk
|
||||
).select_related()
|
||||
|
||||
def get_all_attribute_slugs(self):
|
||||
|
|
|
|||
132
eav/queryset.py
132
eav/queryset.py
|
|
@ -44,56 +44,56 @@ def is_eav_and_leaf(expr, gr_name):
|
|||
bool
|
||||
"""
|
||||
return (
|
||||
getattr(expr, 'connector', None) == 'AND' and
|
||||
len(expr.children) == 1 and
|
||||
expr.children[0][0] in ['pk__in', '{}__in'.format(gr_name)]
|
||||
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.
|
||||
Rewrites Q-expression to safe form, in order to ensure that
|
||||
generated SQL is valid.
|
||||
|
||||
IGNORE:
|
||||
Suppose we have the following Q-expression:
|
||||
IGNORE:
|
||||
Suppose we have the following Q-expression:
|
||||
|
||||
└── OR
|
||||
├── AND
|
||||
│ └── eav_values__in [1, 2, 3]
|
||||
└── AND (1)
|
||||
├── AND
|
||||
│ └── eav_values__in [4, 5]
|
||||
└── AND
|
||||
└── eav_values__in [6, 7, 8]
|
||||
IGNORE
|
||||
└── OR
|
||||
├── AND
|
||||
│ └── eav_values__in [1, 2, 3]
|
||||
└── AND (1)
|
||||
├── AND
|
||||
│ └── eav_values__in [4, 5]
|
||||
└── AND
|
||||
└── eav_values__in [6, 7, 8]
|
||||
IGNORE
|
||||
|
||||
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::
|
||||
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::
|
||||
|
||||
IGNORE:
|
||||
└── OR
|
||||
├── AND
|
||||
│ └── eav_values__in [1, 2, 3]
|
||||
└── AND
|
||||
└── pk__in [1, 2]
|
||||
IGNORE
|
||||
IGNORE:
|
||||
└── OR
|
||||
├── AND
|
||||
│ └── eav_values__in [1, 2, 3]
|
||||
└── AND
|
||||
└── pk__in [1, 2]
|
||||
IGNORE
|
||||
|
||||
This is done by merging dangerous AND's and substituting them with
|
||||
explicit ``pk__in`` filter, where pks are taken from evaluted
|
||||
Q-expr branch.
|
||||
This is done by merging dangerous AND's and substituting them with
|
||||
explicit ``pk__in`` filter, where pks are taken from evaluted
|
||||
Q-expr branch.
|
||||
|
||||
Args:
|
||||
model_cls (TypeVar): model class used to construct :meth:`QuerySet`
|
||||
from leaf attribute-value expression.
|
||||
expr: (Q | tuple): Q-expression (or attr-val leaf) to be rewritten.
|
||||
Args:
|
||||
model_cls (TypeVar): model class used to construct :meth:`QuerySet`
|
||||
from leaf attribute-value expression.
|
||||
expr: (Q | tuple): Q-expression (or attr-val leaf) to be rewritten.
|
||||
|
||||
Returns:
|
||||
Union[Q, tuple]
|
||||
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.
|
||||
|
|
@ -162,6 +162,7 @@ def eav_filter(func):
|
|||
: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 = []
|
||||
|
|
@ -244,10 +245,7 @@ def expand_eav_filter(model_cls, key, value):
|
|||
else:
|
||||
lookup = '__{}'.format(fields[2]) if len(fields) > 2 else ''
|
||||
value_key = 'value_{}{}'.format(datatype, lookup)
|
||||
kwargs = {
|
||||
value_key: value,
|
||||
'attribute__slug': slug
|
||||
}
|
||||
kwargs = {value_key: value, 'attribute__slug': slug}
|
||||
value = Value.objects.filter(**kwargs)
|
||||
|
||||
return '%s__in' % gr_name, value
|
||||
|
|
@ -317,20 +315,25 @@ class EavQuerySet(QuerySet):
|
|||
|
||||
field_name = 'value_%s' % attr.datatype
|
||||
|
||||
pks_values = Value.objects.filter(
|
||||
# Retrieve pk-values pairs of the related values
|
||||
# (i.e. values for the specified attribute and
|
||||
# belonging to entities in the queryset).
|
||||
attribute__slug=attr.slug,
|
||||
entity_id__in=self
|
||||
).order_by(
|
||||
# Order values by their value-field of
|
||||
# appriopriate attribute data-type.
|
||||
field_name
|
||||
).values_list(
|
||||
# Retrieve only primary-keys of the entities
|
||||
# in the current queryset.
|
||||
'entity_id', field_name
|
||||
pks_values = (
|
||||
Value.objects.filter(
|
||||
# Retrieve pk-values pairs of the related values
|
||||
# (i.e. values for the specified attribute and
|
||||
# belonging to entities in the queryset).
|
||||
attribute__slug=attr.slug,
|
||||
entity_id__in=self,
|
||||
)
|
||||
.order_by(
|
||||
# Order values by their value-field of
|
||||
# appriopriate attribute data-type.
|
||||
field_name
|
||||
)
|
||||
.values_list(
|
||||
# Retrieve only primary-keys of the entities
|
||||
# in the current queryset.
|
||||
'entity_id',
|
||||
field_name,
|
||||
)
|
||||
)
|
||||
|
||||
# Retrive ordered values from pk-value list.
|
||||
|
|
@ -353,29 +356,20 @@ class EavQuerySet(QuerySet):
|
|||
# WHEN id = 4 THEN 3
|
||||
# END
|
||||
#
|
||||
when_clauses = [
|
||||
When(id=pk, then=i)
|
||||
for pk, i in entities_pk
|
||||
]
|
||||
when_clauses = [When(id=pk, then=i) for pk, i in entities_pk]
|
||||
|
||||
order_clause = Case(
|
||||
*when_clauses,
|
||||
output_field=IntegerField()
|
||||
)
|
||||
order_clause = Case(*when_clauses, output_field=IntegerField())
|
||||
|
||||
clause_name = '__'.join(term)
|
||||
# Use when-clause to construct
|
||||
# custom order-by clause.
|
||||
query_clause = query_clause.annotate(
|
||||
**{clause_name: order_clause}
|
||||
)
|
||||
query_clause = query_clause.annotate(**{clause_name: order_clause})
|
||||
|
||||
order_clauses.append(clause_name)
|
||||
|
||||
elif len(term) >= 2 and term[0] == config_cls.eav_attr:
|
||||
raise NotSupportedError(
|
||||
'EAV does not support ordering through '
|
||||
'foreign-key chains'
|
||||
'EAV does not support ordering through ' 'foreign-key chains'
|
||||
)
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class EavConfig(object):
|
|||
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'
|
||||
|
|
@ -60,8 +61,7 @@ class Registry(object):
|
|||
return
|
||||
|
||||
if config_cls is EavConfig or config_cls is None:
|
||||
config_cls = type("%sConfig" % model_cls.__name__,
|
||||
(EavConfig,), {})
|
||||
config_cls = type("%sConfig" % model_cls.__name__, (EavConfig,), {})
|
||||
|
||||
# set _eav_config_cls on the model so we can access it there
|
||||
setattr(model_cls, '_eav_config_cls', config_cls)
|
||||
|
|
@ -125,9 +125,9 @@ class Registry(object):
|
|||
delattr(self.model_cls, self.config_cls.manager_attr)
|
||||
|
||||
if hasattr(self.config_cls, 'old_mgr'):
|
||||
self.config_cls.old_mgr \
|
||||
.contribute_to_class(self.model_cls,
|
||||
self.config_cls.manager_attr)
|
||||
self.config_cls.old_mgr.contribute_to_class(
|
||||
self.model_cls, self.config_cls.manager_attr
|
||||
)
|
||||
|
||||
def _attach_signals(self):
|
||||
"""
|
||||
|
|
@ -136,31 +136,33 @@ class Registry(object):
|
|||
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)
|
||||
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)
|
||||
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__
|
||||
rel_name = (
|
||||
self.config_cls.generic_relation_related_name or self.model_cls.__name__
|
||||
)
|
||||
|
||||
gr_name = self.config_cls.generic_relation_attr.lower()
|
||||
generic_relation = \
|
||||
generic.GenericRelation(Value,
|
||||
object_id_field='entity_id',
|
||||
content_type_field='entity_ct',
|
||||
related_query_name=rel_name)
|
||||
generic_relation = generic.GenericRelation(
|
||||
Value,
|
||||
object_id_field='entity_id',
|
||||
content_type_field='entity_ct',
|
||||
related_query_name=rel_name,
|
||||
)
|
||||
generic_relation.contribute_to_class(self.model_cls, gr_name)
|
||||
|
||||
def _detach_generic_relation(self):
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ 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):
|
||||
if not isinstance(value, datetime.datetime) and not isinstance(
|
||||
value, datetime.date
|
||||
):
|
||||
raise ValidationError(_(u"Must be a date or datetime"))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ from django.forms.widgets import Textarea
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.core import validators
|
||||
|
||||
EMPTY_VALUES = validators.EMPTY_VALUES + ('[]', )
|
||||
EMPTY_VALUES = validators.EMPTY_VALUES + ('[]',)
|
||||
|
||||
|
||||
class CSVWidget(Textarea):
|
||||
is_hidden = False
|
||||
|
||||
def prep_value(self, value):
|
||||
""" Prepare value before effectively render widget """
|
||||
"""Prepare value before effectively render widget"""
|
||||
if value in EMPTY_VALUES:
|
||||
return ""
|
||||
elif isinstance(value, str):
|
||||
|
|
@ -20,4 +21,3 @@ class CSVWidget(Textarea):
|
|||
def render(self, name, value, **kwargs):
|
||||
value = self.prep_value(value)
|
||||
return super().render(name, value, **kwargs)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue