跳到主要內容

[Django教學18]善用Django Filter來快速建構多條件的查詢功能

django_filter
Photo by Nathana Rebouças on Unsplash
在數據爆炸的時代裡,許多的網站、社群平台及資訊系統等,擁有了大量的資料,為了要精準的找到符合所需的資料來進行分析、處理或存檔,這時候就非常需要仰賴查詢的功能,提供適當的條件來讓使用者進行資料篩選。

使用PythonDjango框架來進行網站開發,想要提供這種多條件的查詢功能,則可以使用django-filter套件,它是一個能夠基於資料模型欄位,動態依照條件來進行資料集的篩選,除了能夠簡化在檢視函式(View Function)中撰寫大量的查詢語法,也提升了開發效率。

所以本文將利用django-filter套件,來建置多條件的電影查詢功能,來示範它的使用方式。其中的實作包含:
  • 安裝django-filter
  • 建立資料模型(Models)
  • 建立過濾器(Filters)
  • 建立檢視函式(Views)
  • 建立樣板(Templates)
  • 客製化過濾器

一、安裝django-filter

首先,利用以下的指令來安裝django-filter
$ pip install django-filter
安裝成功後,開啟Django專案中的settings.py檔案,在INSTALL_APPS的地方,加入django-filters,如下範例:
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'movies.apps.MoviesConfig',
    'django_filters',
]

二、建立資料模型(Models)

本文所使用的資料模型,如下範例:
from django.db import models

class Movie(models.Model):
    GENRE_CHOICES = (
        ('action', '動作片'),
        ('romance', '愛情片'),
        ('drama', '劇情片'),
    )
    title = models.CharField(max_length=100)  # 電影名稱
    genre = models.CharField(max_length=20, choices=GENRE_CHOICES)  # 電影類型
    release_year = models.IntegerField()  # 年份
分別包含了電影名稱、類型及上映年份,其中電影類型的選項(GENRE_CHOICES),前方為下拉選單存進資料庫中的值(value),而後方為顯示的文字(text)

接下來,利用以下指令執行Django Migrations(資料遷移):
$ python manage.py makemigrations
$ python manage.py migrate
資料模型同步完成後,開啟Django專案的admin.py,將此資料模型加入至Django Administration(管理員後台),如下範例:
from django.contrib import admin
from .models import Movie


class MovieAdmin(admin.ModelAdmin):
    list_display = ('title', 'genre', 'release_year')


admin.site.register(Movie, MovieAdmin)
接著,利用以下指令建立Django Administration(管理員後台)的帳號密碼:
$ python manage.py createsuperuser
這時候,就可以啟動本地端伺服器,登入Django Administration(管理員後台),在剛剛所建立的Movie資料模型中,新增幾部電影資料,方便等一下進行多條件的查詢測試,如下圖:
django_filter

三、建立過濾器(Filters)

Django資料模型的部分建置完成,在應用程式(APP)的資料夾下新增filters.py檔案,用來建置查詢資料的django-filter過濾器,如下範例:
from .models import Movie
import django_filters


class MovieFilter(django_filters.FilterSet):

    class Meta:
        model = Movie
        fields = '__all__'
範例中,引用所要查詢的資料模型及django-filters,接著,自訂過濾器類別(MovieFilter),並且繼承自FilterSet類別,其中,再利用modelfields屬性來分別指定所要查詢的資料模型和欄位。

四、建立檢視函式(Views)

django-filter建立完成後,開啟Django專案中的views.py檔案,撰寫以下的程式碼:
from django.shortcuts import render
from .models import Movie
from .filters import MovieFilter


def index(request):
    movies = Movie.objects.all()

    movieFilter = MovieFilter(queryset=movies)

    if request.method == "POST":
        movieFilter = MovieFilter(request.POST, queryset=movies)

    context = {
        'movieFilter': movieFilter
    }

    return render(request, 'movies/index.html', context)
在範例的檢視函式(View Function)中,當第一次載入網頁時,由於請求方法為GET,所以在第9行利用queryset關鍵字參數(Keyword Argument),來指定django-filter物件所要查詢資料集,這邊需指定資料模型中的所有資料。

