البحث في الموقع
المحتوى عن 'جانغو للمبتدئين'.
-
حان الوقت الآن لإنشاء تطبيق مدونة متكامل باستخدام إطار عمل جانغو 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. اقرأ أيضًا المقال السابق: استخدام عمليات CRUD لإدارة مدونة في جانغو إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات إنشاء موقع ويب هيكلي بجانغو رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج البدء مع إطار العمل جانغو لإنشاء تطبيق ويب
-
قدمنا في المقالات السابقة من هذه السلسلة العديد من المفاهيم الجديدة في جانغو، وسنوضح في هذا المقال كيفية تفاعل الموجه 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. اقرأ أيضًا المقال السابق: استخدام بنية MTV لإنشاء مدونة بسيطة في جانغو إعداد بيئة تطوير تطبيقات جانغو Django بناء تطبيق مهام باستخدام جانغو Django وريآكت React رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج
-
نشرح في مقال اليوم بنية نموذج-قالب-عرض Model-Template-View أو MTV اختصارًا التي يعتمد عليها إطار عمل جانغو Django لتطوير الويب حيث يكون النموذج Model مسؤولًا عن التفاعل مع قاعدة البيانات، ويجب أن يقابل كل نموذج جدولًا منها، ويعبر القالب Template عن جزء الواجهة الأمامية من التطبيق، وهو الشيء الذي سيراه المستخدم، أما العرض View فهو يتضمن المنطق البرمجي لواجهة التطبيق الخلفية، ومسؤولً عن استرداد البيانات من قاعدة البيانات عبر النماذج ووضعها في العرض المقابل وإعادة القالب المعروض إلى المستخدم في النهاية. هذه المقالة جزء من سلسلة من المقالات تشرح جانغو للمبتدئين على النحو التالي: الجزء الأول: البدء في إنشاء مدونة بسيطة الجزء الثاني: استخدام بنية MTV لإنشاء مدونة بسيطة الجزء الثالث: استخدام عمليات CRUD لإدارة المدونة الجزء الرابع: تطبيق المدونة الكامل الجزء الخامس: إضافة بعض الميزات المتقدمة إلى تطبيق المدونة مفهوم النماذج Models النموذج من أفضل ميزات جانغو Django، ففي أطر عمل الويب الأخرى ستحتاج إلى إنشاء نموذج وملف تهجير Migration، وملف التهجير هو مخطط Schema لقاعدة البيانات، يصف بنية قاعدة البيانات كأسماء الأعمدة وأنواعها، ويوفر النموذج واجهةً تتعامل مع معالجة البيانات بناءً على هذا المخطط، ولكنك ستحتاج إلى نموذج فقط في جانغو، ويمكن توليد ملفات التهجير المقابلة باستخدام أمر بسيط هو python manage.py makemigrations، مما يوفر عليك كثيرًا من الوقت. يحتوي كل تطبيق جانغو على ملف models.py واحد، ويجب تعريف جميع النماذج المرتبطة بالتطبيق بداخله، ويقابل كل نموذج ملف تهجير، والذي يقابل بدوره جدولًا في قاعدة البيانات. يمكنك التمييز بين الجداول الخاصة بالتطبيقات المختلفة من خلال إسناد بادئة لكل جدول تلقائيًا، حيث سيكون لجدول قاعدة البيانات المقابل لتطبيق blog الخاص بنا البادئة blog_. يوضح المثال التالي نموذجًا في الملف blog/models.py: from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) يمكنك بعد ذلك توليد ملف التهجير باستخدام الأمر التالي: python manage.py makemigrations ويجب أن يبدو ملف التهجير الناتج blog/migrations/0001_initial.py كما يلي: # Generated by Django 4.1.2 on 2022-10-19 23:13 from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [] operations = [ migrations.CreateModel( name="Person", fields=[ ( "id", models.BigAutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("first_name", models.CharField(max_length=30)), ("last_name", models.CharField(max_length=30)), ], ), ] يمكنك اعتبار ملف التهجير بمثابة مخطط يوضح كيف يجب أن يبدو جدول قاعدة البيانات، ويمكنك استخدام الأمر التالي لتطبيق هذا المخطط: python manage.py migrate يجب أن تبدو قاعدة بياناتك db.sqlite3 كما يلي: لا تحاول تعديل أو حذف ملفات التهجير إذا كنت مبتدئًا، ودع جانغو يطبّق كل شيء نيابةً عنك إلّا إن كنت تعرف ما تفعله تمامًا. يمكن لجانغو في معظم الحالات اكتشاف التغييرات التي أجريتها في النماذج حتى إذا حذفتَ شيئًا وتوليد ملفات الترحيل وفقًا لذلك. سينشئ النموذج في مثالنا جدول قاعدة بيانات person، وسيحتوي هذا الجدول على ثلاثة أعمدة اسمها id و first_name و last_name، حيث يُنشأ العمود id تلقائيًا كما هو موضح في ملف التهجير، ويُستخدم هذا العمود كمفتاح رئيسي Primary Key للفهرسة Indexing افتراضيًا. يسمَّىCharField() بنوع الحقل ويعرِّف نوع العمود، ويسمَّى max_length بخيار الحقل ويحدّد معلومات إضافية حول هذا العمود. أهم نواع حقول النموذج وخياراتها يوضّح الجدول التالي بعض أنواع الحقول الأكثر استخدامًا وننصحك بالاطلاع على جميع أنواع الحقول وخياراتها من توثيق جانغو الرسمي. نوع الحقل وصف عنه BigAutoField ينشئ عمودًا نوعه عدد صحيح يتزايد تلقائيًا، يُستخدم عادةً مع العمود id. BooleanField ينشئ عمودًا قيمه منطقية True أو False. DateField و DateTimeField يُستخدم لإضافة تواريخ وأوقات كما يوحي اسمه. FileField و ImageField ينشئ عمودًا لتخزين المسار الذي يؤشّر إلى الملف أو الصورة المرفوعة. IntegerField و BigIntegerField تتراوح قيم الأعداد الصحيحة Integer من -2147483648 إلى 2147483647، وتتراوح قيم الأعداد الصحيحة الكبيرة Big Integer من -9223372036854775808 إلى 9223372036854775807 SlugField الاسم المختصر Slug هو نسخة بسيطة من عنوان URL للاسم أو العنوان. CharField و TextField ينشئ كل من CharField و TextField عمودًا لتخزين السلاسل النصية Strings، ولكن يقابل TextField مربع نص أكبر في صفحة مدير جانغو Django Admin التي سنتحدث عنها لاحقًا في هذه السلسلة من المقالات. يوضح الجدول التالي بعض خيارات الحقول الأكثر استخدامًا: خيار الحقل وصف عنه blank يسمح للحقل بأن يحتوي على إدخال فارغ. choices يمنح الحقل خيارات متعددة، حيث سنوضّح ذلك لاحقًا عندما نصل إلى مدير جانغو. default يعطي الحقل قيمةً افتراضية. unique يتأكد من أن كلّ عنصر في العمود فريد، ويُستخدَم عادةً لتحديد الاسم المختصر Slug والحقول الأخرى التي يُفترَض أن تحتوي على قيم فريدة. خيارات الصنف Meta يمكنك أيضًا إضافة الصنف Class الذي هو Meta في صنف النموذج، والذي يحتوي على معلومات إضافية حول هذا النموذج مثل اسم جدول قاعدة البيانات وخيارات الترتيب والأسماء المفردة وجمعها التي يمكن أن يقرأها الإنسان كما يلي: class Category(models.Model): priority = models.IntegerField() class Meta: ordering = ["priority"] verbose_name_plural = "categories" توابع النموذج توابع النموذج هي دوال معرَّفة في صنف النموذج، تسمح بتطبيق إجراءات مخصصة على النسخة الحالية من كائن النموذج كما في المثال التالي: class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() def baby_boomer_status(self): "Returns the person's baby-boomer status." import datetime if self.birth_date < datetime.date(1945, 8, 1): return "Pre-boomer" elif self.birth_date < datetime.date(1965, 1, 1): return "Baby boomer" else: return "Post-boomer" في الكود السابق، يفحص جانغو تاريخ ميلاد الشخص ويعيد حالة تمثل إن كانت فترة ولادته قبل زيادة المواليد التي حدثت بعد الحرب العالمية الثانية Pre-boomer، أم خلالها Baby-Boomer أم بعدها Post-boomer للشخص عند استدعاء التابع baby_boomer_status(). ملاحظة: الكائنات والتوابع والخاصيات مفاهيم مهمة جدًا في لغات البرمجة، لذا ننصح بمطالعة مقال كائنات وأصناف بايثون لمزيد من المعلومات. وراثة النماذج ستحتاج إلى أكثر من نموذج واحد في معظم تطبيقات الويب، وسيكون لبعضها حقول مشتركة، لذا يمكنك إنشاء نموذج أب Parent Model يحتوي على الحقول المشتركة، ثم تجعل النماذج الأخرى ترث هذا النموذج الأب كما يلي: class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True class Student(CommonInfo): home_group = models.CharField(max_length=5) لاحظ أن النموذج CommonInfo نموذج مجرد Abstract، مما يعني أنه لا يقابل نموذجًا فرديًا فعليًا، بل يستخدم كأب لنماذج أخرى. لنولّد الآن ملف تهجير جديد blog/migrations/0002_student.py باستخدام الأمر التالي للتحقق من ذلك: python manage.py makemigrations سيتولّد ملف التهجير التالي: # Generated by Django 4.1.2 on 2022-10-19 23:28 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("blog", "0001_initial"), ] operations = [ migrations.CreateModel( name="Student", fields=[ ( "id", models.BigAutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=100)), ("age", models.PositiveIntegerField()), ("home_group", models.CharField(max_length=5)), ], options={ "abstract": False, }, ), ] لاحظ إنشاء الجدول Student فقط. علاقات قاعدة البيانات تحدثنا عن كيفية إنشاء جداول فردية، ولكن لا تكون هذه الجداول مستقلة تمامًا في معظم التطبيقات، بل توجد علاقات بينها، فمثلًا قد يكون لديك فئة Category تنتمي إليها منشورات متعددة، ويكون كل منشور في المدونة تابع لمستخدم معين وما إلى ذلك، لذا توجد ثلاثة أنواع أساسية للتعبير عن العلاقات بين جداول قاعدة البيانات وهي: علاقة واحد إلى واحد One-to-one وعلاقة متعدد إلى واحد Many-to-one وعلاقة متعدد إلى متعدد Many-to-many وسنشرح تاليًا المزيد حول هذه العلاقات وطريقة وصفها ضمن نماذج جانغو. علاقة واحد إلى واحد علاقة واحد إلى واحد هي العلاقة الأسهل، فمثلًا يمكن أن يكون لكل شخص هاتف واحد، ويمكن أن يعود كل هاتف إلى شخص واحد، حيث يمكننا وصف هذه العلاقة في النماذج كما يلي: class Person(models.Model): name = models.CharField(max_length=100) class Phone(models.Model): person = models.OneToOneField('Person', on_delete=models.CASCADE) يعمل نوع الحقل OneToOneField مثل أي نوع حقل آخر، ولكنه يتطلب وسيطين على الأقل هما: الوسيط الأول هو اسم النموذج الآخر الذي يرتبط به هذا النموذج بعلاقة، والوسيط الثاني هو on_delete الذي يعرّف الإجراء الذي سيتخذه جانغو عند حذف البيانات، ويتعلق هذا المعامل بلغة SQL أكثر من جانغو، لذا لن نتحدث عنه بالتفصيل، ولكن إن كنت مهتمًا، فاطلع على بعض القيم المتاحة للمعامل on_delete. يمكنك الآن إنشاء عمليات تهجير وتطبيقها لهذه النماذج ومعرفة ما يحدث، وإذا واجهتك مشاكل أثناء تشغيل الأوامر التالية، فاحذف الملف db.sqlite3 وملفات التهجير للبدء من جديد: python manage.py makemigrations python manage.py migrate لاحظ أن نوع الحقل OneToOneField أنشأ العمود person_id في الجدول blog_phone، وسيخزّن هذا العمود معرّف id الشخص الذي يمتلك هذا الهاتف. علاقة متعدد إلى واحد يمكن أن تحتوي كل فئة على منشورات متعددة وينتمي كل منشور إلى فئة واحدة مثلًا، حيث يشار إلى هذه العلاقة باسم علاقة متعدد إلى واحد التي نعرّفها كما يلي: class Category(models.Model): name = models.CharField(max_length=100) class Post(models.Model): category = models.ForeignKey('Category', on_delete=models.CASCADE) ينشئ نوع الحقل ForeignKey العمود category_id في الجدول blog_post، والذي يخزن معرّف id الفئة التي ينتمي إليها هذا المنشور. علاقة متعدد إلى متعدد تكون علاقة متعدد إلى متعدد أكثر تعقيدًا بعض الشيء، فمثلًا يمكن أن يكون لكل مقال وسوم Tags متعددة، ويمكن أن يكون لكل وسم مقالات متعددة: class Tag(models.Model): name = models.CharField(max_length=100) class Post(models.Model): tags = models.ManyToManyField('Tag') ستنشئ الشيفرة البرمجية السابقة جدولًا جديدًا بالاسم post_tags بدلًا من إنشاء عمود جديد، وسيحتوي هذا الجدول الجديد على عمودين هما post_id و tag_id، مما يتيح لك تحديد جميع الوسوم المرتبطة بمنشور معين والعكس صحيح. لنفترض أن لدينا الجدول التالي مثلًا لتوضيح الأمور: post_id tag_id 1 1 2 1 3 2 1 2 2 3 يمتلك المنشور الذي له المعرّف id=1 وسمين tags لهما المعرّف id=1 والمعرّف id=2. إذا أردنا عكس الأمور للعثور على المنشورات باستخدام الوسم، فيمكننا أن نرى منشورين لهما المعرّف id=3 والمعرّف id=1 بالنسبة للوسم ذي المعرّف id=2. طبقة العرض View طبقة العرض مكون مهم في تطبيق جانغو ، فهي المكان الذي نكتب فيه المنطق البرمجي للواجهة الخلفية. يسترد العرض البيانات من قاعدة البيانات من خلال النموذج المقابل ويعالج هذه البيانات المستردة ويضعها في الموقع المقابل في القالب ويعرض هذا القالب ويعيده إلى المستخدم. لا تعمل دالة العرض View Function على استرداد البيانات فقط، إذ توجد أربع عمليات أساسية يمكن تطبيقها على البيانات في معظم تطبيقات الويب، وهي الإنشاء Create والقراءة Read والتحديث Update والحذف Delete، ويشار إلى هذه العمليات مجتمعة بالاسم CRUD، والتي سنوضّحها في مقال لاحق. تحدّثنا عن النماذج في القسم السابق، ولكننا ما زلنا لا نعرف كيفية استرداد البيانات أو تخزينها باستخدام النموذج، حيث يقدم جانغو واجهة برمجة تطبيقات API بسيطة لمساعدتنا في ذلك، تسمى QuerySet. لنفترض أن لدينا النموذج blog/models.py التالي: class Category(models.Model): name = models.CharField(max_length=100) class Tag(models.Model): name = models.CharField(max_length=200) class Post(models.Model): title = models.CharField(max_length=255) content = models.TextField() pub_date = models.DateField() category = models.ForeignKey(Category, on_delete=models.CASCADE) tags = models.ManyToManyField(Tag) يمكنك معالجة البيانات بمساعدة QuerySet من خلال هذا النموذج ضمن دوال العرض التي توجد في الملف blog/views.py. إنشاء البيانات وحفظها لنفترض أنك تريد إنشاء فئة جديدة كما يلي: # استيراد نموذج الفئة Category from blog.models import Category # إنشاء نسخة جديدة من النموذج Category category = Category(name="New Category") # حفظ الفئة التي أنشأناها في قاعدة البيانات category.save() يجب أن يكون ما سبق سهلًا إذا كنت على دراية بمفهوم البرمجة كائنية التوجه، حيث أنشأنا في المثال السابق نسخة جديدة من الكائن Category واستخدمنا التابع save() الذي ينتمي إلى هذا الكائن لحفظ المعلومات في قاعدة البيانات. توجد علاقة متعدد إلى واحد بين الفئة والمنشور، وقد عرفناها باستخدام الحقل ForeignKey()، مع افتراض أن لدينا عدد كافي من السجلات في قاعدة البيانات. from blog.models import Category, Post # Post.objects.get(pk=1) هي الطريقة التي نسترجع بها المنشور الذي له المفتاح الرئيسي pk=1، # حيث يرمز pk إلى المفتاح الرئيسي Primary Key الذي يمثّل المعرّف id عادةً إن لم نحدّد خلاف ذلك. post = Post.objects.get(pk=1) # استرداد الفئة التي اسمها "New Category" new_category = Category.objects.get(name="New Category") # إسناد المتغير new_category إلى حقل فئة المنشور وحفظه post.category = new_category post.save() توجد أيضًا علاقة متعدد إلى متعدد بين المنشورات والوسوم كما يلي: from blog.models import Tag, Post post1 = Post.objects.get(pk=1) # استرداد المنشور 1 tag1 = Tag.objects.get(pk=1) # استرداد الوسم 1 tag2 = Tag.objects.get(pk=2) # استرداد الوسم 2 tag3 = Tag.objects.get(pk=3) # استرداد الوسم 3 tag4 = Tag.objects.get(pk=4) # استرداد الوسم 4 tag5 = Tag.objects.get(pk=5) # استرداد الوسم 5 post.tags.add(tag1, tag2, tag3, tag4, tag5) # إضافة الوسوم من 1 إلى 5 إلى المنشور 1 استرداد البيانات استرداد الكائنات أكثر تعقيدًا قليلًا مما رأيناه سابقًا، لذا سنوضّح فيما يلي كيف سنجد سجلًا معينًا في قاعدة بياناتنا التي تحتوي على آلاف السجلات إن لم نكن نعرف المعرّف id، وسنوضّح كيفية الحصول على مجموعة من السجلات التي تناسب معايير معينة بدلًا من الحصول على سجل واحد مثلًا. توابع QuerySet تتيح توابع QuerySet استرداد البيانات بناءً على معايير معينة، ويمكن الوصول إليها باستخدام السمة Attribute التي هي objects، حيث يُستخدَم التابع get() الذي رأيناه سابقًا لاسترداد سجل معين كما يلي: first_tag = Tag.objects.get(pk=1) new_category = Category.objects.get(name="New Category") ويمكن أيضًا استرداد جميع السجلات باستخدام التابع all(): Post.objects.all() يعيد التابع all() مجموعة من السجلات والتي نسميها مجموعة الاستعلام QuerySet، ويمكنك تحسين هذه المجموعة من خلال سَلسَلة التابع filter() أو التابع exclude() مع التابع all() كما يلي: Post.objects.all().filter(pub_date__year=2024) سيؤدي ذلك إلى إعادة جميع المنشورات المنشورة في عام 2024، ويُسمَّى pub_date__year بوسيط البحث في الحقول Field Lookup، وسنناقش هذا الموضوع بالتفصيل لاحقًا. يمكننا أيضًا استبعاد المنشورات المنشورة عام 2024 كما يلي: Post.objects.all().exclude(pub_date__year=2024) هناك أيضًا العديد من توابع QuerySet الأخرى بالإضافة إلى get() و all() و filter() و exclude()، ولكن لن نتحدث عنها جميعًا، لذا اطّلع على القائمة الكاملة لجميع توابع QuerySet من توثيق جانغو الرسمي. وسطاء عمليات البحث في الحقول Field Lookups وسطاء عمليات البحث في الحقول هي وسطاء الكلمات المفتاحية للتوابع get() و filter() و exclude()، والتي تعمل بطريقة مشابهة لتعليمة WHERE في لغة SQL، وتأخذ الصيغة fieldname__lookuptype=value مع وجود شرطة سفلية مزدوجة. Post.objects.all().filter(pub_date__lte='2024-01-01') pub_date هو اسم الحقل و lte هو نوع البحث الذي يرمز إلى أقل من أو يساوي، وستُعاد جميع المنشورات التي يكون فيها تاريخ النشر pub_date أقل من أو يساوي 2024-01-01. يمكن أيضًا استخدام وسطاء عمليات البحث في الحقول للعثور على السجلات التي لها علاقة بالسجل الحالي كما في المثال التالي، وستُعاد جميع المنشورات التي تنتمي إلى الفئة التي اسمها "Django": Post.objects.filter(category__name='Django') يمكننا تطبيق ذلك عكسيًا مثل إعادة جميع الفئات التي تحتوي على منشورٍ واحد على الأقل الذي يحتوي عنوانه على الكلمة "Django" كما يلي: Category.objects.filter(post__title__contains='Django') يمكننا أيضًا المرور عبر علاقات متعددة كما يلي: Category.objects.filter(post__author__name='Admin') يعيد الاستعلام هنا جميع الفئات التي تحتوي على منشورات ينشرها المستخدم Admin، إذ يمكن وضع سلسلة من العلاقات بالعدد الذي نريده. ويمكنك مطالعة جميع وسطاء عمليات البحث في الحقول التي يمكنك استخدامها من توثيق جانغو الرسمي. حذف الكائنات نستخدم التابع delete() لحذف سجلٍ ما، حيث ستحذف الشيفرة البرمجية التالية المنشور الذي له المفتاح الرئيسي pk=1: post = Post.objects.get(pk=1) post.delete() ويمكننا أيضًا استخدامه لحذف سجلات متعددة معًا كما يلي: Post.objects.filter(pub_date__year=2022).delete() سيؤدي ذلك إلى حذف جميع المنشورات في عام 2022، ولكن قد يتعلّق السجل الذي نحذفه بسجل آخر مثل محاولة حذف فئة تحتوي على منشورات متعددة كما يلي: category = Category.objects.get(pk=1) category.delete() يحاكي جانغو سلوك قيد SQL التالي ON DELETE CASCADE، مما يعني حذف جميع المنشورات التي تنتمي إلى هذه الفئة أيضًا، ولكن إذا أردتَ تغيير ذلك، فيمكنك تغيير خيار on_delete إلى شيء آخر، لذا اطّلع على جميع خيارات on_delete المتاحة في توثيق جانغو الرسمي. دالة العرض View Function وضّحنا ما يمكن فعله داخل دالة العرض، وسنوضّح كيف تبدو دالة العرض الكاملة كما في المثال التالي، حيث تُعرَّف جميع العروض ضمن الملف views.py: from django.shortcuts import render from blog.models import Post # أنشئ عروضك الخاصة هنا def my_view(request): posts = Post.objects.all() return render(request, 'blog/index.html', { 'posts': posts, }) هناك شيئان يجب الانتباه إليهما في هذا المثال، أولهما أن دالة العرض تأخذ المتغير request كدخل، وهذا المتغير هو كائن HttpRequest يُمرَّر تلقائيًا إلى العرض من موجّه إرسال Dispatcher عناوين URL. اطّلع على مقال مدخل إلى HTTP لمزيد من المعلومات حول التواصل بين عميل وخادم التطبيق. يحتوي request على الكثير من المعلومات حول طلب HTTP الحالي، فمثلًا يمكننا الوصول إلى تابع طلب HTTP وكتابة شيفرات برمجية مختلفة لتوابع مختلفة كما يلي: if request.method == 'GET': do_something() elif request.method == 'POST': do_something_else() ملاحظة: اطّلع على جميع المعلومات التي يمكنك الوصول إليها من كائن request في توثيق جانغو الرسمي. والشيء الآخر الذي يجب الانتباه إليه هو استيراد اختصار Shortcut اسمه render()، ثم استخدامه لتمرير المتغير posts إلى القالب blog/index.html. يُطلق عليه اختصار لأنه من المفترض تحميل القالب افتراضيًا باستخدام التابع loader() وعرض هذا القالب مع البيانات المُسترَدة وإعادة كائن HttpResponse، ولكن بسّط جانغو هذه العملية باستخدام الاختصار render(). لن نتحدث عن الطريقة المعقدة لذلك، لأننا لن نستخدمها في هذا المقال ويمكنك مطالعة جميع دوال الاختصار Shortcut Functions في توثيق جانغو الرسمي. نظام قوالب جانغو طبقة القوالب Template هي جزء الواجهة الأمامية لتطبيق جانغو، لذا تكون ملفات القالب هي شيفرات مكتوبة بلغة HTML لأنها تمثّل ما تراه في المتصفح، ولكن قد تكون الأمور أكثر تعقيدًا قليلًا، فإن احتوى القالب على شيفرات HTML فقط، فسيكون موقع الويب ساكنًا بالكامل، ولا نريد ذلك، لذا يجب أن يخبر القالبُ دالةَ العرض بمكان وضع البيانات المسترَدة. عمليات الضبط Configurations يجب أولًا تغيير شيء ما في الملف settings.py، إذ يجب أن تخبر جانغو بمكان وضع ملفات القوالب، لذا لننشئ المجلد templates، حيث اخترنا وضعه ضمن المجلد الجذر للمشروع، ولكن يمكنك نقله لمكان آخر. . ├── blog ├── db.sqlite3 ├── djangoBlog ├── env ├── manage.py ├── mediafiles ├── staticfiles └── templates انتقل إلى الملف settings.py وابحث عن TEMPLATES. TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ 'templates', ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] لنعدّل الخيار DIRS الذي يؤشر إلى المجلد templates، ولنتحقق الآن من عمل هذا الإعداد، لذا ننشئ نمط عنوان URL جديد يؤشّر إلى العرض test() في الملف djangoBlog/urls.py كما يلي: from django.urls import path from blog import views urlpatterns = [ path('test/', views.test), ] ولننشئ الآن العرض test() في الملف blog/views.py كما يلي: def test(request): return render(request, 'test.html') انتقل إلى المجلد templates وأنشئ القالب test.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" /> <title>Test Page</title> </head> <body> <p>This is a test page.</p> </body> </html> شغّل خادم التطوير وانتقل إلى العنوان http://127.0.0.1:8000/، وستظهر الصفحة التالية: لغة قوالب جانغو لنناقش الآن محرّك قوالب جانغو بالتفصيل، وتذكّر أنه يمكننا إرسال البيانات من العرض إلى القالب كما يلي: def test(request): return render(request, 'test.html', { 'name': 'Jack' }) تُسنَد السلسلة النصية 'Jack' إلى المتغير name وتمرَّر إلى القالب، ويمكننا عرض المتغير name ضمن القالب باستخدام أقواس معقوصة مزدوجة {{ }} كما يلي: <p>Hello, {{ name }}</p> حدّث المتصفح، وسترى النتيجة التالية: ولكن لا تكون البيانات المُمرَّرة إلى القالب سلسلة نصية بسيطة في أغلب الحالات كما في المثال التالي: def test(request): post = Post.objects.get(pk=1) return render(request, 'test.html', { 'post': post }) المتغير post في المثال السابق هو قاموس Dictionary، حيث يمكنك الوصول إلى العناصر الموجودة في هذا القاموس في القالب كما يلي: {{ post.title }} {{ post.content }} {{ post.pub_date }} المرشحات Filters تحوّل المرشحات قيم المتغيرات، فمثلًا إذا كان لدينا المتغير django الذي قيمته 'the web framework for perfectionists with deadlines' ووضعنا المرشح title مع هذا المتغير كما يلي: {{ django|title }} فسيُحوَّل القالب إلى ما يلي: The Web Framework For Perfectionists With Deadlines اطّلع على جميع المرشحات المُضمَّنة في جانغو من توثيق جانغو الرسمي. الوسوم Tags تضيف الوسوم ميزات لغات البرمجة مثل التحكم في التدفق والحلقات إلى شيفرة HTML، مما يوفر الكثير من الوقت والموارد، إذ لن نضطر إلى كتابة الشيفرة البرمجية نفسها مرارًا وتكرارًا. تُعرَّف جميع الوسوم باستخدام {% %} مثل حلقة for التالية: <ul> {% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %} </ul> وتكون تعليمة if كما يلي: {% if somevar == "x" %} This appears if variable somevar equals the string "x" {% endif %} وتكون تعليمة if-else كما في المثال التالي: {% if athlete_list %} Number of athletes: {{ athlete_list|length }} {% elif athlete_in_locker_room_list %} Athletes should be out of the locker room soon! {% else %} No athletes. {% endif %} اطّلع على جميع الوسوم المُضمَّنة في جانغو من توثيق جانغو الرسمي، حيث توجد الكثير من المرشحات والوسوم المفيدة الأخرى في نظام قوالب جانغو، والتي سنتحدث عنها لاحقًا. نظام الوراثة Inheritance الفائدة الأساسية لاستخدام قوالب جانغو هي أنك لست بحاجة إلى كتابة الشيفرة البرمجية نفسها مرارًا وتكرارًا، فمثلًا يوجد شريط تنقل Navigation Bar وتذييل Footer في جميع صفحات تطبيق الويب النموذجي، ومن الصعب تكرار هذه الشيفرة البرمجية في كل صفحة من صيانة هذا التطبيق، لذا يقدم جانغو طريقة سهلة للغاية لحل هذه المشكلة كما سنوضح فيما يلي. لننشئ الملف layout.html في المجلد templates، ويمثّل هذا الملف المكان الذي نعرّف فيه تخطيط القالب الخاص بنا كما يلي، ولكننا لم نضع الشيفرة البرمجية الخاصة بالتذييل وشريط التنقل لتسهيل القراءة: <!DOCTYPE html> <html> <head> {% block meta %} {% endblock %} <-- استورد شيفرة CSS هنا –!> </head> <body> <div class="container"> <!-- ضع شيفرة شريط التنقل هنا --> {% block content %} {% endblock %} <!-- ضع شيفرة تذييل الصفحة هنا --> </div> </body> </html> لاحظ أننا عرّفنا كتلتين في هذا الملف هما meta و content باستخدام الوسم {% block ... %}، وعرّفنا القالب home.html التالي لاستخدام هذا التخطيط: {% extends 'layout.html' %} {% block meta %} <title>Page Title</title> <meta charset="UTF-8"> <meta name="description" content="Free Web tutorials"> <meta name="keywords" content="HTML, CSS, JavaScript"> <meta name="author" content="John Doe"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> {% endblock %} {% block content %} <p>This is the content section.</p> {% include 'vendor/sidebar.html' %} {% endblock %} سيجد جانغو أولًا الملف layout.html عند استدعاء القالب السابق، ويملأ الكتلتين meta و content بالمعلومات الموجودة في صفحة home.html. لاحظ وجود شيء آخر في هذا القالب، حيث تخبر التعليمة {% include 'vendor/sidebar.html' %} جانغو بالبحث عن القالب templates/vendor/sidebar.html ووضعه في الصفحة، حيث يتضمن القالب sidebar.html المحتويات التالية مثلًا: <p>This is the sidebar.</p> لا يُعَد ذلك شريطًا جانبيًا، ولكن يمكننا استخدامه لتوضيح عمل نظام الوراثة، وتأكد أيضًا من صحة العرض كما يلي: from django.shortcuts import render def home(request): return render(request, 'home.html') وتأكّد من أن موجّه إرسال عنوان URL الخاص بك يؤشّر إلى هذا العرض كما يلي: path('home/', views.home), افتح متصفحك وانتقل إلى العنوان http://127.0.0.1:8000/home، ويجب أن ترى الصفحة التالية: ترجمة -وبتصرّف- للمقال Django for Beginners #2 - The MTV Structure لصاحبه Eric Hu. اقرأ أيضًا المقال السابق: جانغو للمبتدئين الجزء الأول: البدء في إنشاء مدونة بسيطة إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الأول: الأساسيات العروض والقوالب في Django - الجزء الأول العروض والقوالب في Django - الجزء الثاني مدخل إلى إطار عمل الويب جانغو Django
-
جانغو Django هو إطار عمل ويب عالي المستوى مجاني ومفتوح المصدر ومكتوب بلغة البرمجة بايثون Python، ويُستخدَم على نطاق واسع لبناء تطبيقات ويب معقدة، وهو معروف بقدرته على التعامل مع حركة المرور العالية وميزات الأمان الخاصة به ومكوناته القابلة لإعادة الاستخدام. يتبع جانغو نمط معمارية نموذج-عرض-متحكم Model-View-Controller أو MVC اختصارًا ويأتي مع واجهة مُدمَجة لإدارة الموقع مع دعم الاستيثاق Authentication وربط الكائنات بالعلاقات Object-Relational Mapping أو ORM اختصارًا الذي يسمح بتعريف مخطط قاعدة بياناتك باستخدام أصناف Classes بايثون. يتمتع جانغو بمجتمع كبير ونشط يوفر مجموعة كبيرة من البرامج التعليمية والحزم والموارد الأخرى التي يمكن للمطورين استخدامها، إذ تُبنَى العديد من المواقع الإلكترونية المعروفة وذات حركة المرور العالية -مثل إنستغرام Instagram وبنترست Pinterest وموقع واشنطن تايمز The Washington Times- باستخدام إطار عمل جانغو. يُعَد جانغو أحد أكثر أطر عمل الويب شعبيةً وقوة للغة بايثون، وتستخدمه كلٌّ من الشركات الناشئة والكبيرة على نطاق واسع، مما يجعله خيارًا رائعًا لبناء تطبيقات ويب قوية وقابلة للتوسّع، وتؤدي مرونته وقابليته للتوسع إلى جعله الخيار الأفضل للمطورين والمؤسسات التي تريد إنشاء تطبيقات ويب معقدة وصغيرة الحجم. هذه المقالة جزء من سلسلة من المقالات تشرح جانغو للمبتدئين على النحو التالي: البدء في إنشاء مدونة بسيطة استخدام بنية MTV لإنشاء مدونة بسيطة استخدام عمليات CRUD لإدارة المدونة تطبيق المدونة الكامل إضافة بعض الميزات المتقدمة للمدونة تثبيت الأدوات اللازمة توجد بعض الأدوات التي يجب تثبيتها على جهازك قبل البدء، حيث تحتاج مبدئيًا لغة برمجة (بايثون)، وقاعدة بيانات (SQLite)، وخادم (سنستخدم خادم التطوير المُدمَج مع جانغو) لتشغيل التطبيق بنجاح. تذكّر أن هذه الأدوات مُخصَّصة لبيئة التطوير Dev Environment المخصصة لإنشاء واختبار التطبيق فقط، لذا لا يجب استخدام هذه الأدوات في بيئة الإنتاج Production Environment. تحتاج أيضًا إلى بيئة تطوير متكاملة IDE مثل بيئة باي تشارم PyCharm أو محرّر شيفرات برمجية على الأقل، حيث سنستخدم في هذا المقال المحرّر VS Code لأنه برنامج مجاني. اتبّع الآن الخطوات التالية للحصول على هذه الأدوات: تنزيل بايثون. تنزيل SQLite. تنزيل VS Code. تنزيل PyCharm. إنشاء مشروع جانغو جديد حان الوقت لبدء كتابة الشيفرة البرمجية بعد تثبيت الأدوات السابقة، لذا أنشئ أولًا مجلد عمل جديد باستخدام الأمر التالي لتنظيم كل شيء: mkdir django-demo ثم انتقل إلى هذا المجلد باستخدام الأمر التالي: cd django-demo أنشئ بعد ذلك بيئة افتراضية جديدة للغة بايثون ضمن مجلد العمل باستخدام الأمر التالي، حيث ستكون هذه البيئة الافتراضية معزولة عن بيئة بايثون في نظامك ومشاريع بايثون الأخرى التي قد تكون على جهازك. python -m venv env إذا استخدمتَ نظام لينكس Linux أو ماك macOS، فقد تضطر إلى تشغيل أمر python3 بدلًا من python، ولكننا سنستخدم python للتبسيط. سيعمل الأمر السابق على إنشاء المجلد env الذي يحتوي على البيئة الافتراضية التي أنشأتها، ويمكنك تنشيط هذه البيئة الافتراضية باستخدام الأمر التالي: source env/bin/activate وإذا استخدمتَ نظام ويندوز Windows، فاستخدم الأمر التالي بدلًا من ذلك: env/Scripts/activate إذا نشطت البيئة الافتراضية بنجاح، فسيكون موجّه سطر الأوامر الخاص بك كما يلي: (env) eric@djangoDemo:~/django-demo$ ملاحظة: يعني (env) هنا أنك تعمل حاليًا في بيئة افتراضية اسمها env. حان الوقت الآن لتهيئة مشروع جانغو جديد، حيث تحتاج إلى تثبيت حزمة Django من خلال تشغيل الأمر التالي: python -m pip install Django ثم يمكنك استخدام أمر django-admin لإنشاء مشروع جانغو جديد كما يلي: django-admin startproject djangoBlog وسينشَأ المجلد الجديد djangoBlog التالي: يُفضَّل إعادة هيكلة المشروع بعض الشيء بحيث نبدأ من المجلد الجذر للمشروع كما يلي، ولكن لا حاجة لذلك إن لم ترغب في ذلك. إنشاء تطبيق المدونة لا يزال المشروع فارغًا حاليًا، ولاحظ أن بنية هذا المشروع بسيطة مقارنةً ببنية مشروع لارافيل Laravel، وسنناقش كل ملف في مجلد المشروع بالتفصيل لاحقًا. يتيح جانغو إنشاء تطبيقات متعددة في مشروع واحد، فمثلًا يمكن أن يكون هناك تطبيق blog وتطبيق gallery وتطبيق forum ضمن مشروع واحد. يمكن أن تتشارك هذه التطبيقات في نفس الملفات الثابتة (ملفات CSS وجافاسكربت JavaScript) والصور ومقاطع الفيديو أو يمكن أن تكون مستقلة تمامًا عن بعضها البعض، إذ يعتمد ذلك على احتياجاتك الخاصة. سننشئ في هذا المقال تطبيق مدونة blog فقط، لذا نفّذ الأمر التالي في الطرفية: python manage.py startapp blog يجب أن ترى مجلد blog جديد ضمن مجلد جذر المشروع، ويمكنك سرد محتوى المجلد باستخدام الأمر التالي: ls وإذا أردتَ تضمين الملفات المخفية أيضًا في النتيجة، فاستخدم الأمر التالي: ls -a وإذا أردتَ رؤية بنية الملفات فاستخدم الأمر التالي: tree ولكن قد تحتاج إلى تثبيت برنامج tree ليعمل الأمر السابق بنجاح اعتمادًا على نظام تشغيلك. سنستخدم الأمر السابق لعرض بنية الملفات، حيث يجب أن يكون لمشروعك البنية التالية حاليًا: . ├── blog │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ ├── models.py │ ├── tests.py │ └── views.py ├── djangoBlog │ ├── asgi.py │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── env │ ├── bin │ ├── include │ ├── lib │ ├── lib64 -> lib │ └── pyvenv.cfg └── manage.py يجب بعد ذلك تسجيل تطبيق blog الجديد في جانغو، لذا انتقل إلى الملف settings.py وابحث عن القائمة INSTALLED_APPS: INSTALLED_APPS = [ 'blog', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] بدء تشغيل الخادم يمكنك الآن بدء تشغيل خادم التطوير لاختبار نجاح عمل كل شيء، لذا افتح الطرفية وشغّل الأمر التالي: python manage.py runserver وسيظهر الخرج التالي في واجهة سطر الأوامر: Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. October 19, 2022 - 01:39:33 Django version 4.1.2, using settings 'djangoBlog.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. افتح المتصفح وانتقل إلى العنوان http://127.0.0.1:8000/، وستظهر الصفحة التالية: بنية التطبيق لنتحدث عن البنية التالية لتطبيق جانغو الجديد وما يفعله كل ملف قبل أن نبدأ في كتابة الشيفرة البرمجية: . ├── blog │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ ├── models.py │ ├── tests.py │ └── views.py ├── djangoBlog │ ├── asgi.py │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── env │ ├── bin │ ├── include │ ├── lib │ ├── lib64 -> lib │ └── pyvenv.cfg └── manage.py المجلد الجذر يحتوي المجلد الجذر على الملفات والمجلدات التالية: manage.py: يمثل أداة سطر الأوامر التي تتيح لك التفاعل مع مشروع جانغو بطرق مختلفة. djangoBlog: مجلد المشروع الرئيسي الذي يحتوي على ملف الإعدادات ونقطة الدخول إلى المشروع. blog: يمثل تطبيق المدونة blog. مجلد المشروع يحتوي هذا المجلد على الملفات التالية: djangoBlog/__init__.py: ملف فارغ يخبر بايثون بأن هذا المجلد يجب عَدّه حزمة بايثون. djangoBlog/settings.py: ملف الإعدادات أو الضبط Configuration لمشروع جانغو. djangoBlog/urls.py: يحتوي على التصريحات عن عناوين URL لمشروع جانغو، ويمثل جدول محتويات تطبيق جانغو الخاص بك، حيث سنتحدث عنه أكثر لاحقًا. djangoBlog/asgi.py: يُعَد نقطة الدخول لخوادم الويب المتوافقة مع واجهة ASGI لتخديم مشروعك. djangoBlog/wsgi.py: نقطة الدخول لخوادم الويب المتوافقة مع واجهة WSGI لتخديم مشروعك. مجلد التطبيق يحتوي هذا المجلد على المجلدات والملفات التالية: blog/migrations: يحتوي هذا المجلد على جميع ملفات التهجير Migration لتطبيق المدونة، حيث ينشئ جانغو هذه الملفات تلقائيًا بناءً على نماذجك Models على عكس إطار عمل لارافيل. blog/admin.py: يأتي جانغو أيضًا مع لوحة مُدمَجة لإدارة الموقع، حيث يحتوي هذا الملف على جميع عمليات الضبط الخاصة بهذه اللوحة. blog/models.py: تصف النماذج Models بنية وعلاقة قاعدة البيانات، وتُنشَأ ملفات التهجير بناءً على هذا الملف. blog/views.py: يكافئ المتحكمات Controllers في لارافيل، ويحتوي على المنطق البرمجي الأساسي لهذا التطبيق. ضبط Configuring مشروع جانغو توجد بعض التغييرات التي يجب إجراؤها على الملف settings.py قبل البدء بالشروع كما سنوضح فيما يلي. المضيفون المسموح بهم تُعد القائمة ALLOWED_HOSTS قائمة بالنطاقات Domains التي يُسمَح لموقع جانغو بتخديمها، وهي إجراء أمني لمنع هجمات ترويسات مضيف HTTP أو HTTP Host Header Attacks، والتي يمكن حدوثها حتى عند إجراء العديد من عمليات ضبط خادم الويب الآمنة ظاهريًا. لاحظ أنه ما زال بإمكاننا الوصول إلى موقعنا باستخدام المضيف 127.0.0.1 حتى وإن كانت القائمة ALLOWED_HOSTS فارغة حاليًا، والسبب هو التحقق من صحة المضيف مقابل ['.localhost', '127.0.0.1', '[::1]'] عندما تكون قيمة DEBUG هي True وتكون القائمة ALLOWED_HOSTS فارغة. قاعدة البيانات DATABASES هو قاموس يحتوي على إعدادات قاعدة البيانات التي يحتاج موقعنا إلى استخدامها، حيث يستخدم جانغو قاعدة بيانات SQLite افتراضيًا، وهي قاعدة بيانات خفيفة الوزن وتتكون من ملف واحد فقط. يجب أن تكون قاعدة بيانات SQLite كافيةً لمشروعنا التجريبي الصغير، لكنها لن تناسب المواقع الكبيرة، لذا إذا أردتَ استخدام قواعد بيانات أخرى، فإليك المثال التالي: DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "<database_name>", "USER": "<database_user_name>", "PASSWORD": "<database_user_password>", "HOST": "127.0.0.1", "PORT": "5432", } } ملاحظة: يوصي جانغو باستخدام قاعدة بيانات PostgreSQL في بيئة الإنتاج. توجد العديد من البرامج التعليمية التي تعلمك كيفية استخدام جانغو مع قاعدة بيانات MongoDB، ولكنها تُعَد فكرة سيئة، إذ قد تكون قاعدة بيانات MongoDB حلًا رائعًا، ولكنها لا تعمل بطريقة جيدة مع إطار عمل جانغو. الملفات الساكنة Static Files وملفات الوسائط Media Files يجب أن نهتم أيضًا بالملفات الساكنة وملفات الوسائط، فالملفات الساكنة هي ملفات CSS وملفات جافاسكربت، وملفات الوسائط هي الصور ومقاطع الفيديو والأمور الأخرى التي قد يرفعها المستخدم. الملفات الساكنة Static Files يجب أولًا تحديد مكان تخزين هذه الملفات، حيث سننشئ المجلد static ضمن تطبيق المدونة لموقع جانغو الخاص بنا، ويُعَد هذا المجلد هو المكان الذي نخزّن فيه الملفات الساكنة لتطبيق blog أثناء التطوير. blog ├── admin.py ├── apps.py ├── __init__.py ├── migrations ├── models.py ├── static ├── tests.py └── views.py تختلف الأمور بعض الشيء في بيئة الإنتاج، إذ يجب أن ننشئ مجلدًا مختلفًا ضمن المجلد الجذر للمشروع، ولنسمّيه بالاسم staticfiles. . ├── blog ├── db.sqlite3 ├── djangoBlog ├── env ├── manage.py └── staticfiles ثم يجب تحديد هذا المجلد في الملف settings.py كما يلي: STATIC_ROOT = "staticfiles/" نخبر بعد ذلك جانغو بعنوان URL الذي يجب استخدامه عند الوصول إلى هذه الملفات في المتصفح. ليس من الضروري أن يكون هذا العنوان هو /static، ولكن تأكد من أنه لا يتداخل مع عمليات ضبط عناوين URL الخاصة بنا والتي سنتحدث عنها لاحقًا. STATIC_URL = "static/" ملفات الوسائط Media Files نضبط ملفات الوسائط باستخدام الطريقة نفسها، حيث يمكنك إنشاء المجلد mediafiles في المجلد الجذر للمشروع كما يلي: . ├── blog ├── db.sqlite3 ├── djangoBlog ├── env ├── manage.py ├── mediafiles └── staticfiles ثم نحدّد الموقع وعنوان URL في الملف settings.py كما يلي: # ملفات الوسائط MEDIA_ROOT = "mediafiles/" MEDIA_URL = "media/" موجه إرسال Dispatcher عناوين URL في جانغو يحتوي مجال تطوير الويب على بنية نموذج-عرض-متحكم Model-View-Controller (أو MVC اختصارًا)، حيث يكون النموذج Model في هذه البنية مسؤولًا عن التفاعل مع قاعدة بياناتنا، ويجب أن يقابل كلُّ نموذج جدولَ قاعدة بيانات واحد. العرض View هو جزء الواجهة الأمامية Frontend من التطبيق، وهو ما يمكن للمستخدمين رؤيته، والمتحكم Controller هو المنطق البرمجي للواجهة الخلفية للتطبيق مثل استرداد البيانات من قاعدة البيانات عبر النماذج ووضعها في العرض المقابل وإعادة القالب المعروض إلى المستخدم في النهاية. جانغو هو إطار عمل ويب مُصمَّم بناءً على بنية MVC مع مصطلحات مختلفة، فبنية جانغو هي بنية نموذج-قالب-عرض Model-Template-View (أو MTV اختصارًا)، فالقالب Template هو الواجهة الأمامية، والعرض هو المنطق البرمجي للواجهة الخلفية. سنركّز في هذا المقال على فهم هذه الطبقات، ولكن يجب أولًا البدء بنقطة الدخول لكل تطبيق ويب، والتي هي موجّه إرسال Dispatcher عناوين URL، حيث يقرأ هذا الموجّه عنوان URL ويوجّه المستخدم إلى الصفحة الصحيحة عندما يكتب المستخدم عنوان URL ويضغط على مفتاح Enter. عمليات ضبط عناوين URL الأساسية يُخزَّن ضبط عناوين URL في الملف example/urls.py كما يلي: djangoBlog ├── asgi.py ├── __init__.py ├── __pycache__ ├── settings.py ├── urls.py └── wsgi.py يوضّح المثال التالي ما يجب أن يبدو عليه موجّه إرسال عناوين URL: from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] يقرأ موجّه الإرسال المعلومات من عنوان URL ويعيد عرضًا، ولكن لا تخلط بين هذا العرض وعرض إطار عمل لارافيل، فالعرض في جانغو يمثّل متحكّمًا في لارافيل. إذا تبع النطاقُ admin/ في هذا المثال، فسيوجّه موجّه الإرسال المستخدم إلى صفحة المدير Admin. تمرير المعاملات باستخدام موجه إرسال عناوين URL في جانغو يكون من الضروري في بعض الأحيان أخذ مقاطع من عنوان URL وتمريرها إلى العرض بوصفها معاملات إضافية، فمثلًا إذا أردتَ عرض صفحة التدوينة، فستحتاج إلى تمرير المعاملَين id أو slug الخاصين بالتدوينة إلى العرض حتى تتمكّن من استخدام هذه المعلومات الإضافية للعثور على التدوينة التي تبحث عنها. إليك فيما يلي الطريقة التي يمكننا تمرير المعاملات باستخدامها: from django.urls import path from blog import views urlpatterns = [ path('post/<int:id>', views.post), ] ستلتقط الأقواس الزاويّة جزءًا من عنوان URL كمعامل، حيث يُسمَّى int على الجانب الأيسر بمحوِّل المسار Path Converter الذي يلتقط معاملًا من النوع الصحيح، ويمكن مطابقة أيّ سلسلة نصية باستثناء المحرف / عند عدم تضمين المحوّل. لاحظ وجود اسم المعامل على الجانب الأيمن، وهو ما سنحتاج إلى استخدامه في العرض. تتوفّر محوّلات المسارات التالية افتراضيًا: str: يطابق أيّ سلسلة نصية غير فارغة باستثناء فاصل المسار '/' افتراضيًا عند عدم تضمين محوّلٍ في التعبير. int: يطابق الصفر أو أيّ عدد صحيح موجب، ويعيد قيمة من النوع int. slug: يطابق أيّ سلسلة نصية للاسم المختصر Slug، والذي يتكون من حروف أو أرقام ASCII، بالإضافة إلى محارف الشرطة والشرطة السفلية مثل building-your-1st-django-site. uuid: يطابق معرّف UUID المنسَّق، حيث يجب تضمين شرطات، ويجب أن تكون الحروف صغيرة لمنع ربط عناوين URL متعددة مع الصفحة نفسها مثل المعرّف 075194d3-6885-417e-a8a8-6c931e272f00، ويعيد نسخةً من UUID. path: يطابق أي سلسلة نصية غير فارغة مع فاصل المسار '/'، مما يتيح لك المطابقة مع مسار URL كامل بدلًا من المطابقة مع مقطعٍ من مسار URL كما هو الحال مع المحوِّل str. استخدام التعابير النمطية Regular Expressions لمطابقة أنماط عناوين URL قد يكون النمط Pattern الذي تريد مطابقته أكثر تعقيدًا في بعض الحالات، وبالتالي يمكنك استخدام التعابير النمطية Regular Expressions لمطابقة أنماط عناوين URL. يجب استخدام التابع re_path() بدلًا من التابع path() لاستخدام التعابير النمطية: from django.urls import path, re_path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail), ] استيراد عمليات ضبط عناوين URL الأخرى لنفترض أن لديك مشروع جانغو يحتوي على تطبيقات متعددة مختلفة، حيث إذا وضعت جميع عمليات ضبط عناوين URL في ملف واحد، فسيكون الأمر فوضويًا جدًا، ولكن يمكنك فصل عمليات ضبط عناوين URL ونقلها إلى تطبيقات مختلفة، فمثلًا يمكننا في مشروعنا إنشاء ملف urls.py جديد ضمن تطبيق blog كما يلي: from django.urls import path, include urlpatterns = [ path('blog/', include('blog.urls')) ] وهذا يعني أنه إذا احتوى عنوان URL على النمط http://www.mydomain.com/blog/xxxx، فسينتقل جانغو إلى الملف blog/urls.py ويطابق باقي عنوان URL. لنعرّف الآن أنماط عناوين URL لتطبيق blog باستخدام الطريقة نفسها كما يلي: from django.urls import path from blog import views urlpatterns = [ path('post/<int:id>', views.post), ] سيتطابق هذا النمط مع نمط عنوان URL الذي هو http://www.mydomain.com/blog/post/123. تسمية أنماط عناوين URL يمكنك تسمية نمط عنوان URL من خلال إعطائه معاملًا ثالثًا كما يلي: urlpatterns = [ path('articles/<int:year>/', views.year_archive, name='news-year-archive'), ] يؤدي ذلك إلى القدرة على عكس عناوين URL المطلقة من القالب، حيث سنتحدث عن ذلك لاحقًا عندما نصل إلى طبقة القوالب. تعرّفنا على أساسيات موجّه إرسال عناوين URL، ولكن لا زال هناك شيء لم نتطرق لشرحه وهو كيفية التمييز بين التوابع المختلفة لطلبات HTTP، حيث لا يمكنك في جانغو مطابقة توابع HTTP المختلفة ضمن موجه إرسال عناوين URL، إذ تتطابق جميع التوابع مع نمط عنوان URL نفسه، وبالتالي يجب استخدام التحكم في التدفق لكتابة شيفرات برمجية مختلفة لهذه التوابع في دوال العرض، حيث سنناقش ذلك بالتفصيل في المقالات اللاحقة من هذه السلسلة. ترجمة -وبتصرّف- للمقال Django for Beginners #1 - Getting Started لصاحبه Eric Hu. اقرأ أيضًا حزم بايثون الثمانية التي تسهل تعاملك مع Django مدخل إلى إطار عمل الويب جانغو Django تعرف على أمان تطبيقات جانغو بناء تطبيق مهام باستخدام جانغو Django وريآكت React