How to Order Queryset by EAV Attribute in Django Using django-filters?
I'm working on a Django project where I'm using an EAV (Entity-Attribute-Value) model to store dynamic attributes for my Product model. I need to allow users to filter and order products by price, which is stored as an EAV attribute (slug='price').
My Setup: I’m using django-filters for filtering. Price is stored in the Value model (part of Django EAV). I'm using a Subquery annotation to retrieve the price from Value. Ordering by price is not working correctly.
class BaseInFilterWidget(django_filters.widgets.SuffixedMultiWidget):
suffixes = ['choice']
def __init__(self, *args, **kwargs):
widgets = [forms.SelectMultiple,]
super().__init__(widgets, *args, **kwargs)
def value_from_datadict(self, data, files, name):
""" Override this method to properly extract the value from the request. """
# Since the key ends with '_choices', we need to handle it here.
name_choices = f"{name}_choice"
return data.getlist(name_choices)
class ProductFilter(django_filters.FilterSet):
class Meta:
model=product.Product
fields=[]
def __init__(self, *args, **kwargs):
super(ProductFilter, self).__init__(*args, **kwargs)
multiple_choice = False
for slug in self.data:
if slug in ['category', 'location', 'order_by']:
if slug == 'category':
self.filters[slug] = django_filters.ModelChoiceFilter(
field_name='category',
queryset=Category.objects.all(),
label='Category',
to_field_name='slug'
)
elif slug == 'location':
self.filters[slug] = django_filters.ModelChoiceFilter(
field_name='location',
queryset=Location.objects.all(),
label='Location',
to_field_name='slug'
)
elif slug == 'order_by':
self.filters[slug] = django_filters.OrderingFilter(
fields=(
("created", "created"),
("price", "price")
),
field_labels={
'price': 'price Added',
},
label='Sort by'
)
continue
if slug.endswith('_choice'):
multiple_choice = True
slug = slug[:-7]
elif slug.endswith('_max') or slug.endswith('_min'):
slug = slug[:-4]
field_name = f"eav__{slug}"
if hasattr(product.Product().eav, slug):
if multiple_choice:
self.filters[slug] = django_filters.BaseInFilter(
field_name=field_name,
lookup_expr='in',
widget=BaseInFilterWidget
)
else:
self.filters[slug] = django_filters.RangeFilter(field_name=field_name)
def filter_queryset(self, queryset):
# If ordering by price is requested, annotate the queryset with a 'price' field.
order_by_value = self.data.get('order_by', '')
order_by_fields = order_by_value.split(',') if order_by_value else []
if 'price' in order_by_fields or '-price' in order_by_fields:
price_subquery = Value.objects.filter(
product=OuterRef('pk'),
attribute__slug='price'
).values('value_float')[:1]
queryset = queryset.annotate(
price=Subquery(price_subquery, output_field=FloatField())
)
return super(ProductFilter, self).filter_queryset(queryset)
@property
def qs(self):
parent = super(ProductFilter, self).qs
try:
return parent
except TypeError as e:
print("hello")
I need to do order_by price