而當請求方法為POST,也就是使用者輸入查詢條件,按下查詢按鈕時,django-filter物件將依據請求所傳入的查詢條件(request.POST),來自動篩選資料集。

五、建立樣板(Templates)

接著,建立剛剛所指定的index.html樣板,其中包含上方的查詢條件表單及下方的查詢結果,如下範例:
{% extends 'base.html' %}

{% block content %}

<form method='POST'>
    {% csrf_token %}
    名稱:{{ movieFilter.form.title }}
    類型:{{ movieFilter.form.genre }}
    年份:{{ movieFilter.form.release_year }}
    <input type="submit" class="btn btn-primary" value="查詢" />
</form>
<br />
<table class="table">
    <thead>
        <th>名稱</th>
        <th>類型</th>
        <th>年份</th>
    </thead>
    <tbody>
        {% for movie in movieFilter.qs %}
        <tr>
            <td>{{ movie.title }}</td>
            <td>{{ movie.get_genre_display }}</td>
            <td>{{ movie.release_year }}</td>
        </tr>
        {% endfor %}
    </tbody>
</table>
{% endblock  %}
執行結果
django_filter
在上方查詢條件表單的部分,可以透過django-filter物件來建立,非常的方便。而下方的查詢結果,在透過迴圈讀取django-filter的資料時,需加上「.qs」,就是queryset資料集的意思

這邊針對下方查詢結果的「類型」欄位來特別說明一下,由於在資料庫中是儲存類型的值(value),也就是"action"、"romance""drama",而當Django讀取出來要進行顯示時,則需使用像第23行的get_<欄位名稱>_display方式,來顯示相應的文字(text)

六、客製化過濾器

到這邊事實上已經完成了Django多條件查詢的功能,但是各位可能還有兩個疑問:
  1. 目前所執行的查詢必須都要條件完全符合才查得到,如果「電影名稱」想要進行模糊查詢,該如何實作?
  2. 查詢條件的表單該如何套用Bootstrap樣式來進行美化?
這時候,就可以開啟Django專案中所建立的filters.py,來進行客製化,如下範例:
from django import forms
from .models import Movie
import django_filters


class MovieFilter(django_filters.FilterSet):

    title = django_filters.CharFilter(
        lookup_expr='icontains',
        widget=forms.TextInput(attrs={'class': 'form-control'}))

    genre = django_filters.CharFilter(
        widget=forms.Select(choices=(('', '請選擇'),) + Movie.GENRE_CHOICES, attrs={'class': 'form-control'}))

    release_year = django_filters.CharFilter(
        widget=forms.NumberInput(attrs={'class': 'form-control'}))

    class Meta:
        model = Movie
        fields = '__all__'
執行結果
django_filter
首先,「電影名稱」欄位想要進行模糊查詢,則可以像第9行一樣,指定django-filterlookup_expr(查詢表示式)為「icontains」,當然,可想而知預設為「iexact」,也就是完全符合的意思。

另外,查詢條件的表單想要套用Bootstrap框架的樣式,就需要先引用Djangoforms物件,之後則可以仿照建置Django Forms一樣,利用widget參數來套用CSS樣式。

七、小結

以上就是利用django-filter套件,來快速建構多個條件的查詢功能,除了提升開發效率外,使用的方式Django ModelForm(資料模型表單)大同小異,很好上手,相較於傳統的方式來進行開發,簡化了檢視函式(View Function)中撰寫複雜的查詢語法及判斷式。詳細的實作程式碼,可以參考下方的GitHub網址唷。

如果您喜歡我的文章,請幫我按五下Like(使用GoogleFacebook帳號免費註冊),支持我創作教學文章,回饋由LikeCoin基金會出資,完全不會花到錢,感謝大家。









留言

  1. 謝謝前輩 我會運用努力來支持您的苦心,謝謝對後輩學習者的貢獻,有時候卡很久卻沒有相對回報有點難過,謝謝您教學。

    回覆刪除

張貼留言