ایجاد یک برنامه To-do با HTMX و Django، قسمت 9: جستجوی فعال

خوش آمدید! در بخش 9، یک ویژگی دیگر را اضافه خواهیم کرد که نشان میدهد چگونه HTMX میتواند تجربه وب غنی رایج را با کد بسیار کم ارائه دهد: جستجوی فعال.
نتیجه ارسال به این صورت خواهد بود POST
درخواست می کند /tasks/search
و نتایج را در لیست عوض کنید.
اضافه کردن URL و نمایش
ما به یک URL جدید نیاز داریم، /tasks/search
، که الف دریافت خواهد کرد POST
درخواست با یک پارامتر فراخوانی شده است query
.
در core/urls.py
مسیر را اضافه می کنیم
# core/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("tasks/", views.tasks, name="tasks"),
path("tasks//toggle/ ", views.toggle_todo, name="toggle_todo"),
path("tasks// ", views.task_details, name="task_details"),
path("tasks//edit/ ", views.edit_task, name="edit_task"),
path("tasks/search/", views.search, name="search"), # <-- NEW
]
در core/views.py
روش جدید را اضافه می کنیم
# core/views.py
... previous code
@login_required
@require_http_methods(["POST"])
def search(request):
query = request.POST.get("query")
if not query:
return redirect("tasks")
results = request.user.todos.filter(title__icontains=query).order_by("-created_at")
return render(
request,
"tasks.html#todo-items-partial",
{"todos": results},
status=HTTPStatus.OK,
)
اولین نکته جالب رسیدگی به پرس و جوهای خالی است. هنگامی که کاربر فیلد جستجو را پاک می کند، می توانیم به سادگی به آن تغییر مسیر دهیم tasks
view، که تمام موارد انجام کار را برمی گرداند و رفتار اسکرول نامحدود را حفظ می کند.
هنگامی که پرس و جو خالی نیست، موارد انجام کار را با فیلتر می کنیم icontains
، که به حروف کوچک و بزرگ حساس نیست و موارد منطبق را برمی گرداند.
یک بار دیگر از الگوی جزئی خود استفاده می کنیم، todo-items-partial
، که لیستی از کارهای انجام شده را می پذیرد.
افزودن ورودی جستجو در قالب
در tasks.html
قالب یک ورودی جدید در بالای صفحه اضافه می کنیم و آن را به url/view جدید خود وصل می کنیم
..previous code
class="w-full max-w-2xl">
The code to fire the search url on change, with debouncing, is straight from the examples section of the official HTMX site.
It's nice how self-explanatory the hx-trigger
attribute reads: whenever the input is changed, with a delay (debounce) of 500 milliseconds, or when the ENTER key is pressed.
hx-target
informs that we want to place the results in the #todo-items
element (the ul
), and since we want to change its children, we don't need to specify hx-swap
, its default value is what we want already, innerHTML
.
While we're checking the code working,let's have a look in developer tools/network, to see the requests being fired.

Testing the view
Let's add some tests to our new view, in test_view_tasks.py
The first test is our "happy path", we want to ensure we just return the items searched, which are two of the three fake items we have in the test. The other two test for no matches and empty searches.
@pytest.mark.django_db
def test_search_filtering(client, make_todo, make_user):
user = make_user()
client.force_login(user)
make_todo(title="Todo 1", user=user)
make_todo(title="Another Todo", user=user)
make_todo(title="Something else", user=user)
response = client.post(reverse("search"), {"query": "Todo"})
content = response.content.decode()
assert "Todo 1" in content
assert "Another Todo" in content
assert "Something else" not in content
@pytest.mark.django_db
def test_search_zero_matches_returns_empty_list(client, make_todo, make_user):
user = make_user()
client.force_login(user)
make_todo(title="Todo 1", user=user)
make_todo(title="Another Todo", user=user)
make_todo(title="Something else", user=user)
response = client.post(reverse("search"), {"query": "Nonexistent"})
content = response.content.decode()
assert not any(
todo in content for todo in ["Todo 1", "Another Todo", "Something else"]
)
@pytest.mark.django_db
def test_search_empty_query_redirects_to_all_tasks(client, make_todo, make_user):
user = make_user()
client.force_login(user)
make_todo(title="Todo 1", user=user)
make_todo(title="Another Todo", user=user)
make_todo(title="Something else", user=user)
response = client.post(reverse("search"), {"query": ""})
assert response.status_code == HTTPStatus.FOUND # redirect
assert response.url == reverse("tasks")
انجام همه آزمایشها برای اطمینان از اینکه همه چیز خوب است
❯ uv run pytest
Test session starts (platform: darwin, Python 3.12.8, pytest 8.3.4, pytest-sugar 1.0.0)
django: version: 5.1.4, settings: todomx.settings (from ini)
configfile: pyproject.toml
plugins: sugar-1.0.0, django-4.9.0
collected 10 items
core/tests/test_todo_model.py ✓ 10% █
core/tests/test_view_tasks.py ✓✓✓✓✓✓✓✓✓ 100% ██████████
Results (0.53s):
10 passed
یادداشتی در مورد آزمایش زمینه و الگوها
ممکن است کسی که تجربه بیشتری در آزمایش الگوهای جنگو داشته باشد تعجب کند که چرا ما آن را آزمایش می کنیم محتوا از الگوی رندر شده، و نه زمینه تصویب می شود، که قاطعانه تر است، به این ترتیب:
...
response = client.post(reverse("search"), {"query": "Todo"})
context = response.context
assert {todo.title for todo in context["todos"]} == {"Todo 1", "Another Todo"}
این به دلیل یک مشکل فعلی در django-template-partials، شماره 54 است که هنوز برطرف نشده است (من آن را انجام می دهم، اما بی اهمیت نیست..)
با توجه به این مسئله، پاسخ حاوی مقادیری برای context
و templates
.
به هر حال، این برای جستجوی فعال است! در مجموع، کد زیادی برای بهبود فوق العاده در UX وجود ندارد. طبق معمول نسخه نهایی این کد را می توانید در شعبه آن https://github.com/rodbv/todo-mx/tree/part-9 پیدا کنید. به سلامتی
در ادامه بخوانید

شناسایی و حل جلسات مسدود شده در پایگاه داده اوراکل
آرویند تورپو –

تصاویر Docker خود را با Trivy ایمن کنید: راهنمای گام به گام
آرون کومار –

پیاده سازی آپلود تصویر در React Quill
کاربردهای اساسی –

Lettuce – یک مشتری جاوا Redis
سعید اولانو –