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

Вернуться на верх