Search code examples
pythondjangodjango-formsdjango-filter

How add non model field to django filters


I have a view with pagination and filtering that displays a list of products. I would like to provide the user with the option to choose between displaying all the products they can buy or displaying only the products they have already purchased.

show_products.py

def show_products(previously_purchased, user):
    if previously_purchased:
        transactions = Transaction.objects.filter(user_id=user).values_list('product_id')
        return Product.objects.filter(pk__in=[q[0] for q in transactions])
    return Product.objects.all()

models.py

class Product(models.Model):
    name = models.CharField(max_length=255)
    ...

views.py

class ProductList(ListView):
    ...
    def dispatch(self, request, *args, **kwargs):
        return super(ProductList, self).dispatch(request, *args, **kwargs)
    def get_queryset(self):
        # show_ = True if 'previously_purchased' in  self.request.GET and self.request.GET['previously_purchased'] == 'True' else False
        self.filterset = ProductFilter(
            self.request.GET,
            queryset=show_products(previously_purchased=) # show_products(previously_purchased=show_)
        )
        return self.filterset.qs
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = self.filterset.form
        context['products'] = show_products(previously_purchased=)
        return context

filters.py

class ProductFilter(django_filters.FilterSet):
    CHOICE = {
        ('True', 'previously purchased'),
        ('False', 'all')
    }
    name = django_filters.CharFilter()
    # previously_purchased = django_filters.ChoiceFilter(choices=CHOICE)
    
    class Meta:
        model = Product
        fields = ['name']
    

The show_products(previously_purchased=) function is responsible for creating a queryset of all products or only previously purchased ones, using the previously_purchased parameter.

Unfortunately, when I add previously_purchased in ProductFilter, I get an error saying that the previously_purchased field does not exist in the Product model.

When I add @property in the Product model, and intercept self.request.GET['previously_purchased'] to change the show_products() parameter, the display works fine. But the pagination stops working.


Solution

  • In django-filter you can specify keyword argument named as method inside field which accepts either a callable or the name of a method so you camodify you ProductFilter like this

    class ProductFilter(django_filters.FilterSet):
        CHOICE = {
            ('True', 'previously purchased'),
            ('False', 'all')
        }
        name = django_filters.CharFilter()
        previously_purchased = django_filters.ChoiceFilter(
                                 choices=CHOICE,
                                 method='filter_previously_purchased',
                               )
        def filter_previously_purchased(self, queryset, name, value):
            if value == 'True':
                transactions = Transaction.objects.filter(user_id=self.request.user).values_list('product_id')
                queryset = queryset.filter(pk__in=[q[0] for q in transactions])
            return queryset
        
        class Meta:
            model = Product
            fields = ['name', 'previously_purchased',]
    

    In above code I've added your show_products() logic inside filter_previously_purchased function with which takes these arguments

    • queryset It is queryset of specified model eg. Product.objects.all()
    • name it's a field name eg. previously_purchased
    • value it's value which comes from client.