from __future__ import unicode_literals from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Q from django.urls import reverse from image_cropping import ImageRatioField from orderable.models import Orderable from eav.models import Entity from icmgeneric.base import GenericPage from django.utils.translation import ugettext_lazy as _ class Product(GenericPage): name = models.CharField(max_length=256, verbose_name=_('Name')) annotation = models.TextField(null=True, blank=True, verbose_name=_('Annotation')) category = models.ForeignKey(Category, null=True, verbose_name=_('Category')) price = models.DecimalField(null=True, verbose_name=_('Price'), max_digits=6, decimal_places=2) old_price = models.DecimalField(null=True, blank=True, verbose_name=_('Old price'), max_digits=6, decimal_places=2) discount = models.IntegerField(null=True, blank=True, verbose_name=_('Discount')) in_new_products_list = models.BooleanField(default=False, verbose_name=_('In new products list')) in_stock = models.BooleanField(default=True, verbose_name=_('In stock')) active = models.BooleanField(default=True, verbose_name=_('Active')) @property def entity(self): return Entity(self) @property def values(self): return self.entity.get_values() def get_values_for_detail(self): dc = {} for value in self.values: if value.attribute.name not in dc.keys(): dc[value.attribute.name] = [value.value] else: dc[value.attribute.name].append(value.value) return dc def get_absolute_url(self): return reverse('catalog:detail', args=[self.category.slug, self.slug]) def get_attributes(self): attr_list = [] for item in self.values: attr_list.append(item.attribute) return attr_list def get_filters(self): return self.values.filter(Q(active=True) & Q(in_filter_list=True)) def get_filters_values(self): filters = [] for filter in self.values.filter(Q(active=True) & Q(in_filter_list=True)): filters.append(filter.value) return filters def get_photos(self): return self.photoitem_set.filter(active=True) def __unicode__(self): return self.name class Meta: ordering = ['name'] verbose_name = _('Product') verbose_name_plural = _('Products') eav.register(Product) 

