قدمنا في المقالات السابقة من هذه السلسلة العديد من المفاهيم الجديدة في جانغو، وسنوضح في هذا المقال كيفية تفاعل الموجه URL Dispatcher والنماذج Models والعروض Views والقوالب Templates معًا في تطبيق مدونة في جانغو.
لن ننشئ في هذا المقال مدونة كاملة الميزات مع الفئات Categories والوسوم Tags وما إلى ذلك، بل سنكتفي فقط بإنشاء صفحة تعرض مقالًا منشورًا، وصفحة رئيسية تعرض قائمةً بجميع المقالات، وصفحة لإنشاء وتعديل وحذف المنشورات.
تصميم بنية قاعدة البيانات
لنبدأ أولًا بطبقة النموذج Model التي سنصمم فيها بنية قاعدة البيانات، لذا انتقل إلى الملف blog/models.py
وأنشئ نموذج Post
جديد، وضع فيه الشيفرة البرمجية التالية:
from django.db import models class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField()
يحتوي نموذج المنشور Post
على حقلين فقط هما، العنوان title
من النوع CharField
بحد أقصى 100 محرف، والمحتوى content
من النوع TextField
.
لتطبيق هذه التغييرات على قاعدة البيانات علينا توليد ملفات التهجير Migration المقابلة باستخدام الأمر التالي:
python manage.py makemigrations
ثم تطبيق عمليات التهجير باستخدام الأمر التالي:
python manage.py migrate
عمليات CRUD
حان الوقت الآن لنتعمق في التطبيق نفسه، فمن غير المحتمل أن ننشئ جميع المتحكمات Controllers أولًا ثم نصمم القوالب ثم نتقل إلى الموجّهات Routers عند إنشاء تطبيقات واقعية، بل يجب أن نفكر من وجهة نظر المستخدم وفي الإجراءات التي قد يرغب في اتخاذها. يجب أن يمتلك المستخدم القدرة على إجراء أربع عمليات لكل مورد، والذي هو Post
في حالتنا، وهذه العمليات هي:
- الإنشاء Create لإدراج بيانات جديدة في قاعدة البيانات
- القراءة Read لاسترداد البيانات من قاعدة البيانات
- التحديث Update لتعديل البيانات الموجودة مسبقًا في قاعدة البيانات
- الحذف Delete لإزالة البيانات من قاعدة البيانات
ويشار إلى هذه العمليات مع بعضها البعض باسم عمليات CRUD.
إجراء الإنشاء Create
لنبدأ أولًا بعملية الإنشاء، فلا تزال قاعدة البيانات فارغة، لذا يجب على المستخدم إنشاء منشور جديد، ولكنك تحتاج إلى موجّه إرسال عناوين URL لتوجيه نمط عنوان URL الذي هو /post/create/
إلى دالة العرض View Function وهي post_create()
لإكمال هذا الإجراء.
تحتاج دالة العرض post_create()
إلى التمييز بين نوع الطلب الوارد إلى السيرفر لذا يجب أن تحتوي على عنصر تحكم في التدفق كتعليمة if
لتميز فيما إذا كان تابع الطلب هو GET
، عندها ستعيد دالة العرض قالبًا يحتوي على استمارة HTML، لتسمح للمستخدم بتمرير المعلومات إلى الواجهة الخلفية، أما إرسال الاستمارة Form فيجب أن يكون ضمن طلب POST
. لذا إذا كان تابع الطلب هو POST
، فيجب إنشاء مورد Post
جديد وحفظه.
إليك مراجعةً مختصرة لتوابع HTTP في حال احتياجك إلى تجديد بعض المعلومات:
-
تابع
GET
هو تابع طلبات HTTP الأكثر استخدامًا، ويُستخدم لطلب البيانات والموارد من الخادم -
يُستخدم تابع
POST
لإرسال البيانات إلى الخادم، ويستعمل عادة لإنشاء أو تحديث المورد -
يعمل تابع
HEAD
مثل تابعGET
تمامًا، باستثناء أن استجابة HTTP تحتوي على الترويسة فقط دون الجسم، ويستخدم المطورون هذا التابع لأغراض تنقيح الأخطاء Debugging -
تابع
PUT
مشابه لتابعPOST
، مع اختلاف واحد بسيط، فإذا أرسلت مورد باستخدام التابعPOST
وكان المورد موجودًا مسبقًا على الخادم، فلن يسبب هذا الإجراء أي فرق على الخادم، أما التابعPUT
فسيكرر تحديث هذا المورد بالبيانات المرسلة في كل مرة تجري فيها الطلب. -
يزيل تابع
DELETE
موردًا من الخادم.
لنبدأ بموجّه إرسال عناوين URL، لذا انتقل إلى الملف djangoBlog/urls.py
وضع فيه ما يلي:
from django.urls import path from blog import views urlpatterns = [ path("post/create/", views.post_create, name="create"), ]
ستحتاج بعد ذلك إلى دالة العرض post_create()،
لذا انتقل إلى الملف blog/views.py وضع فيه الشيفرة البرمجية التالية:
from django.shortcuts import redirect, render from .models import Post def post_create(request): if request.method == "GET": return render(request, "post/create.html") elif request.method == "POST": post = Post(title=request.POST["title"], content=request.POST["content"]) post.save() return redirect("home")
تفحص الدالة post_create()
أولًا تابع طلب HTTP، فإذا كان تابع GET
، فيجب إعادة القالب create.html
، وإذا كان POST
فيجب استخدام المعلومات التي يمرّرها طلب POST لإنشاء نسخة POST
جديدة، ثم إعادة التوجيه إلى الصفحة الرئيسية التي سننشئها في الخطوة التالية.
سننشئ القالب create.html
، ولكن يجب إنشاء القالب templates/layout.html
أولًا كما يلي:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.tailwindcss.com"></script> {% block title %}{% endblock %} </head> <body class="container mx-auto font-serif"> <div class="bg-white text-black font-serif"> <div id="nav"> <nav class="flex flex-row justify-between h-16 items-center shadow-md"> <div class="px-5 text-2xl"> <a href="/"> My Blog </a> </div> <div class="hidden lg:flex content-between space-x-10 px-10 text-lg"> <a href="{% url 'create' %}" class="hover:underline hover:underline-offset-1">New Post</a> <a href="https://github.com/ericnanhu" class="hover:underline hover:underline-offset-1">GitHub</a> </div> </nav> </div> {% block content %}{% endblock %} <footer class="bg-gray-700 text-white"> <div class="flex justify-center items-center sm:justify-between flex-wrap lg:max-w-screen-2xl mx-auto px-4 sm:px-8 py-10"> <p class="font-serif text-center mb-3 sm:mb-0">Copyright © <a href="https://www.ericsdevblog.com/" class="hover:underline">Eric Hu</a></p> <div class="flex justify-center space-x-4"> . . . </div> </div> </footer> </div> </body> </html>
لاحظ {% url 'create' %}
في السطر 22، فهذه هي الطريقة التي يمكنك بها عكس عناوين URL اعتمادًا على أسمائها، حيث يتطابق الاسم create
مع الاسم الذي أعطيته لموجّه الإرسال post/create/
. أضفنا أيضًا إطار عمل TailwindCSS عبر شبكة CDN في السطر 8 لجعل هذه الصفحة تبدو أفضل، ولكن يجب ألّا تفعل ذلك في بيئة الإنتاج.
لننشئ بعد ذلك القالب templates/post/create.html
، إذ يتوجب علينا إنشاء المجلد post
له لتوضيح أن هذا القالب مخصص لإنشاء منشور:
{% extends 'layout.html' %} {% block title %} <title>Create</title> {% endblock %} {% block content %} <div class="w-96 mx-auto my-8"> <h2 class="text-2xl font-semibold underline mb-4">Create new post</h2> <form action="{% url 'create' %}" method="POST"> {% csrf_token %} <label for="title">Title:</label><br> <input type="text" id="title" name="title" class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"><br> <br> <label for="content">Content:</label><br> <textarea type="text" id="content" name="content" rows="15" class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"></textarea><br> <br> <button type="submit" class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Submit</button> </form> </div> {% endblock %}
يحدّد السطر 10 الإجراء الذي ستتخذه هذه الاستمارة عند إرسالها، وتابع الطلب الذي ستستخدمه، ويضيف السطر 11 حماية من هجمات CSRF إلى الاستمارة لأغراض أمنية. لاحظ أيضًا الحقل <input>
في السطرين 13 و 14، فالسمة Attribute التي هي name
في هذا الحقل مهمة جدًا، حيث سيُربَط إدخال المستخدم بهذه السمة عند إرسال الاستمارة، ويمكنك بعد ذلك استرداد هذا الإدخال في دالة العرض كما يلي:
title=request.POST["title"]
وينطبق الأمر نفسه على العنصر <textarea>
في السطرين 17 و 18، ويجب أن يكون الزر من النوع type="submit"
ليعمل بنجاح.
إجراء القائمة List
لننشئ الآن صفحة رئيسية لعرض قائمة بجميع المنشورات، حيث سنبدأ بعنوان URL لهذه الصفحة في الملف djangoBlog/urls.py
كما يلي:
path("", views.post_list, name="home"),
ثم ننتقل إلى دالة العرض في الملف blog/views.py
ونضع فيه ما يلي:
def post_list(request): posts = Post.objects.all() return render(request, "post/list.html", {"posts": posts})
وسيتضمن القالب list.html
المحتويات التالية:
{% extends 'layout.html' %} {% block title %} <title>My Blog</title> {% endblock %} {% block content %} <div class="max-w-screen-lg mx-auto my-8"> {% for post in posts %} <h2 class="text-2xl font-semibold underline mb-2"><a href="{% url 'show' post.pk %}">{{ post.title }}</a></h2> <p class="mb-4">{{ post.content | truncatewords:50 }}</p> {% endfor %} </div> {% endblock %}
تتكرر التعليمة {% for post in posts %}
على جميع المنشورات posts
، ويُسنَد كل عنصر إلى المتغير post
، وتمرّر التعليمة {% url 'show' post.pk %}
المفتاح الرئيسي Primary Key للمنشور post
إلى موجّه إرسال عنوان URL للصفحة show
التي سننشئها لاحقًا. تستخدم التعليمة {{ post.content | truncatewords:50 }}
المرشّح truncatewords
لاقتطاع المحتوى بحيث يحتوي على أول 50 كلمة.
إجراء العرض Show
يجب أن يعرض إجراء العرض محتوى منشور معين، مما يعني أن عنوان URL الخاص به يجب أن يحتوي على شيء فريد يسمح لجانغو بتحديد نسخة واحدة من Post
فقط، ويكون هذا الشيء الفريد هو المفتاح الرئيسي Primary Key، لذا ضع ما يلي في الملف djangoBlog/urls.py
:
path("post/<int:id>", views.post_show, name="show"),
سيُسنَد العدد الصحيح الذي يلي post/
إلى المتغير id
، ويُمرّر إلى دالة العرض في الملف blog/views.py
كما يلي:
def post_show(request, id): post = Post.objects.get(pk=id) return render(request, "post/show.html", {"post": post})
وسيتضمن القالب المقابل templates/post/show.html
المحتويات التالية:
{% extends 'layout.html' %} {% block title %} <title>{{ post.title }}</title> {% endblock %} {% block content %} <div class="max-w-screen-lg mx-auto my-8"> <h2 class="text-2xl font-semibold underline mb-2">{{ post.title }}</h2> <p class="mb-4">{{ post.content }}</p> <a href="{% url 'update' post.pk %}" class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Update</a> </div> {% endblock %}
إجراء التحديث Update
ضع ما يلي في الملف djangoBlog/urls.py
لتحديد موجّه إرسال عنوان URL لإجراء التحديث:
path("post/update/<int:id>", views.post_update, name="update"),
وتكون دالة العرض كما يلي في الملف blog/views.py
:
def post_update(request, id): if request.method == "GET": post = Post.objects.get(pk=id) return render(request, "post/update.html", {"post": post}) elif request.method == "POST": post = Post.objects.update_or_create( pk=id, defaults={ "title": request.POST["title"], "content": request.POST["content"], }, ) return redirect("home")
ملاحظة: أضيف التابع update_or_create()
حديثًا إلى الإصدار 4.1 من جانغو.
وسيتضمن القالب المقابل templates/post/update.html
المحتويات التالية:
{% extends 'layout.html' %} {% block title %} <title>Update</title> {% endblock %} {% block content %} <div class="w-96 mx-auto my-8"> <h2 class="text-2xl font-semibold underline mb-4">Update post</h2> <form action="{% url 'update' post.pk %}" method="POST"> {% csrf_token %} <label for="title">Title:</label><br> <input type="text" id="title" name="title" value="{{ post.title }}" class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"><br> <br> <label for="content">Content:</label><br> <textarea type="text" id="content" name="content" rows="15" class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300">{{ post.content }}</textarea><br> <br> <div class="grid grid-cols-2 gap-x-2"> <button type="submit" class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Submit</button> <a href="{% url 'delete' post.pk %}" class="font-sans text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Delete</a> </div> </form> </div> {% endblock %}
إجراء الحذف Delete
ضع ما يلي في الملف djangoBlog/urls.py
لتحديد موجّه إرسال عنوان URL لإجراء الحذف:
path("post/delete/<int:id>", views.post_delete, name="delete"),
وتكون دالة العرض كما يلي في الملف blog/views.py
:
def post_delete(request, id): post = Post.objects.get(pk=id) post.delete() return redirect("home")
لا يتطلب هذا الإجراء قالبًا، لأنه يعيد توجيهك إلى الصفحة الرئيسية بعد اكتمال الإجراء.
بدء تشغيل الخادم
لنبدأ الآن بتشغيل خادم التطوير ونرى النتيجة كما يلي:
python manage.py runserver
ستكون الصفحة الرئيسية للمدونة كما يلي:
وتكون صفحة إنشاء منشور جديد كما يلي:
وتكون صفحة عرض المنشور كما يلي:
وتكون صفحة تحديث المنشور كما يلي:
الخلاصة
بهذا نكون قد أنهينا العمل على تطبيق مدونتنا البسيطة باستخدام Django وأنشأنا نموذج لتمثيل المنشورات في قاعدة البيانات، وتعلمنا كيف ننفيذ عمليات عبر توابع HTTP مثل GET و POST و PUT و DELETE. كما شرحنا خطوات تصميم واجهة المستخدم باستخدام القوالب لعرض المنشورات وإنشاء منشورات جديدة وتحديثها وحذفها. تابع المقال التالي من السلسلة للتعرف على خطوات إكمال المدونة.
ترجمة -وبتصرّف- للمقال Django for Beginners #3 - The CRUD Operations لصاحبه Eric Hu.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.