اذهب إلى المحتوى

قدمنا في المقالات السابقة من هذه السلسلة العديد من المفاهيم الجديدة في جانغو، وسنوضح في هذا المقال كيفية تفاعل الموجه 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

ستكون الصفحة الرئيسية للمدونة كما يلي:

الصفحة الرئيسية للمدونة

وتكون صفحة إنشاء منشور جديد كما يلي:

02 create

وتكون صفحة عرض المنشور كما يلي:

صفحة عرض المنشور

وتكون صفحة تحديث المنشور كما يلي:

04_update.png

الخلاصة

بهذا نكون قد أنهينا العمل على تطبيق مدونتنا البسيطة باستخدام Django وأنشأنا نموذج لتمثيل المنشورات في قاعدة البيانات، وتعلمنا كيف ننفيذ عمليات عبر توابع HTTP مثل GET و POST و PUT و DELETE. كما شرحنا خطوات تصميم واجهة المستخدم باستخدام القوالب لعرض المنشورات وإنشاء منشورات جديدة وتحديثها وحذفها. تابع المقال التالي من السلسلة للتعرف على خطوات إكمال المدونة.

ترجمة -وبتصرّف- للمقال Django for Beginners #3 - The CRUD Operations لصاحبه Eric Hu.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...