Django: Search form in Class Based ListView

46,534

Solution 1

I think your goal is trying to filter queryset based on form submission, if so, by using GET :

class ProfileSearchView(ListView)
    template_name = '/your/template.html'
    model = Person

    def get_queryset(self):
        name = self.kwargs.get('name', '')
        object_list = self.model.objects.all()
        if name:
            object_list = object_list.filter(name__icontains=name)
        return object_list

Then all you need to do is write a get method to render template and context.

Maybe not the best approach. By using the code above, you no need define a Django form.

Here's how it works : Class based views separates its way to render template, to process form and so on. Like, get handles GET response, post handles POST response, get_queryset and get_object is self explanatory, and so on. The easy way to know what's method available, fire up a shell and type :

from django.views.generic import ListView if you want to know about ListView

and then type dir(ListView). There you can see all the method defined and go visit the source code to understand it. The get_queryset method used to get a queryset. Why not just define it like this, it works too :

class FooView(ListView):
    template_name = 'foo.html'
    queryset = Photo.objects.all()  # or anything

We can do it like above, but we can't do dynamic filtering by using that approach. By using get_queryset we can do dynamic filtering, using any data/value/information we have, it means we also can use name parameter that is sent by GET, and it's available on kwargs, or in this case, on self.kwargs["some_key"] where some_key is any parameter you specified

Solution 2

Well, I think that leaving validation to form is nice idea. Maybe not worth it in this particular case, because it is very simple form - but for sure with more complicated one (and maybe yours will grow also), so I would do something like:

class ProfileList(ListView):
    model = Profile
    form_class = ProfileSearchForm
    context_object_name = 'profiles'
    template_name = 'pages/profile/list_profiles.html'
    profiles = []


    def get_queryset(self):
        form = self.form_class(self.request.GET)
        if form.is_valid():
            return Profile.objects.filter(name__icontains=form.cleaned_data['name'])
        return Profile.objects.all()

Solution 3

This is similar to @jasisz 's approach, but simpler.

class ProfileList(ListView):
    template_name = 'your_template.html'
    model = Profile

    def get_queryset(self):
        query = self.request.GET.get('q')
        if query:
            object_list = self.model.objects.filter(name__icontains=query)
        else:
            object_list = self.model.objects.none()
        return object_list

Then all you have to do on the html template is:

<form method='GET'>
  <input type='text' name='q' value='{{ request.GET.q }}'>
  <input class="button" type='submit' value="Search Profile">
</form>

Solution 4

This has been explained nicely on the generic views topic here Dynamic filtering.

You can do filtering through GET, I don't think you can use POST method for this as ListView is not inherited from edit mixings.

What you can do is:

urls.py

urlpatterns = patterns('', 
                (r'^search/(\w+)/$', ProfileSearchListView.as_view()),
              )

views.py

class ProfileSearchListView(ListView):
    model = Profile
    context_object_name = 'profiles'
    template_name = 'pages/profile/list_profiles.html'
    profiles = []

    def get_queryset(self):
         if len(self.args) > 0:
               return Profile.objects.filter(name__icontains=self.args[0])
         else:
               return Profile.objects.filter()

Solution 5

Search on all fields in model

class SearchListView(ItemsListView):

# Display a Model List page filtered by the search query.

def get_queryset(self):
    fields = [m.name for m in super(SearchListView, self).model._meta.fields]
    result = super(SearchListView, self).get_queryset()
    query = self.request.GET.get('q')
    if query:
        result = result.filter(
            reduce(lambda x, y: x | Q(**{"{}__icontains".format(y): query}), fields, Q())
        )
    return result
Share:
46,534
neurix
Author by

neurix

Updated on November 08, 2020

Comments

  • neurix
    neurix over 3 years

    I am trying to realize a Class Based ListView which displays a selection of a table set. If the site is requested the first time, the dataset should be displayed. I would prefer a POST submission, but GET is also fine.

    That is a problem, which was easy to handle with function based views, however with class based views I have a hard time to get my head around.

    My problem is that I get a various number of error, which are caused by my limited understanding of the classed based views. I have read various documentations and I understand views for direct query requests, but as soon as I would like to add a form to the query statement, I run into different error. For the code below, I receive an ValueError: Cannot use None as a query value.

    What would be the best practise work flow for a class based ListView depending on form entries (otherwise selecting the whole database)?

    This is my sample code:

    models.py

    class Profile(models.Model):
        name = models.CharField(_('Name'), max_length=255)
    
        def __unicode__(self):
            return '%name' % {'name': self.name}
    
        @staticmethod
        def get_queryset(params):
    
            date_created = params.get('date_created')
            keyword = params.get('keyword')
            qset = Q(pk__gt = 0)
            if keyword:
                qset &= Q(title__icontains = keyword)
            if date_created:
                qset &= Q(date_created__gte = date_created)
            return qset
    

    forms.py

    class ProfileSearchForm(forms.Form):
        name = forms.CharField(required=False)
    

    views.py

    class ProfileList(ListView):
        model = Profile
        form_class = ProfileSearchForm
        context_object_name = 'profiles'
        template_name = 'pages/profile/list_profiles.html'
        profiles = []
    
    
        def post(self, request, *args, **kwargs):
            self.show_results = False
            self.object_list = self.get_queryset()
            form = form_class(self.request.POST or None)
            if form.is_valid():
                self.show_results = True
                self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
            else:
                self.profiles = Profile.objects.all()
            return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))
    
        def get_context_data(self, **kwargs):
            context = super(ProfileList, self).get_context_data(**kwargs)
            if not self.profiles:
                self.profiles = Profile.objects.all()
            context.update({
                'profiles': self.profiles
            })
            return context
    

    Below I added the FBV which does the job. How can I translate this functionality into a CBV? It seems to be so simple in function based views, but not in class based views.

    def list_profiles(request):
        form_class = ProfileSearchForm
        model = Profile
        template_name = 'pages/profile/list_profiles.html'
        paginate_by = 10
    
        form = form_class(request.POST or None)
        if form.is_valid():
            profile_list = model.objects.filter(name__icontains=form.cleaned_data['name'])
        else:
            profile_list = model.objects.all()
    
        paginator = Paginator(profile_list, 10) # Show 10 contacts per page
        page = request.GET.get('page')
        try:
            profiles = paginator.page(page)
        except PageNotAnInteger:
            profiles = paginator.page(1)
        except EmptyPage:
            profiles = paginator.page(paginator.num_pages)
    
        return render_to_response(template_name, 
                {'form': form, 'profiles': suppliers,}, 
                context_instance=RequestContext(request))
    
  • Vic Nicethemer
    Vic Nicethemer over 8 years
    i got an error that form_class is not defined, how to fix?
  • meteor
    meteor over 8 years
    Vic Nicethemer, you can define it
  • Karl M.W.
    Karl M.W. over 8 years
    @VicNicethemer It should be form = self.form_class(self.request.GET).
  • agmezr
    agmezr almost 7 years
    instead of using the try you could use get with a default value, i think it helps with readability. name = self.kargs.get('name', None) and then if name: # do some stuff
  • EastSw
    EastSw almost 6 years
    Working link for dynamic filtering : docs.djangoproject.com/en/dev/topics/class-based-views/…
  • Nilanj
    Nilanj almost 4 years
    can you please guide me with url here,
  • umair mehmood
    umair mehmood over 2 years
    how we can do the same if we have to query in multiple models and send the results?