Django ListView - Form to filter and sort

40,642

Solution 1

You don't need post. Pass the filter value and order_by in the url for example:

.../update/list/?filter=filter-val&orderby=order-val

and get the filter and orderby in the get_queryset like:

class MyView(ListView):
    model = Update
    template_name = "updates/update.html"
    paginate_by = 10

    def get_queryset(self):
        filter_val = self.request.GET.get('filter', 'give-default-value')
        order = self.request.GET.get('orderby', 'give-default-value')
        new_context = Update.objects.filter(
            state=filter_val,
        ).order_by(order)
        return new_context

    def get_context_data(self, **kwargs):
        context = super(MyView, self).get_context_data(**kwargs)
        context['filter'] = self.request.GET.get('filter', 'give-default-value')
        context['orderby'] = self.request.GET.get('orderby', 'give-default-value')
        return context

Make sure you give proper default value to filter and orderby

Example form (you can modify this to your need):

<form method="get" action="{% url 'update-list' %}">
    <p>Filter: <input type="text" value={{filter}} name="filter"/></p>
    <p>order_by: <input type="text" value={{orderby}} name="orderby"/></p>
    <p><input type="submit" name="submit" value="submit"/></p>
</form>

Solution 2

I am wondering why nobody mentioned here this cool library: django-filter https://github.com/carltongibson/django-filter

you can define your logic for filtering very clean and get fast working forms etc.

demo here: https://stackoverflow.com/a/46492378/953553

Solution 3

I posted this elsewhere but I think this adds to the selected answer.

I think you would be better off doing this via get_context_data. Manually create your HTML form and use GET to retrieve this data. An example from something I wrote is below. When you submit the form, you can use the get data to pass back via the context data. This example isn't tailored to your request, but it should help other users.

def get_context_data(self, **kwargs):
    context = super(Search, self).get_context_data(**kwargs)
    filter_set = Gauges.objects.all()
    if self.request.GET.get('gauge_id'):
        gauge_id = self.request.GET.get('gauge_id')
        filter_set = filter_set.filter(gauge_id=gauge_id)

    if self.request.GET.get('type'):
        type = self.request.GET.get('type')
        filter_set = filter_set.filter(type=type)

    if self.request.GET.get('location'):
        location = self.request.GET.get('location')
        filter_set = filter_set.filter(location=location)

    if self.request.GET.get('calibrator'):
        calibrator = self.request.GET.get('calibrator')
        filter_set = filter_set.filter(calibrator=calibrator)

    if self.request.GET.get('next_cal_date'):
        next_cal_date = self.request.GET.get('next_cal_date')
        filter_set = filter_set.filter(next_cal_date__lte=next_cal_date)

    context['gauges'] = filter_set
    context['title'] = "Gauges "
    context['types'] = Gauge_Types.objects.all()
    context['locations'] = Locations.objects.all()
    context['calibrators'] = Calibrator.objects.all()
    # And so on for more models
    return context

Solution 4

This is how we do it, that way you get validation/type conversion as well:

class UnitList(PermissionRequiredMixin, ListView):
    """ Class based view to show a list of all buildings for a specific user """

    model = Unit
    ordering = ['building', 'unit']
    paginate_by = 100

    # Access
    permission_required = ['core.manager_perm']
    raise_exception = True  # If true, give access denied message rather than redirecting to login

    def get_queryset(self):
        try:
            units = self.model.objects.filter(building__company=self.request.user.profile.company)
        except Profile.DoesNotExist:
            units = self.model.objects.none()

        form = UnitSearchForm(self.request.GET)

        if form.is_valid():
            filters = {}

            address = form.cleaned_data['address']
            neighborhood = form.cleaned_data['neighborhood']
            beds = form.cleaned_data['beds']
            amenity = form.cleaned_data['amenity']

            if address:
                filters['building__street_index__istartswith'] = compute_street_address_index(address)

            if neighborhood:
                filters['building__neighborhood__icontains'] = neighborhood

            if beds:
                filters['beds'] = beds

            if amenity:
                filters['unit_amenities__name__iexact'] = amenity
    
            units = units.filter(**filters)

        return units.select_related('building').order_by(*self.ordering)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = UnitSearchForm(self.request.GET)

        return context
Share:
40,642

Related videos on Youtube

Rooterle
Author by

Rooterle

Updated on July 09, 2022

Comments

  • Rooterle
    Rooterle almost 2 years

    My Goal

    • A site that list all my Updates (model) in a table
    • Dont display all models at once (pagination - maybe 10 per page)
    • Filter and sort the list

    My thoughts

    • I can use ListView to get a set of all my Updates
    • Use paginate_by = 10
    • Use a form to set order_by or filter in my QuerySet

    My Problem

    I am not sure how to add an form to modify my QuerySet with filter and sortings. My Idea was to modify the Query in get_queryset with additional filter and order_by.

    My View

    class MyView(ListView):
        model = Update
        template_name = "updates/update.html"
        paginate_by = 10
    
        def get_queryset(self):
            return Update.objects.filter(
                ~Q(state=Update.STATE_REJECTED),
                ~Q(state=Update.STATE_CANCELED),
                ~Q(state=Update.STATE_FINISHED),
            ).order_by(
                'planned_release_date'
            )
    

    My Idea

    Something like this. I know it's not working like this ... just to illustrate

    class MyView(ListView):
        model = Update
        template_name = "updates/update.html"
        paginate_by = 10
    
        def post(self, request, *args, **kwargs):
            new_context = Update.objects.filter(
                request.POST.get("filter"),
            ).order_by(
                request.POST.get("sorting"),
            )
    
        def get_queryset(self):
            return Update.objects.filter(
                ~Q(state=Update.STATE_REJECTED),
                ~Q(state=Update.STATE_CANCELED),
                ~Q(state=Update.STATE_FINISHED),
            ).order_by(
                'planned_release_date'
            )
    
  • Rooterle
    Rooterle over 8 years
    Hi, thanks for your solution. It's working except that I loose sorting and filter when I switch the page :-(
  • Anush Devendra
    Anush Devendra over 8 years
    @Rooterle I have edited my answer, please check. I have edited both view and form.
  • Rooterle
    Rooterle over 8 years
    Ok thanks :-) In the template I have to add the parameters to the links, then its working well.