Djangoのクラスベースのビューを使って検索画面を作成する場合、FromViewとListViewを使うのが選択肢となる。しかし、同じページで処理する場合には、二つのクラスの機能を合わせたViewが必要となってくる。調べたところ、いいものが見つからず、自分でクラスを作成した。
https://github.com/Arisophy/django-searchview
レポジトリーには、サンプルコード、データも入っています。
1.SearchViewとSearchFormのコード
追加で使うクラスは、SearchViewとSearchFormクラスです。GitHubを参照して下さい。
[GitHub]
パッケージをインストールするか、これらの2つのファイルをプロジェクトにインポートし、SearchFormクラスとSearchViewクラスを使用することで、検索ページを簡単に実装できる。
pip install django-serachview-lib
2.使い方
基本的に、FormViewとListViewの使い方と同じ。SearchViewの特徴として、特別な部分は以下。
1)first_display_all_list
Trueの場合、最初のページのobject_listは、SearchFormの初期値から検索される。 Falseの場合、最初のページのobject_listは空となる。
2)form_class
SearchView.form_classに設定するのは、SearchFormクラスである。
3)検索条件の指定方法
- SearchView.get_form_conditionsでは、SearchFormでのField変数名を、Field Lookup検索条件のLHSとなる。
- 変数名を使わない場合や、複雑な検索条件は、SearchView.get_form_conditionsをオーバーライドする。
- “annotate“が必要な場合は、get_annotated_querysetをオーバーライドして追加する。
4)ページネーション
POSTで遷移すること。GETで要求した場合は、404エラーとなる。
5)success_url
FormとListが同じページとなったので、FormView.success_urlは使われない。
3.検索画面の実装例
1)SearchForm,SearchViewを取り込む
pip install django-searchview-lib
あるいは、views.pyとforms.pyを、プロジェクトの適当なライブラリ用のフォルダに配置する。
2)Formのサンプル
SearchFormクラスを使用して、検索フィールドを定義する必要がある。フィールドの名前は検索条件になる。以下はサンプルで、自分のDBモデルに合わせて作成する。
[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のサンプル
SearchViewクラスから、検索画面のビューを定義する。
[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のサンプル
: from sample import views : urlpatterns = [ : # Test path('', views.AlbumSearchListView.as_view(), name='album'), path('musician/', views.MusicianSearchListView.as_view(), name='musician'), : ] :
5)テンプレートのサンプル
テンプレートについては、FormViewで作っていたテンプレートと、ListViewで作っていたテンプレートを、混ぜて一つのテンプレートに入れることができる。ただ、1点だけ、ページネーションを使う場合は、POSTで要求するようにすること。
: {% 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 %}