حان الوقت الآن لإنشاء تطبيق مدونة متكامل باستخدام إطار عمل جانغو Django، فقد تعلمنا في المقال السابق كيف يمكن للنماذج Model والعروض View والقوالب Template أن تعمل معًا لإنشاء تطبيق جانغو لكن هذه العملية صعبة نوعًا ما، إذ يتوجب علينا كتابة 5 إجراءات على الأقل لكل ميزة نريد تحقيقها، وستكون الشيفرة البرمجية مكررةً في معظمها.
لذا سنستخدم في هذا المقال واحدة من أفضل ميزات جانغو، وهي لوحة المدير Admin Panel المُضمَّنة، فما عليك سوى كتابة إجراء العرض لمعظم الميزات التي ترغب في إنشائها لتطبيقك، وسيتولى جانغو الباقي نيابة عنك تلقائيًا.
إنشاء طبقة النموذج Model
سنبدأ أولًا بتصميم بنية قاعدة البيانات.
تصميم بنية قاعدة البيانات
بالنسبة لأيّ نظام تدوين أساسي، ستحتاج إلى 4 نماذج على الأقل هي User
و Category
و Tag
و Post
، وسنضيف في المقال التالي بعض الميزات المتقدمة لاحقًا، ولكن سنكتفي حاليًا بهذه النماذج الأربعة فقط.
نموذج المستخدم User
المفتاح | نوعه | معلومات عنه |
---|---|---|
المعرّف id | عدد صحيح Integer | يتزايد تلقائيًا |
الاسم name | سلسلة نصية String | - |
البريد الإلكتروني email | سلسلة نصية | فريد |
كلمة السر password | سلسلة نصية | - |
إن النموذج User
مُضمَّن مسبقًا في جانغو، إذ يوفّر هذا النموذج المُضمَّن بعض الميزات الأساسية مثل تشفير كلمات المرور Password Hashing واستثياق المستخدمين User Authentication، بالإضافة إلى نظام الأذونات المُضمَّن مع مدير جانغو Django Admin كما سنوضّح لاحقًا.
نموذج الفئة Category
المفتاح | نوعه | معلومات عنه |
---|---|---|
المعرّف id | عدد صحيح Integer | يتزايد تلقائيًا |
الاسم name | سلسلة نصية String | - |
الاسم المختصر slug | سلسلة نصية | فريد |
الوصف description | نص | - |
نموذج الوسم Tag
المفتاح | نوعه | معلومات عنه |
---|---|---|
المعرّف id | عدد صحيح Integer | يتزايد تلقائيًا |
الاسم name | سلسلة نصية String | - |
الاسم المختصر slug | سلسلة نصية | فريد |
الوصف description | نص | - |
نموذج المنشور Post
المفتاح | نوعه | معلومات عنه |
---|---|---|
المعرّف id | عدد صحيح Integer | يتزايد تلقائيًا |
العنوان title | سلسلة نصية String | - |
الاسم المختصر slug | سلسلة نصية | فريد |
المحتوى content | نص | - |
featured_image | سلسلة نصية String | - |
is_published | قيمة منطقية Boolean | - |
is_featured | قيمة منطقية | - |
created_at | تاريخ Date | - |
نموذج الموقع Site
ستحتاج أيضًا إلى جدول آخر لتخزين المعلومات الأساسية للموقع بالكامل مثل الاسم والوصف والشعار.
المفتاح | نوعه | معلومات عنه |
---|---|---|
الاسم name | سلسلة نصية String | - |
الوصف description | نص text | - |
الشعار logo | سلسلة نصية | - |
العلاقات
توجد ست علاقات بالنسبة لهذا التطبيق وهي:
- لكل مستخدم عدة منشورات
- لكل فئة عدة منشورات
- ينتمي كل وسم إلى منشورات متعددة
- ينتمي كل منشور إلى مستخدم واحد
- ينتمي كل منشور إلى فئة واحدة
- ينتمي كل منشور إلى وسوم متعددة
تطبيق التصميم باستخدام جانغو
الآن، بعد أن انتهينا من تصميم هذا النموذج، سنبدأ في تطبيقه كما سنوضح في الخطوات التالية
نموذج الموقع Site
لنبدأ بنموذج الموقع Site
مع توفير خصائص لحفظ بيانات الموقع مثل اسم الموقع ووصفه وصورة الشعار كما يلي:
class Site(models.Model): name = models.CharField(max_length=200) description = models.TextField() logo = models.ImageField(upload_to="logo/") class Meta: verbose_name_plural = "site" def __str__(self): return self.name
لاحظ أن نوع حقل الصورة ImageField()
هو سلسلة نصية string
، إذ لا يمكن لقواعد البيانات تخزين الصور، لذا تُخزَّن الصور في نظام ملفات خادمك، وسيحتفظ هذا الحقل بالمسار الذي يشير لموقع الصورة. سنرفع الصور في هذا المثال إلى المجلد mediafiles/logo/
، وتذكّر إضافة مجلد ملفات الوسائط MEDIA_ROOT = "mediafiles/"
في الملف settings.py
الذي أعددناه سابقًا.
ملاحظة: تحتاج لأن تثبّت مكتبة Pillow على جهازك ليعمل الحقل ImageField()
كما يلي:
pip install Pillow
نموذج الفئة Category
بعدها سنصميم نموذج الفئة Category الذي سيُستخدم لتنظيم المحتويات ضمن المدونة ضمن فئات ويحدد الحقول التي سنحتاج إليها لتمثيل الفئات بشكل صحيح في قاعدة البيانات. يتضمن نموذج الفئة Category
المحتويات التالية:
class Category(models.Model): name = models.CharField(max_length=200) slug = models.SlugField(unique=True) description = models.TextField() class Meta: verbose_name_plural = "categories" def __str__(self): return self.name
ينبغي أن يكون النموذج Category
مفهومًا بالنسبة لك، لكن دعنا نوضّح الصنف Meta
، الذي سنستخدمه لإضافة البيانات الوصفية Metadata إلى النماذج. تمثّل البيانات الوصفية للنموذج أيّ شيء ليس حقلًا مثل خيارات الترتيب واسم جدول قاعدة البيانات وما إلى ذلك، حيث استخدمنا في مثالنا الخيار verbose_name_plural
لتحديد صيغة الجمع لكلمة "category"، فجانغو ليس ذكيًا مثل لارافيل Laravel في هذا الجانب، فإن لم نمنح جانغو صيغة الجمع الصحيحة، فسوف يستخدم الجمع "categorys"، وهذا خاطئ.
تحدد الدالة __str__(self)
الحقل الذي سيستخدمه جانغو عند الإشارة إلى فئة معينة، حيث استخدمنا في مثالنا الحقل name
، وسنوضّح أهمية ذلك عندما نصل إلى قسم مدير جانغو.
نموذج الوسم Tag
يتضمن نموذج الوسم Tag
لتخزين الوسوم التي سترتبط بالمنشورات وهو يتضمن اسم الوسم والرابط اللطيف له ووصفه كما توضح الشيفرة التالية:
class Tag(models.Model): name = models.CharField(max_length=200) slug = models.SlugField(unique=True) description = models.TextField() def __str__(self): return self.name
نموذج المنشور Post
لنعرف أخيرًا نموذج المنشور Post
الذي يتضمن اسم المنشور والرابط اللطيف والمحتوى الرئيسي والصورة البارزة للمنشور وتاريخ إنشائه وحقلين منطقيين يوضحان هل نشر أم لا وهل هو منشور مميز أما لا كما توضح الشيفرة التالية:
from ckeditor.fields import RichTextField . . . class Post(models.Model): title = models.CharField(max_length=200) slug = models.SlugField(unique=True) content = RichTextField() featured_image = models.ImageField(upload_to="images/") is_published = models.BooleanField(default=False) is_featured = models.BooleanField(default=False) created_at = models.DateField(auto_now=True) # تعريف العلاقات category = models.ForeignKey(Category, on_delete=models.CASCADE) tag = models.ManyToManyField(Tag) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) def __str__(self): return self.title
لاحظ في السطر 1 أنه إذا نسختَ هذه الشيفرة البرمجية ولصقتها لديك، فسيخبرك محرّر نصوصك بعدم العثور على RichTextField
و ckeditor
، لأنها حزمة خارجية ولا تُضمَّن في إطار عمل جانغو.
تذكّر أنه يمكنك فقط إضافة نص عادي عند إنشاء منشور في المقال السابق، وهذا ليس مثاليًا لمقال في مدونة. إذ تمنحك محرّرات النصوص الغنية أو محرّرات WYSIWYG القدرة على تحرير صفحات HTML مباشرةً دون كتابة الشيفرة البرمجية، حيث استخدمنا المحرّر CKEditor في هذا المقال.
لذا يمكنك تثبيت المحرّر CKEditor من خلال تشغيل الأمر التالي:
pip install django-ckeditor
سجّل بعد ذلك ckeditor
في ملف الإعدادات settings.py
كما يلي:
INSTALLED_APPS = [ "blog", "ckeditor", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
تعريف العلاقات
أخيرًا، يمكنك إضافة علاقات إلى النماذج، حيث سنضيف الأسطر الثلاثة التالية في النموذج Post
للقيام بذلك كما يلي:
category = models.ForeignKey(Category, on_delete=models.CASCADE) tag = models.ManyToManyField(Tag) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
بما أننا نستخدم نموذج المستخدم User
المُضمَّن (settings.AUTH_USER_MODEL
)، فتذكر استيراد الوحدة settings
كما يلي:
from django.conf import settings
ولِّد بعد ذلك ملفات التهجير Migration Files وطبّقها على قاعدة البيانات باستخدام الأمرين التاليين:
python manage.py makemigrations
python manage.py migrate
إعداد لوحة مدير المدونة
الخطوة التالية هي إعداد لوحة المدير، حيث يأتي جانغو مع نظام إدارة مضمَّن معه، ويمكنك استخدامه من خلال التسجيل بمستخدم Superuser باستخدام الأمر التالي:
python manage.py createsuperuser
يمكنك بعد ذلك الوصول إلى لوحة المدير من خلال الانتقال إلى العنوان http://127.0.0.1:8000/admin/
.
لا تزال لوحة المدير فارغة حاليًا، ولا يوجد بها سوى تبويب الاستيثاق Authentication الذي يمكنك استخدامه لإسناد أدوار مختلفة للمستخدمين، وهذا أمر معقد إلى حد ما ويتطلب مقالًا آخر، لذا لن نتحدث عنه الآن، بل سنركز على كيفية ربط تطبيق المدونة blog
بنظام المدير، حيث يجب أن تجد ملفًا بالاسم admin.py
ضمن التطبيق blog
، لذا أضف إليه الشيفرة البرمجية التالية:
from django.contrib import admin from .models import Site, Category, Tag, Post # سجّل نماذجك هنا class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} class TagAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} class PostAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("title",)} admin.site.register(Site) admin.site.register(Category, CategoryAdmin) admin.site.register(Tag, TagAdmin) admin.site.register(Post, PostAdmin)
نستورد في السطر 2 النماذج التي أنشأناها، ثم نسجل النماذج المستوردة باستخدام التابع admin.site.register()
. لاحظ بعد ذلك وجود شيء إضافي عند تسجيل النموذج Category
، وهو الصنف CategoryAdmin
الذي عرّفناه في السطر 6، وبالتالي يمكنك تمرير بعض المعلومات الإضافية إلى نظام الإدارة في جانغو. يمكنك استخدام الميزة prepopulated_fields
لتوليد أسماء لطيفة Slugs لجميع الفئات والوسوم والمنشورات، حيث تعتمد قيمة slug
على الاسم name
، ولنختبر ذلك من خلال إنشاء فئة جديدة.
انتقل إلى العنوان http://127.0.0.1:8000/admin/
، وانقر على الفئات Categories، وأضف فئة جديدة. تذكّر أننا حددنا صيغة الجمع للكلمة Category في نموذجنا، فإن لم نفعل ذلك، فسيستخدم جانغو الجمع Categorys بدلًا من ذلك كما شرحنا سابقًا.
لاحظ توليد الاسم اللطيف تلقائيًا عند كتابة الاسم. حاول الآن إضافة بعض البيانات التجريبية، يجب أن يعمل كل شيء بنجاح.
عمليات ضبط اختيارية Optional Configurations
لم ينتهي عملنا بعد، لذا افتح لوحة الفئات، ستلاحظ أن بإمكانك الوصول إلى الفئات من صفحة المنشور، ولكن لا توجد طريقة للوصول إلى المنشورات المقابلة من صفحة الفئة. لحل هذه المشكلة، يمكنك استخدام الصنف InlineModelAdmin
في الملف blog/admin.py
كما يلي:
class PostInlineCategory(admin.StackedInline): model = Post max_num = 2 class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} inlines = [ PostInlineCategory ]
أنشأنا أولًا الصنف PostInlineCategory
، ثم استخدمناه في CategoryAdmin
، وتعني عبارة max_num = 2
عرض منشورين فقط في صفحة الفئة التي ستبدو كما يلي:
يمكنك بعد ذلك تطبيق الشيء نفسه مع TagAdmin
في الملف blog/admin.py
:
class PostInlineTag(admin.TabularInline): model = Post.tag.through max_num = 5 class TagAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)} inlines = [ PostInlineTag ]
لاحظ أن هذه الشيفرة البرمجية مشابهة جدًا لما سبق، ولكن المتغير model
ليس Post
فقط، بل هو Post.tag.through
، لأن العلاقة بين المنشور Post
والوسم Tag
هي علاقة متعدد إلى متعدد Many-to-Many، وستكون النتيجة النهائية كما يلي:
بناء طبقة العروض View
ركزنا في الأقسام السابقة على الواجهة الخلفية ولوحة مدير المدونة، وسنركز الآن على جزء الواجهة الأمامية، وهو الجزء الذي يمكن للمستخدمين رؤيته، حيث سنبدأ بدوال العرض View Functions.
بما أننا أعددنا لوحة المدير لمدونتنا، فلن نحتاج إلى إنشاء عمليات CRUD الكاملة، وبالتالي وسنهتم فقط بكيفية استرداد المعلومات من قاعدة البيانات. سنحتاج إلى أربع صفحات هي: الصفحة الرئيسية وصفحة الفئة وصفحة الوسم وصفحة المنشور، وسنحتاج إلى دالة عرض واحدة لكلٍّ منها.
عرض الصفحة الرئيسية home
ضع المحتويات التالية في الملف blog/views.py
:
from .models import Site, Category, Tag, Post def home(request): site = Site.objects.first() posts = Post.objects.all().filter(is_published=True) categories = Category.objects.all() tags = Tag.objects.all() return render(request, 'home.html', { 'site': site, 'posts': posts, 'categories': categories, 'tags': tags, })
نستورد في السطر 1 النماذج التي أنشأناها في المقال السابق. يحتوي الموقع في السطر 4 على المعلومات الأساسية لموقعنا، حيث نسترد دائمًا السجل الأول من قاعدة البيانات. يضمن التابع filter(is_published=True)
في السطر 5 عرض المقالات المنشورة فقط.
بعد ذلك علينا تجهيز موجّه إرسال Dispatcher لعنوان URL المقابل لهذه الصفحة في الملف djangoBlog/urls.py
كما يلي:
path('', views.home, name='home'),
عرض الفئة category
ضع أيضًا المحتويات التالية في الملف blog/views.py
:
def category(request, slug): site = Site.objects.first() posts = Post.objects.filter(category__slug=slug).filter(is_published=True) requested_category = Category.objects.get(slug=slug) categories = Category.objects.all() tags = Tag.objects.all() return render(request, 'category.html', { 'site': site, 'posts': posts, 'category': requested_category, 'categories': categories, 'tags': tags, })
وتذكر تجهيز موجّه إرسال عنوان URL المقابل لهذه الصفحة في الملف djangoBlog/urls.py
كما يلي:
path('category/<slug:slug>', views.category, name='category'),
مرّرنا متغيرًا إضافيًا هو slug
من عنوان URL إلى دالة العرض، واستخدمنا هذا المتغير في السطرين 3 و 4 من الشيفرة البرمجية السابقة للعثور على الفئة والمنشورات الصحيحة.
عرض الوسم tag
ضع أيضًا المحتويات التالية في الملف blog/views.py
:
def tag(request, slug): site = Site.objects.first() posts = Post.objects.filter(tag__slug=slug).filter(is_published=True) requested_tag = Tag.objects.get(slug=slug) categories = Category.objects.all() tags = Tag.objects.all() return render(request, 'tag.html', { 'site': site, 'posts': posts, 'tag': requested_tag, 'categories': categories, 'tags': tags, })
تذكر تجهيز موجّه إرسال عنوان URL المقابل لهذه الصفحة في الملف djangoBlog/urls.py
كما يلي:
path('tag/<slug:slug>', views.tag, name='tag'),
عرض المنشور post
ضع أيضًا المحتويات التالية في الملف blog/views.py
:
def post(request, slug): site = Site.objects.first() requested_post = Post.objects.get(slug=slug) categories = Category.objects.all() tags = Tag.objects.all() return render(request, 'post.html', { 'site': site, 'post': requested_post, 'categories': categories, 'tags': tags, })
وتذكر تجهيز موجّه إرسال عنوان URL المقابل لهذه الصفحة في الملف djangoBlog/urls.py
كما يلي:
path('post/<slug:slug>', views.post, name='post'),
إنشاء طبقة القوالب Template
يمكنك استخدام قالب جاهز، فنحن في هذه السلسة لا نركز على لغتي HTML و CSS.
وستكون بنية القالب الذي سنستخدمه كما يلي:
templates ├── category.html ├── home.html ├── layout.html ├── post.html ├── search.html ├── tag.html └── vendor ├── list.html └── sidebar.html
يحتوي الملف layout.html
على ترويسة وتذييل الصفحة، وهو المكان الذي نستورد فيه عادة أكواد التنسيقات CSS وأكواد جافا سكريبت JavaScript. وتوجهنا دوال العرض إلى القوالب home
و category
و tag
و post
، وتتوسّع جميعها إلى القالب layout
الذي يعتبر كأساس لها جميعًا. وتتواجد المكونات التي ستظهر عدة مرات في قوالب مختلفة ضمن المجلد vendor
، ويمكنك استيرادها باستخدام الوسم include
.
قالب التخطيط Layout
ضع المحتويات التالية في الملف 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" /> {% load static %} <link rel="stylesheet" href="{% static 'style.css' %}" /> {% block title %}{% endblock %} </head> <body class="container mx-auto font-serif"> <nav class="flex flex-row justify-between h-16 items-center border-b-2"> <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="https://github.com/ericnanhu" class="hover:underline hover:underline-offset-1" >GitHub</a > <a href="#" class="hover:underline hover:underline-offset-1">Link</a> <a href="#" class="hover:underline hover:underline-offset-1">Link</a> </div> </nav> {% 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> </body> </html>
لاحظ في السطرين 7 و 8 الطريقة التي يمكنك بها استيراد الملفات الساكنة ملفات CSS و جافا سكريبت في جانغو، فكما وضحنا لن نتحدث في هذا المقال عن لغة CSS، ولكن ما يهمنا هو معرفة طريقة استيراد ملفات CSS إضافية لتطبيق جانغو. سيبحث جانغو عن الملفات الساكنة Static Files في مجلدات التطبيق الفردية افتراضيًا، حيث سيذهب إلى المجلد /blog
في تطبيقنا blog
، ويبحث عن المجلد static
، ثم يبحث عن الملف style.css
ضمن المجلد static
كما هو محدد في القالب.
blog ├── admin.py ├── apps.py ├── __init__.py ├── migrations ├── models.py ├── static │ ├── input.css │ └── style.css ├── tests.py └── views.py
قالب الصفحة الرئيسية Home
ضع المحتويات التالية في الملف home.html
:
{% extends 'layout.html' %} {% block title %} <title>Page Title</title> {% endblock %} {% block content %} <div class="grid grid-cols-4 gap-4 py-10"> <div class="col-span-3 grid grid-cols-1"> <!-- المنشور البارز --> <div class="mb-4 ring-1 ring-slate-200 rounded-md hover:shadow-md"> <a href="{% url 'post' featured_post.slug %}" ><img class="float-left mr-4 rounded-l-md object-cover h-full w-1/3" src="{{ featured_post.featured_image.url }}" alt="..." /></a> <div class="my-4 mr-4 grid gap-2"> <div class="text-sm text-gray-500"> {{ featured_post.created_at|date:"F j, o" }} </div> <h2 class="text-lg font-bold">{{ featured_post.title }}</h2> <p class="text-base"> {{ featured_post.content|striptags|truncatewords:80 }} </p> <a class="bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase text-sm font-semibold font-sans w-fit focus:ring" href="{% url 'post' featured_post.slug %}" >Read more →</a > </div> </div> {% include "vendor/list.html" %} </div> {% include "vendor/sidebar.html" %} </div> {% endblock %}
لاحظ أننا فصلنا الشريط الجانبي وقائمة المنشورات ووضعناها في المجلد vendor
بدلًا من كتابة شيفرتها البرمجية الثابتة، إذ سنستخدم المكونات نفسها في صفحة الفئة والوسم.
قائمة المنشورات
ضع أيضًا المحتويات التالية في الملف vendor/list.html
:
<!-- قائمة المنشورات --> <div class="grid grid-cols-3 gap-4"> {% for post in posts %} <!-- المنشور --> <div class="mb-4 ring-1 ring-slate-200 rounded-md h-fit hover:shadow-md"> <a href="{% url 'post' post.slug %}" ><img class="rounded-t-md object-cover h-60 w-full" src="{{ post.featured_image.url }}" alt="..." /></a> <div class="m-4 grid gap-2"> <div class="text-sm text-gray-500"> {{ post.created_at|date:"F j, o" }} </div> <h2 class="text-lg font-bold">{{ post.title }}</h2> <p class="text-base"> {{ post.content|striptags|truncatewords:30 }} </p> <a class="bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase text-sm font-semibold font-sans w-fit focus:ring" href="{% url 'post' post.slug %}" >Read more →</a > </div> </div> {% endfor %} </div>
لاحظ في الأسطر من 3 إلى 27 كيف مرّرنا المتغير posts
من العرض إلى القالب، حيث يحتوي هذا المتغير على مجموعة من المنشورات، وسنقوم بالمرور ضمن القالب على كل عنصر في هذه المجموعة باستخدام حلقة for
.
تذكّر في السطر 6 أننا أنشأنا موجّه إرسال عنوان URL كما يلي:
path('post/<slug:slug>', views.post, name='post'),
ستجد التعليمة {% url 'post' post.slug %}
في القالب موجّهَ إرسال عنوان URL بالاسم 'posts'
، وتسند القيمة post.slug
إلى المتغير <slug:slug>
، والذي سيُمرَّر بعد ذلك إلى دالة العرض المقابلة. ينسّق المرشّح Filter الذي هو date
في السطر 14 بيانات التاريخ المُمرَّرة إلى القالب لأن القيمة الافتراضية ليست سهلة الاستخدام، ويمكنك العثور على تنسيقات تواريخ أخرى في توثيق جانغو الرسمي.
وضعنا مرشحَين بعد post.content
في السطر 18، حيث يزيل المرشّح الأول وسوم HTML، ويأخذ المرشّح الثاني أول 30 كلمة ويقتطع الباقي.
قالب الشريط الجانبي Sidebar
ضع أيضًا المحتويات التالية في الملف vendor/sidebar.html
:
<div class="col-span-1"> <div class="border rounded-md mb-4"> <div class="bg-slate-200 p-4">Search</div> <div class="p-4"> <form action="" method="get"> <input type="text" name="search" id="search" class="border rounded-md w-44 focus:ring p-2" placeholder="Search something..."> <button type="submit" class="bg-blue-500 hover:bg-blue-700 rounded-md p-2 text-white uppercase font-semibold font-sans w-fit focus:ring">Search</button> </form> </div> </div> <div class="border rounded-md mb-4"> <div class="bg-slate-200 p-4">Categories</div> <div class="p-4"> <ul class="list-none list-inside"> {% for category in categories %} <li> <a href="{% url 'category' category.slug %}" class="text-blue-500 hover:underline" >{{ category.name }}</a > </li> {% endfor %} </ul> </div> </div> <div class="border rounded-md mb-4"> <div class="bg-slate-200 p-4">Tags</div> <div class="p-4"> {% for tag in tags %} <span class="mr-2" ><a href="{% url 'tag' tag.slug %}" class="text-blue-500 hover:underline" >{{ tag.name }}</a ></span > {% endfor %} </div> </div> <div class="border rounded-md mb-4"> <div class="bg-slate-200 p-4">More Card</div> <div class="p-4"> <p> . . . </p> </div> </div> </div>
قالب الفئة Category
ضع المحتويات التالية في الملف category.html
:
{% extends 'layout.html' %} {% block title %} <title>Page Title</title> {% endblock %} {% block content %} <div class="grid grid-cols-4 gap-4 py-10"> <div class="col-span-3 grid grid-cols-1"> {% include "vendor/list.html" %} </div> {% include "vendor/sidebar.html" %} </div> {% endblock %}
قالب الوسم Tag
ضع المحتويات التالية في الملف tag.html
:
{% extends 'layout.html' %} {% block title %} <title>Page Title</title> {% endblock %} {% block content %} <div class="grid grid-cols-4 gap-4 py-10"> <div class="col-span-3 grid grid-cols-1"> {% include "vendor/list.html" %} </div> {% include "vendor/sidebar.html" %} </div> {% endblock %}
قالب المنشور Post
أخيرًا، يتضمن قالب المنشور المحتويات التالية:
{% extends 'layout.html' %} {% block title %} <title>Page Title</title> {% endblock %} {% block content %} <div class="grid grid-cols-4 gap-4 py-10"> <div class="col-span-3"> <img class="rounded-md object-cover h-96 w-full" src="{{ post.featured_image.url }}" alt="..." /> <h2 class="mt-5 mb-2 text-center text-2xl font-bold">{{ post.title }}</h2> <p class="mb-5 text-center text-sm text-slate-500 italic">By {{ post.user|capfirst }} | {{ post.created_at }}</p> <div>{{ post.content|safe }}</div> <div class="my-5"> {% for tag in post.tag.all %} <a href="{% url 'tag' tag.slug %}" class="text-blue-500 hover:underline" mr-3">#{{ tag.name }}</a> {% endfor %} </div> </div> {% include "vendor/sidebar.html" %} </div> {% endblock %}
لاحظ في السطر 19 أننا أضفنا المرشح safe
، لأن جانغو سيعرض شيفرة HTML كنص عادي افتراضيًا لأسباب أمنية، لذا يجب ان نخبر جانغو بأنه يمكن عرض شيفرات HTML كما هي.
أصبح كل شيءجاهزًا، كل ما عليك الأن هو تشغيل خادم التطوير باستخدام الأمر التالي، وعرض تطبيق جانغو الأول الخاص بك:
python manage.py runserver
ترجمة -وبتصرّف- للمقال Django for Beginners #4 - The Blog App لصاحبه Eric Hu.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.