When I wanted to make a Search Page with class based View of Django, the first choice was using FormView and ListView. But, I wanted to display search form and results list in one page. I needed a new view class, multiple inheritance from FormView and ListView. I searched for it, but I couldn’t find a good solution. So I made it by myself.
https://github.com/Arisophy/django-searchview
The repository also contains sample code and data.
1.Code of SearchView and SearchForm
Additional classes are SearchView and SearchForm. See my GitHub.
You can easily implement a search page by importing these two files into your project and using the SearchForm and SearchView classes or install django-searchview-lib.
pip install django-searchview-lib
2.How to use
Basically, SearchView is same as FormView and ListView. The differences of SearchView are below.
1)first_display_all_list
If True, the object_list on the first page will be searched from the initial values of SearchForm. If False, the object_list on the first page is empty.
2)form_class
SearchView.form_class must be SearchForm class.
3)How to specify search conditions
- In SearchView.get_form_conditions, Field valiable names of SearchForm are taken as LHS of Field lookups .
- Override SearchView.get_form_conditions, if you don’t use variable names or want to make complex search conditions.
- Override SearchView.get_annotated_queryset, if you need “annotate“.
4)Pagination
The request method must be POST. For the GET method, get a 404 error.
5)success_url
FormView.success_url is not used by SearchView, because form and list are on the same page.
3.Sample of Search Page Implementation
1)Import SearchForm and SearchView
pip install django-searchview-lib
Otherwise, place views.py and forms.py in a folder for a suitable library path of your project.
2)Form Sample
You have to use SearchForm class and define search Fields. The names of Fields become search conditions. The following is a sample, please create it according to your DB model.
[forms.py] from django.forms import Form : # Test For SearchView from searchview.forms import SearchForm class MusicianSearchForm(SearchForm): """Search for Musicians.""" first_name = forms.CharField( label='first_name =', required = False, max_length=50, ) last_name__istartswith = forms.CharField( label="last_name like 'val%'", required = False, max_length=50, ) instrument__contains = forms.CharField( label="instrument like '%val%'", required = False, max_length=32, ) album_count__gte = forms.IntegerField( label='album_count >=', required = False, initial=1, min_value=0, max_value=999, ) album_count__lt = forms.IntegerField( label='album_count <', required = False, min_value=0, max_value=999, ) class AlbumSearchForm(SearchForm): """Search for Albums.""" : artist__first_name__contains = forms.CharField( label="Musician.first_name like '%val%'", required = False, max_length=50, )
3)View Sample
Use SearchView class to define a view for your search page.
[views.py] : # Test For SearchView from django.db.models import Count from searchview.views import SearchView from .forms import MusicianSearchForm,AlbumSearchForm from .models import Musician,Album class AlbumSearchListView(SearchView): template_name = 'app/album.html' form_class = AlbumSearchForm model = Album paginate_by = 5 first_display_all_list = False ordering='-release_date' class MusicianSearchListView(SearchView): template_name = 'app/musician.html' form_class = MusicianSearchForm model = Musician paginate_by = 5 first_display_all_list = True ordering='first_name' def get_annotated_queryset(self, queryset, cleaned_data): # 'album_count' queryset = queryset.annotate( album_count=Count('album')) return queryset
4)Urlpatterns Sample
[url.py] : from sample import views : urlpatterns = [ : # Test path('', views.AlbumSearchListView.as_view(), name='album'), path('musician/', views.MusicianSearchListView.as_view(), name='musician'), : ] :
5)Template Sample
In the template, you can mix the template code for FormView and the temlpate code for ListView into one template. Don’t forget that pagenation request must be POST method.
: {% block content %} : <div class="row"> <div class="col-md-6"> <h2>Search Condition</h2> <div class="table-responsive"> <form method="post" id="search_form"> {% csrf_token %} <table class="table table-striped table-sm"> <tbody> {{ form.as_table }} </tbody> </table> <p class="text-center"><input class="btn btn-primary" type="submit" value="Search"></p> <input id="pagenation_page" type="hidden" name="page" /> </form> </div> </div> <div class="col-md-6"> <h2>Search Results(Album)</h2> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <tr> <th>#</th> <th>name</th> <th>release_date</th> <th>num_stars</th> <th>artist</th> </tr> </thead> <tbody> {% for album in object_list %} <tr> <td>{{ album.id }}</td> <td>{{ album.name }}</td> <td>{{ album.release_date }}</td> <td>{{ album.num_stars }}</td> <td>{{ album.artist.first_name }}</td> </tr> {% endfor %} </tbody> </table> </div> <div class="pagination"> <span class="step-links"> {% if page_obj.has_previous %} <button type="submit" class="page-btn" value="1">« first</button> <button type="submit" class="page-btn" value="{{ page_obj.previous_page_number }}">previous</button> {% endif %} <span class="current"> Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. </span> {% if page_obj.has_next %} <button type="submit" class="page-btn" value="{{ page_obj.next_page_number }}">next</button> <button type="submit" class="page-btn" value="{{ page_obj.paginator.num_pages }}">last »</button> {% endif %} </span> </div> </div> </div> {% endblock %} {% block scripts %} <script> $(function(){ $('.page-btn').click(function(){ var form1 = document.forms['search_form']; var pageno = $(this).val(); $('#pagenation_page').val(pageno); form1.submit(); return false; }); }) </script> {% endblock %}