المشروع: مسح المهام المنجزة
جزء من قسم واجهات API بصيغة JSON في رحلة Django على Coddy. الدرس 16 من 19.
تتراكم المهام المنجزة، لذا يحتاج المتعقّب إلى طريقة لمسحها دفعةً واحدة. يُطبَّق المسح على مهام كثيرة في آنٍ واحد، وليس على مهمة واحدة، لذا يكون له عنوان URL خاص به، وبما أنه يغيّر البيانات، فإنه يقبل POST فقط: ترسل المتصفحات ومعاينات الروابط ومحركات البحث طلبات GET من دون سؤال أحد، لذا يجب ألا يؤدي طلب GET إلى الحذف أبدًا.
هناك تفصيل جديد. تُرجع delete() في QuerySet زوجًا يتضمن عدد الصفوف المحذوفة وقاموسًا بأعداد الصفوف لكل نموذج:
>>> Book.objects.filter(year__lt=1900).delete()
(2, {'books.Book': 2})أما الباقي فقد أنشأته من قبل: دالة عرض، ومسارها، ومُزيِّنان، واستجابة JSON.
التحدي
صعبأضف نقطة نهاية تمسح كل مهمة منجزة.
- في
tasks/views.py، أضف العرضclear_done. يحذف كل مهمة منجزة ويستجيب بـ{"deleted": 1}، وهو عدد المهام المحذوفة. أي طريقة غيرPOSTتُرجع405، ويعمل هذا دون رمز CSRF. - في
tasks/urls.py، أضف المسار"api/tasks/clear-done/"بالاسمclear_done.
شغّل السطر أدناه في تبويب Shell: سيطبع {'deleted': 1}، لأن «اتصل بالبنك» كانت المهمة المنجزة الوحيدة.
from django.test import Client; print(Client().post("/api/tasks/clear-done/").json())جرّب بنفسك
import json
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_http_methods
from .models import Task
def task_to_dict(task):
return {
"id": task.id,
"title": task.title,
"done": task.done,
"priority": task.priority,
}
def home(request):
return render(request, "tasks/home.html")
def task_page(request):
return render(request, "tasks/list.html", {"tasks": Task.objects.all()})
def task_row(request, pk):
task = get_object_or_404(Task, pk=pk)
return render(request, "tasks/list.html#task-row", {"task": task})
@require_GET
def stats(request):
total = Task.objects.count()
done = Task.objects.filter(done=True).count()
data = {
"total": total,
"done": done,
"open": total - done,
}
return JsonResponse(data)
@csrf_exempt
@require_http_methods(["GET", "POST"])
def task_list(request):
if request.method == "POST":
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "The body is not valid JSON."}, status=400)
errors = {}
title = data.get("title", "").strip()
if not title:
errors["title"] = "This field is required."
priority = data.get("priority", "normal")
if priority not in Task.Priority.values:
errors["priority"] = "Choose low, normal or high."
if errors:
return JsonResponse({"errors": errors}, status=400)
task = Task.objects.create(
title=title,
priority=priority,
)
return JsonResponse(task_to_dict(task), status=201)
tasks = Task.objects.all()
done = request.GET.get("done")
if done == "true":
tasks = tasks.filter(done=True)
elif done == "false":
tasks = tasks.filter(done=False)
priority = request.GET.get("priority")
if priority:
tasks = tasks.filter(priority=priority)
return JsonResponse([task_to_dict(task) for task in tasks], safe=False)
def not_found():
return JsonResponse({"error": "Task not found"}, status=404)
@method_decorator(csrf_exempt, name="dispatch")
class TaskDetail(View):
def get(self, request, pk):
task = Task.objects.filter(pk=pk).first()
if task is None:
return not_found()
return JsonResponse(task_to_dict(task))
def patch(self, request, pk):
task = Task.objects.filter(pk=pk).first()
if task is None:
return not_found()
data = json.loads(request.body)
if "title" in data:
task.title = data["title"]
if "done" in data:
task.done = data["done"]
task.save()
return JsonResponse(task_to_dict(task))
def delete(self, request, pk):
task = Task.objects.filter(pk=pk).first()
if task is None:
return not_found()
task.delete()
return HttpResponse(status=204)
جميع دروس واجهات API بصيغة JSON
تدرّب بنفسك: Playground لـ Django