matobaの備忘録

和歌山と東京を往復しつつ活動するエンジニアの記録

django-filterを軽く動かしてみる(Django編)

django-filterというものが視界に入ってきた。 使ったことがなかったので、とりあえず動かしてみることにしました。 これは動かしてみた記録。

前提

とりあえずdjango-filterはdjangoのライブラリなので、djangoプロジェクトは最初に用意しておくものとします。

基本はドキュメントを見て動かしつつ、メモをとっていく形なので、良い子はドキュメントを見てください。

django-filter.readthedocs.io

あと、ドキュメントのまま動かすとちょいちょい動かないので、修正してます。

インストール

ドキュメントに書いてあるけど、インストールは pip install django-filter をした後に、 INSTALLED_APPSに追加すれば使えます。

INSTALLED_APPS = [
    ...
    'django_filters',
]

どうでもいい話ですが、ライブラリ名は django-filter なのにパッケージ名は django_filters で末尾にsがあります。

Djangoで使う

django-filterはDjangoで使う場合とDRFで使う場合があります。

まずは、Djangoで使ってみます。基本的にドキュメントに書いてあるものを、実際に動く形に修正しつつ動かしたものです。

とりあえず動かす

とりあえず動かしてみます。 まずmodels.pyに以下を設置します。

from django.db import models

class Manufacturer(models.Model):
    """メーカー"""
    name = models.CharField(max_length=255)
    description = models.TextField()

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)

次に、以下のfilterを作ります。詳細は後で見ます。

import django_filters

from myapp.models import Product

class ProductFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr="iexact")

    class Meta:
        model = Product
        fields = ["price", "release_date"]

次にviewsをセットします。

from django.shortcuts import render
from myapp.filters import ProductFilter
from myapp.models import Product

def product_list(request):
    f = ProductFilter(request.GET, queryset=Product.objects.all())
    return render(request, "myapp/product_list.html", {"filter": f})

templateは以下を置きます。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <form method="get">
      {{ filter.form.as_p }}
      <input type="submit" />
  </form>
  <li>
  {% for obj in filter.qs %}
    <ul>{{ obj.name }} - ${{ obj.price }}</ul>
  {% endfor %}
  </li>
</body>
</html>

テストデータを入れたいのでadminも作って、テストデータを入れます。

from django.contrib import admin

from myapp.models import Product, Manufacturer

admin.site.register(Product)
admin.site.register(Manufacturer)

でrunserverを起動してブラウザから画面を見るとフォームが見れます。そのまま、検索すると全データが表示されます。 URLにクエリがついてるのが見えます。フォームに値を入れると絞り込めます。

f:id:mtb_beta:20210807001712p:plain

ProductFiter によって、検索フォームが生成されています。 そして、そのフォームから入力したデータをviewで受け取って、クエリをフィルタリングしています。

フィルターを変更する

今のところ、ドキュメントに書いてあったサンプルフィルターをそのまま使いました。

次のような検索フィルターが作りたいと考えます

  • name検索は、部分一致
  • priceは、範囲指定

多分、指定できるだろう、と思いながら、django-filterのドキュメントを確認し、次のようにFilterを編集します。

import django_filters

from myapp.models import Product

class ProductFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr="contains")
    price = django_filters.RangeFilter()

    class Meta:
        model = Product
        fields = ["release_date"]

以下のような感じで、いい感じにフィルタを追加できました。 f:id:mtb_beta:20210807003445p:plain

コマンドラインで動かす

大抵、実際に動かすときはコマンドラインから叩いて動作確認をするので、ここでもやっておきます。

とりあえずインポートして、準備します。データは5件入ってます

>>> from myapp.filters import ProductFilter
>>> from myapp.models import Product
>>> Product.objects.all().count()
5
>>> Product.objects.all()
<QuerySet [<Product: 製品1>, <Product: 製品2>, <Product: 製品3>, <Product: プロトタイプ1>, <Product: プロトタイプ2>]>

この状態で、Filterを使ってみます。データが空でもエラーにはなりません。

>>> data = {}
>>> products = Product.objects.all()
>>> filter = ProductFilter(data, products)
>>> filter.qs
<QuerySet [<Product: 製品1>, <Product: 製品2>, <Product: 製品3>, <Product: プロトタイプ1>, <Product: プロトタイプ2>]>

nameで絞り込んでみると、絞り込めます。

>>> data = {"name": "製品"}
>>> filter = ProductFilter(data, products)
>>> filter.qs
<QuerySet [<Product: 製品1>, <Product: 製品2>, <Product: 製品3>]>

nameで絞り込んでみると、絞り込めます。

>>> data = {"name": "製品"}
>>> filter = ProductFilter(data, products)
>>> filter.qs
<QuerySet [<Product: 製品1>, <Product: 製品2>, <Product: 製品3>]>

複数条件も設定できました。

>>> data = {"name": "製品", "price_min": 150}
>>> filter = ProductFilter(data, products)
>>> filter.qs
<QuerySet [<Product: 製品2>, <Product: 製品3>]>

ちなみに関係のないフィルターをセットしてもエラーは出ません。

>>> data = {"name": "製品", "price_min": 150, "hogehoge": 1}
>>> filter = ProductFilter(data, products)
>>> filter.qs
<QuerySet [<Product: 製品2>, <Product: 製品3>]>

DRFで動かす

... やりたかったのですが、今回はなしで...(疲れた)

おまけ

些細な話ですが、django-filterのドキュメントで少し古い記載を見つけたのでPRを出してみました。

Updated documentation on the on_delete option by mtb-beta · Pull Request #1405 · carltongibson/django-filter · GitHub