these are property and value models for entities

 class Attribute(models.Model): class Meta: ordering = ['content_type', 'name'] unique_together = ('site', 'content_type', 'slug') verbose_name, verbose_name_plural = _('Attribute'), _('Attributes') TYPE_TEXT = 'text' TYPE_FLOAT = 'float' TYPE_INT = 'int' TYPE_DATE = 'date' TYPE_BOOLEAN = 'bool' # TYPE_OBJECT = 'object' # TYPE_ENUM = 'enum' DATATYPE_CHOICES = ( (TYPE_TEXT, _(u"Text")), (TYPE_FLOAT, _(u"Float")), (TYPE_INT, _(u"Integer")), (TYPE_DATE, _(u"Date")), (TYPE_BOOLEAN, _(u"True / False")), # (TYPE_OBJECT, _(u"Django Object")), # (TYPE_ENUM, _(u"Multiple Choice")), ) name = models.CharField(max_length=100, help_text=_(u"User-friendly attribute name"), verbose_name=_(u'name')) content_type = models.ForeignKey(ContentType, blank=True, null=True, verbose_name=_(u"content type")) site = models.ForeignKey(Site, verbose_name=_(u"site"), default=settings.SITE_ID) slug = EavSlugField(max_length=50, db_index=True, help_text=_(u"Short unique attribute label"), verbose_name=_(u'slug')) description = models.CharField(max_length=256, blank=True, null=True, help_text=_(u"Short description"), verbose_name=_(u'description')) # enum_group = models.ForeignKey(EnumGroup, verbose_name=_(u"choice group"), blank=True, null=True) # type = models.CharField(_(u"type"), max_length=20, blank=True, null=True) @property def help_text(self): return self.description datatype = EavDatatypeField(max_length=6, choices=DATATYPE_CHOICES, verbose_name=_(u"data type")) created = models.DateTimeField(default=timezone.now, editable=False, verbose_name=_(u"created")) modified = models.DateTimeField(auto_now=True, verbose_name=_(u"modified")) required = models.BooleanField(default=False, verbose_name=_(u"required")) display_order = models.PositiveIntegerField(default=1, verbose_name=_(u"display order")) active = models.BooleanField(default=True, verbose_name=_('Active')) objects = models.Manager() on_site = CurrentSiteManager() def get_validators(self): ''' Returns the appropriate validator function from :mod:`~eav.validators` as a list (of length one) for the datatype. .. note:: The reason it returns it as a list, is eventually we may want this method to look elsewhere for additional attribute specific validators to return as well as the default, built-in one. ''' DATATYPE_VALIDATORS = { 'text': validate_text, 'float': validate_float, 'int': validate_int, 'date': validate_date, 'bool': validate_bool, 'object': validate_object, 'enum': validate_enum, } validation_function = DATATYPE_VALIDATORS[self.datatype] return [validation_function] # 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) # if self.datatype == self.TYPE_ENUM: # if value not in self.enum_group.enums.all(): # raise ValidationError(_(u"%(enum)s is not a valid choice " # u"for %(attr)s") % \ # {'enum': value, 'attr': self}) 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) self.full_clean() 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. # ''' # if self.datatype == self.TYPE_ENUM and not self.enum_group: # raise ValidationError(_( # u"You must set the choice group for multiple choice" \ # u"attributes")) # # if self.datatype != self.TYPE_ENUM and self.enum_group: # raise ValidationError(_( # u"You can only assign a choice group to multiple choice " \ # u"attributes")) # 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*. # ''' # if not self.datatype == Attribute.TYPE_ENUM: # return None # return self.enum_group.enums.all() 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. If a :class:`Value` object for this *entity* and attribute doesn't exist, one will be created. .. 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: value_obj = self.value_set.get(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) if value == None or value == '': value_obj.delete() return if value != value_obj.value: value_obj.value = value value_obj.save() def __unicode__(self): return u"%s.%s (%s)" % (self.content_type, self.name, self.get_datatype_display()) class Value(models.Model): entity_ct = models.ForeignKey(ContentType, related_name='value_entities', verbose_name=_(u'Entity')) entity_id = models.IntegerField(verbose_name=_(u'Entity Code')) entity = generic.GenericForeignKey(ct_field='entity_ct', fk_field='entity_id') attribute = models.ForeignKey(Attribute, db_index=True, verbose_name=_(u"Attribute")) value_text = models.TextField(blank=True, null=True, verbose_name=_(u"Text Value")) value_float = models.FloatField(blank=True, null=True, verbose_name=_(u"Float Value")) value_int = models.IntegerField(blank=True, null=True, verbose_name=_(u"Integer Value")) value_date = models.DateTimeField(blank=True, null=True, verbose_name=_(u"Date Value")) value_bool = models.NullBooleanField(blank=True, null=True, verbose_name=_(u"True/False Value")) # value_enum = models.ForeignKey(EnumValue, blank=True, null=True, related_name='eav_values') # generic_value_id = models.IntegerField(blank=True, null=True) # generic_value_ct = models.ForeignKey(ContentType, blank=True, null=True, related_name='value_values') # value_object = generic.GenericForeignKey(ct_field='generic_value_ct', fk_field='generic_value_id') created = models.DateTimeField(_(u"created"), default=timezone.now) modified = models.DateTimeField(_(u"modified"), auto_now=True) active = models.BooleanField(default=True, verbose_name=_('Active')) in_filter_list = models.BooleanField(default=False, verbose_name=_('In filter list')) 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.enums.all(): # raise ValidationError(_(u"%(choice)s is not a valid " \ # u"choice for %s(attribute)") % \ # {'choice': self.value_enum, # 'attribute': self.attribute}) def get_count(self): dc = {'eav__' + self.attribute.slug: self.value} return self.entity.__class__.objects.filter(Q(**dc)).count() 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) def __unicode__(self): return u"%s - %s: \"%s\"" % (self.entity, self.attribute.name, self.value) class Meta: verbose_name, verbose_name_plural = _(u'Value'), _(u'Values') 

Added a color and type property to the admin panel for the Product

Then I make for example such a query:

 qs = Product.objects.filter(Q(name='name_1') & Q(name='name_2')) 

And does not find a single object. Tell me what's wrong.

  • one
    An object cannot have two names name_1 and name_2 at the same time, and that's not located - andreymal
  • An example, of course, I cited bad, but my object may have two properties, for example color - Igor Lisenko
  • Then obviously another example is needed) - andreymal
  • one
    The same changes absolutely everything and should be written in the question itself - andreymal
  • one
    And such a query will work for you? Product.objects.filter (Q (eav__color = 'Red'))? - Mae

1 answer 1

With django-eav did not work, but there is an idea. Try filtering several times, for each criterion separately:

 qs = [Q(eav__color='Red'), (Q(eav__color='Green')] products = Product.objects.empty() for q in qs: products = products.filter(q) 
  • FieldError: Cannot resolve keyword 'eav' into field. Choices are: active, annotation, category, category_id, content, discount, eav_values, header, id, in_new_products_list, - Igor Lisenko
  • one
    I used the field name that you provided in the comments. If you believe the documentation, it should be successfully resolved. That is, something is wrong with your code. Again, in comparison with the code from the documentation, it is too complicated and volume. - Sergey Gornostaev