django-filter の使い方（その１、関数ビュー編）

## はじめに
公式ページの一番簡単な例を参考にdjango-filterが動くまでを初心者向けに報告したいと思います。記事は大きく二つです。
・関数ビューでの作り方
・クラスビューでの作り方

以下のことには触れません。
・Django REST Framework との統合
・ModelChoiceFilter を使っての例（プルダウン式での選択検索）
初心者にとってこれらは応用で難しいので、ここでは基本的な動きを理解することに徹します。
当方の環境ですが
* python 3.8.6
* django 4.1.7
* django-filter 23.1

django-filterの公式ページ
https://django-filter.readthedocs.io/en/stable/

## インストール
すでにプロジェクトやアプリ作成(ここでは'myapp'とします)まで完了している前提で進めます。仮想環境に入って
```
pip install Django-filter
```
ここの真ん中はハイフンです。
settings.py に、'django_filters',を追加します。
```[project]/settings.py
INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]
```
こちらはアンダーバー、sが付きます。

## モデル作成（models.py）
models.py に公式のものと同じモデル（ProductとManufacturer）を作成します。

```myapp/models.py
from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=20)
    country = models.CharField(max_length=20)

    class Meta:
        db_table = 'manufacturer'

    def __str__(self) :
        return self.name

class Product(models.Model):
    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    description = models.TextField()
    release_date = models.DateField()
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)

    class Meta:
        db_table = 'product'

    def __str__(self) :
        return self.name
```
Product の manufacturerは外部キーです。
そして、いつものように、
```
python manane.py makemigrations
python manane.py migrate
```
を実行。スーパーユーザーで管理画面にログインして何件がデータを登録しておきましょう。

## ルーティング（関数ビュー）
myapp内に urls.py を作成して以下を記述します。

```myapp/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.product_list, name="product_list"),
]
```
忘れずに、プロジェクトの urls.py も変更しておきます。
```project/urls.py
from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',include('myapp.urls')),
]
```
ここまでは、通常の関数ビューの作成の過程と変わりません。

## フィルター定義ファイル（filters.py）の作成
次に、myapp 内に、filters.py という別ファイルを作ります。
ここで初めて、django_filters をインポートしてProduct モデルに対するフィルター仕様を定義します。例は、公式ページの一番最初のものにします。

```myapp/filters.py
import django_filters
from .models import Product 

class ProductFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='iexact')
    class Meta:
        model = Product
        fields = ['price', 'release_date']
```
FormViewと同じような感じです。

少し解説すると、
・モデルと同じフィールド名を指定する。
・CharFilter : キャラクター型のフィルター。他に、NumberFilter, DateFilter, BooleanFilter, ChoiceFilter ・・などなど。
・ルックアップ式(lookup_expr)が使える。
　ここでの'iexact'は正確に一字一句同じキーワードで絞るという指定になります。他に'icontains'(を含む),'gt'(<),'lt'(>)とか。この辺は公式をご覧ください。

そして、ここで定義した、name, price, release_date の３つのfield がフィルターの対象になります。なお、Meta クラスの fields に指定した分（nameのように明示的に定義していない分）については、'iexact'の扱いになります。

※別ファイルとしましたが、上記の内容はviews.py に直接書いても構いません。

## 関数ビュー(views.py)の定義
views.py に製品一覧(product_list)を表示するための関数ビューを定義します。

```myapp/views.py
from django.shortcuts import render
from .models import Product
from .filters import ProductFilter

def product_list(request):
	f = ProductFilter(request.GET, queryset=Product.objects.all())
 	return render(request, 'myapp/product_list.html', {'filter': f})
```
　前項の filters.py で作成した ProductFilter も import して使ってください。
　ここまでで、あれ・・と思われた方は鋭いです。 検索に関するフォーム(forms.py)の importや記述がありません。django-filter では、先ほど定義した name, price, release_date の３つの field をもつ検索フォームのオブジェクトまで生成してくれます。

※f の中身が見たくて、print(f)を入れたのですがなぜかエラーになります。

## テンプレートファイル(product_list.html)の作成
myapp内に templates/myapp とディレクトリーを作って、product_list.html を作成します。

```myapp/templates/myapp/product_list.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>製品一覧</title>
</head>
<body>
    <form method="get">
        {% csrf_token %}
        {{ filter.form.as_p }}
        <input type="submit" />
    </form>
    {% for obj in filter.qs %}
        {{ obj.name }} - {{ obj.price }} {{ obj.release_date }}<br />
    {% endfor %}
</html>
```
ここで、views.py の render 第３引数で渡された辞書型の filter オブジェクトの各項目を表示させていきます。なお、 filter.`form` とか filter.`qs` (網掛け部分)は予約語なので、filter.searchform とか filter.queryset とかにしたらダメです。

そして、検索 form 部分の表示ですが、個別に表示させたい場合は、as_p を使用せず、
```
{{ filter.form.name.label }} {{ filter.form.name }}
```
という形にすれば自由にレイアウトできます。

## おわりに
そして、いつものように、
```
python manage.py runserver
```
を動かして、http://127.0.0.1:8000/にアクセスすると
![django-filter1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362730/9bfb5746-e916-1426-2beb-1c93562ca31f.jpeg)
検索フォーム付きで製品一覧が表示できました。パチパチ！
![django-filter2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/362730/b69469d4-34d3-8c3d-964a-21b5196ecda8.jpeg)
ちゃんと絞り込みもできますね。

これで基本が押さえられたと思います。
次に（その２）では、クラスビューの場合を報告したいと思います。
[django-filter の使い方（その２、クラスビュー編）](https://qiita.com/uqKen/items/fa2e3d5f35a6ad0efe3a)