Search เรียกได้ว่าเป็นฟีเจอร์ที่ต้องมีในแทบจะทุก ๆ เว็บไซต์หรือแอป ในบทความ Django Search นี้ แน่นอนว่าเราจะมาประยุกต์ใช้งาน Search เข้ากับ Django project ของเรากันครับ โดยจะใช้ bootstrap form ในส่วน navbar สำหรับ UI ในครั้งนี้
หน้า search ที่เราคุ้นเคยกันดีอย่างเช่น Google Search ก็ถือว่าเป็น search ที่ทุก ๆ คนแทบจะคุ้นเคยกันดีที่สุด เช่นเราใช้มันเข้า stackoverflow ในทุก ๆ วัน นี่ก็คือ search แต่ทาง Google ก็ค่อนข้างที่จะมีอัลกอริทึมที่ค่อนข้างซับซ้อน ซึ่งเราจะไม่ขอพูดถึงในบริบทนี้ จะเป็นการยกตัวอย่างการ search เท่านั้น
Google คือ web ที่ใช้สำหรับ search ที่เราคุ้นเคยกันดี
หน้า search ของ Django official website (Cr Photo: stackpython.medium.com)
จากที่ยกตัวอย่างมาในเบื้องต้นด้านบน จะเห็นได้ว่าเว็บส่วนใหญ่ก็มีฟีเจอร์สำหรับค้นหากันแทบทุกเว็บ จะง่ายหรือซับซ้อนก็ขึ้นอยู่กับ business domain ของเว็บไซต์นั้น ๆ
Django Project directories and files paths
mysite/ __init__.py settings.py urls.py wsgi.py asgi.py blog/ templates/ blog/ home.html search.html ... __init__.py admin.py models.py views.py urls.py tests.py apps.py db.slite3 venv/ manage.py
blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
def __str__(self):
return self.title
จากนั้นก็ทำการ makemigrations และ migrate ตามปกติ โดยแน่นอนว่าบทความนี้เราจะไม่ได้สอนทำโปรเจคท์ตั้งแต่เริ่มต้นเพราะว่าทุกคนต้องมีพื้นฐานตรงนั้นมาก่อนนั่นเอง แต่ถ้าอยากทบทวนหรือเอาไว้ใช้อ้างอิง ก็สามารถเริ่มต้นสร้างโปรเจคท์ได้ที่บทความด้านล่างนี้ครับ
หรือจะเป็นคอร์สเรียน Django แบบจัดเต็ม
ในหน้า Django Admin และสมมติว่าตอนนี้ในตาราง Post ของเรามีข้อมูลดังนี้
โพสต์ในหน้า Django Admin
เข้าหน้า Shell เพื่อทดสอบ search
$ python manage.py shell
จะเข้าสู่หน้า Django Shell และทำการอิมพอร์ตสองส่วนคือตาราง Post ใน models.py ของเราและก็ตัว Q objects ซึ่งเป็น complex lookup ที่จะเข้ามาช่วยให้การ implement การ search ง่ายและสะดวกมากยิ่งขึ้น เข้ามาใช้งานใน Shell อ่านเพิ่มเติมสำหรับ Q objects
(InteractiveConsole)
>>> from blog.models import Post
>>> from django.db.models import Q
>>> post = Post.objects.filter(Q(title__icontains="django"))
>>> post
<QuerySet [<Post: Django 1>, <Post: Django 2>]>
>>> post2 = Post.objects.filter(Q(title__icontains="numpy"))
>>> post2
<QuerySet []>
>>> post3 = Post.objects.filter(Q(title__contains="django"))
>>> post3
<QuerySet []>
>>> post4 = Post.objects.filter(Q(title__icontains="python"))
>>> post4
<QuerySet [<Post: Basic Python>]>
>>> exit()
เมื่อทดสอบเรียบร้อยและสามารถแสดงได้ใน Shell แล้ว ก็แสดงว่าเราสามารถดึงข้อมูลโดยใช้ Q objects ในการช่วย Search filter ได้ โดยเราเปลี่ยนจากการ hardcode เข้าไปเช่นพิมพ์ django, python, numpy หรือคำอะไรก็ตามที่เราต้องการค้นหา ซึ่งวางไว้ด้านหลัง icontains โดยเปลี่ยนเป็นการเก็บเป็นตัวแปรและส่งเข้ามาแทนการ hardcode ซึ่งตัวแปรเราก็จะไปเขียนเพื่อรอรับค่าที่ส่งเข้ามาจากฝั่ง client ผ่าน GET เมธอด ซึ่งจะพูดถึงในส่วนถัดไป
Note
ในโปรเจคท์นี้จะมีการสร้างเพียงแค่ 2 ฟังก์ชัน คือ home และ search จากนั้นทำการ render หน้า HTML ออกไปแสดงผลตามปกติ
blog/views.py
from django.shortcuts import render from .models import Post def home(request): return render(request, 'blog/home.html') def search(request): return render(request, 'blog/search.html)
blog/urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.home, name="home"), path('search', views.search, name="search") ]
mysite/urls.py
from django.contrib import admin
from django.urls import path, include # New
urlpatterns = [
path('', include('blog.urls')), # New
path('admin/', admin.site.urls),
]
บทความนี้จะใช้ Search form ของ Bootstrap ที่มีมาให้ใน Navbar เรียบร้อย โดยสิ่งที่ต้องมีคือ Bootstrap CSS CDN และ Bootstrap Navbar ให้ทำการก็อปปี้และนำมาวางในหน้า base.html ซึ่งเป็น parent file ที่เราจะใช้สืบทอดเท็มเพลต (Template Inheritance)
base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> {% block title %} {% endblock %} <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="{% url 'home' %}">STACKPYTHON</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a> </li> <li class="nav-item"> <a class="nav-link" href="#">Link</a> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Dropdown </a> <div class="dropdown-menu" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="#">Action</a> <a class="dropdown-item" href="#">Another action</a> <div class="dropdown-divider"></div> <a class="dropdown-item" href="#">Something else here</a> </div> </li> <li class="nav-item"> <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a> </li> </ul> <form class="form-inline my-2 my-lg-0" action="{% url 'search' %}"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" name="q"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> {% block content %} {% endblock %} </body> </html>
ทำการสืบทอดจาก base.html เข้าไปใน home.html และ search.html สำหรับหน้า home นั้นก็แสดงผลเพียงแค่คำสั่ง <h1>Hello, this is a homepage</h1> เพียงเท่านั้น ไม่ได้มีการดึงข้อมูลอะไรมาแสดงผล เพราะบทความนี้จะทดสอบทำการ search เท่านั้น
home.html
{% extends 'blog/base.html' %}
{% block title %}
<title>Home</title>
{% endblock %}
{% block content %}
<h1>Hello, this is a homepage</h1>
{% endblock %}
สำหรับหน้า search.html ก็แสดงผลเพียงคำสั่ง <h1>Hello, Django Search</h1> และมีการ for loop ข้อมูลออกมาแสดงผลไว้รอ (แต่ตอนนี้ยังไม่ได้ส่งค่าหรือรีเทิร์นออกเป็น context ออกมา) โดยใช้ django template tag {% for p in post %} และแสดงผลโดยใช้ Value tag {{ p.title }} โดยให้แสดงออกมาในรูปแบบของ HTML list ในแท็ก <li></li>
search.html
{% extends 'blog/base.html' %} {% block title %} <title>Search</title> {% endblock %} {% block content %} <div class="container"> <h1>Hello, Django Search</h1> {% for p in post %} <li>{{ p.title }}</li> {% endfor %} </div> {% endblock %}
base.html (Search form)
ให้ไปที่ส่วนของ Search ใน Navbar ซึ่งสังเกตง่าย ๆ จะอยู่ในแท็ก <form> และให้ทำการเพิ่มแอตทริบิวต์คือ action เข้าไป เพื่อที่จะให้ยิงไปที่ url endpoint ที่ต้องการเมื่อมีการกด submit ตัว Search form ซึ่งแน่นอนว่าในที่นี้จะให้ยิงไปที่ /search ซึ่งในที่นี้จะไม่ hardcode เข้าไปแต่จะใช้ ref name ที่ได้เขียนแทน URL ของ search โดยสามารถเรียกใช้งานได้โดย action="{% url 'search' %}" dfdและในส่วนของ input ที่จะส่งเข้าไปต้องทำการเพิ่มแอตทริบิวต์ที่มีชื่อว่า name เพื่อที่จะส่งเข้าไปใน server เมื่อมีการ search เกิดขึ้น โดยจะส่งเป็น query string หรือ parameter เข้าไปที่มีชื่อว่า q
<form class="form-inline my-2 my-lg-0" action="{% url 'search' %}"> <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" name="q"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form>
โดยไม่ว่าเราจะพิมพ์อะไรเข้าไปเช่น python ซึ่งก็จะได้ q=python ซึ่งตัว q นี้แหละจะเป็นตัวแทนของคำว่า python ที่เราค้นหาและจะถูกส่งไปในฟังก์ชัน search ที่เราเขียนใน views.py ซึ่งแน่นอนว่าเราก็ต้องทำการเขียนลอจิกหรือฟังก์ชันก์เพื่อที่จะรับค่านี้นั้นเองครับ แล้วนำไปประมวลผล ซึ่งการประมวลผลในบริบทนี้คือจะทำการค้นหาคำว่า python ใน database ของเรานั่นเอง ซึ่งแน่นอนว่าจะไปค้นหาในตาราง Post ซึ่งสมมติว่าเราพิมพ์คำว่า django ในช่อง search ฟอร์ม จะได้ URL ดังนี้
http://127.0.0.1:8000/search?q=django
blog/views.py
def search(request): search_post = request.GET.get('q') if search_post: print(search_post) post = Post.objects.filter(Q(title__icontains=search_post)) else: print("Empty") return redirect("/") return render(request, 'blog/search.html', {'post': post})
การทำงานของโค้ดด้านบน
เมื่อทำการพิมพ์เพื่อ search โดยใช้คำค้นหาที่ต้องการ ทดสอบโดยพิมพ์คำว่า "django" เพื่อค้นหาบทความเกี่ยวกับ Django
Console
System check identified no issues (0 silenced). March 21, 2021 - 23:48:27 Django version 3.1.7, using settings 'myWeb2.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. python [21/Mar/2021 23:48:34] "GET /search?search=python HTTP/1.1" 200 2762
Empty [21/Mar/2021 23:52:34] "GET /search?search= HTTP/1.1" 302 0 [21/Mar/2021 23:52:34] "GET / HTTP/1.1" 200 5635
ในหน้าเว็บก็จะแสดงผลรายชื่อบทความเกี่ยวกับ django ในตาราง Post ของ Database โดยแสดงเป็นแบบลิสต์ตามที่ได้เขียนใน HTML <li> tag
Congrats !! ถึงตอนนี้ทุกคนก็สามารถที่จะประยุกต์ใช้งาน search ใน Django project ของเราได้กันแล้วครับ โดยจากรายชื่อบทความ Django 2, Django 1 ด้านบนเราก็สามารถเขียนโค้ดเพิ่มเติมในการลิ้งค์ไปแสดงผลหน้ารายละเอียด (post-details) ของโพสต์นั้น ๆ ได้เลยครับ
References
[ djangoprojects.com ] - Complex lookups with Q objects
[ stackpython.medium.com ] - Django Search with Q objects
กิจกรรมที่กำลังจะมาถึง
ไม่พลาดกิจกรรมเด็ด ๆ ที่น่าสนใจ
Event นี้จะเริ่มขึ้นใน July 5, 2022
รายละเอียดเพิ่มเติม/สมัครเข้าร่วมโปรคอร์สรวมแรกของปี เรียนไพธอนครบครัน หลากหลาย ลดราคาจาก 7,900฿ เหลือ 6,900฿ พร้อมโบนัส รายละเอีดยเพิ่มเติมในลิงก์ ปล. สมาชิกที่เจอลิงก์นี้จากบทความของเรา แคปภาพมา แอดมินลดให้เพิ่มอีก 200 ครับ