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