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 %}