يعد إطار العمل جانغو Django إطار ويب كامل full-stack يمكننا من خلاله إنشاء كل من الواجهة الأمامية frontend والواجهة الخلفية backend معًا وهو قائم على لغة بايثون Python، ويتبع نمط تصميم MTV وهو اختصار للنمط البنائي للبرمجيات نموذج Model قالب Template عرض View. لكن في جانغو، عندما يطلب المستخدم النهائي صفحة ويب، يجب تصيير rendering الصفحة أولًا في الواجهة الخلفية، ومن ثم تُرسل صفحة HTML الناتجة إلى المستخدم. وفي هذه الحالة، عندما يكون لديك عدد كبير من المستخدمين، سيضع ذلك ضغطًا كبيرًا على خادمك.
لحل هذه المشكلة، يقسم المطورون التطبيق إلى قسمين هما الواجهة الخلفية والواجهة الأمامية وعندما يطلب المستخدم صفحة ويب، لن تصيّر الواجهة الخلفية لصفحة الويب، بل يكفي أن تجمع البيانات الضرورية فقط وتنقل إلى الواجهة الأمامية. وهنا يأتي دور العميل ليستخدم هذه البيانات ويعرض صفحة الويب داخل المتصفح مباشرة، وبالتالي يخفف الضغط على الخادم.
سنتحدث في سلسلة المقالات هذه حول كيفية إنشاء تطبيق حديث من صفحة واحدة باستخدام جانغو Django كواجهة خلفية، وفيو Vue كواجهة أمامية، وغراف كيو إل GraphQL كلغة لمعالجة API تربطهما معًا.
هذا المقال هو جزء من سلسلة مقالات تتحدث حول كيفية إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue:
- إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول.
- إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني.
- إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث.
مراجعة سريعة عن جانغو
لنبدأ بمراجعة سريعة حول إطار عمل جانغو Django، جانغو هو إطار عمل ويب يعتمد على لغة بايثون ويتبع النمط البنائي MTV، الذي هو اختصار لثلاث كلمات:
- النموذج Model: عبارة عن واجهة تتيح لنا التفاعل مع قاعدة البيانات، مثل استرجاع السجلات أو إنشائها أو تحديثها أو حذفها.
- القالب Template: يمثل الواجهة الأمامية من إطار العمل، وهو الجزء الذي سيراه المستخدمون النهائيون.
- العرض View: يمثل الواجهة الخلفية للتطبيق، إذ يستخدم النموذج Model للتفاعل مع قاعدة البيانات، مثل استرجاع البيانات التي يطلبها المستخدم، ثم يعمل العرض View على معالجة هذه البيانات بطريقة ما، وتظهر النتيجة للمستخدم عبر القالب Template الذي عادةً ما يكون مخصصًا.
في سلسلة المقالات الحالية؛ سنستخدم جانغو للواجهة الخلفية فقط، أي لن نستخدم القوالب والعروض في جانغو، ونستستخدم عوضًا عن ذلك Vue.js لبناء الواجهة الأمامية و GraphQL كطريقة للتواصل بين الواجهة الأمامية والخلفية.
إنشاء مشروع جانغو جديد
الطريقة التي سنعتمد عليها هي فصل مجلد الواجهة الخلفية عن مجلد الواجهة الأمامية، لذا فإن أول خطوة هي إنشاء هيكل المشروع بالطريقة التالية:
blog ├── backend └── frontend
انتقل إلى مجلد backend، وأنشئ بيئة بايثون افتراضية جديدة. وبيئة بايثون الافتراضية هي بيئة معزولة لكل تثبيت بايثون جديد، لذا عندما تعمل على تثبيت حزم داخل هذه البيئة الافتراضية لن يؤثر ذلك على بيئة بايثون الخاصة بنظامك، وهو أمر بالغ الأهمية لاسيما إذا كنت تستخدم نظام تشغيل لينكس Linux أو ماك أو إس macOS، ولا تريد التأثير على ملفاتهما الأساسية.
cd backend
python3 -m venv env
سيؤدي الأمر السابق إلى إنشاء مجلد جديد يُدعى env
مع إنشاء البيئة الافتراضية بداخله. ولتنشيط هذه البيئة الافتراضية، استخدم الأمر التالي:
source env/bin/activate
إذا كنت تستخدم نظام تشغيل ويندوز Windows، فاستخدم الأمر التالي عوضًا عن السابق، كما يُنصح بتثبيت WSL:
env/Scripts/activate
بعد تنشيط البيئة الافتراضية، ستبدو الطرفية Terminal كما في الشكل أدناه. لاحظ وجود (env)
أمام اسم المستخدم، منا يدل على أنك تعمل حاليًا في البيئة الافتراضية.
يمكنك الآن إنشاء مشروع جانغو جديد.
python -m pip install Django
django-admin startproject backend
بعدها يمكنك إنشاء تطبيق جديد باسم blog:
python manage.py startapp blog
في النهاية، يجب أن يكون هيكل المشروع كما يلي:
. ├── backend │ ├── backend │ ├── blog │ ├── manage.py │ └── requirements.txt └── frontend
إنشاء النماذج Models
تذكر أن النماذج هي عبارة عن واجهة يمكننا استخدامها للتفاعل مع قاعدة البيانات، وواحدة من المزايا الرائعة في جانغو هي أنه يمكنك تلقائيًا معرفة التغييرات التي أجريتها على النماذج، وتوليد ملفات التهجير migration files المقابلة، والتي يمكن استخدامها لإجراء تغييرات على قاعدة البيانات.
نموذج الموقع Site Model
لنبدأ بالنموذج Site الذي يخزن المعلومات الأساسية لموقعك على الويب.
class Site(models.Model): name = models.CharField(max_length=200) description = models.TextField() logo = models.ImageField(upload_to='site/logo/') class Meta: verbose_name = 'site' verbose_name_plural = '1. Site' def __str__(self): return self.name
نلاحظ في السطر الرابع الأمر ImageField
الذي يخبر جانغو بتحميل الصورة إلى مجلد 'site/logo/'
ولتحقيق ذلك، عليك القيام بشيئين اثنين.
أولاً، يجب عليك تثبيت حزمة Pillow، إذ يحتاجها جانغو لمعالجة الصور.
python -m pip install Pillow
ثانيًا، ستحتاج إلى إعداد توجيه جديد لمكان التخزين في settings.py
إذ يجب أن تخبر جانغو بالمكان الذي ستخزن فيه هذه الوسائط، وما هي عناوين URL التي ستستخدمها للوصول إلى هذه الملفات.
import os # Media Files MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles') MEDIA_URL = '/media/'
تشير الإعدادات السابقة إلى أن ملفات الوسائط ستُخزن داخل ملف /mediafiles
وسنحتاج إلى استخدام البادئة /media/
قبل عنوان URL للوصول إلى الملفات. فعلى سبيل المثال، سيؤدي عنوان URL التالي http://localhost:3000/media/example.png
إلى استرجاع الصورة /mediafiles/example.png
.
نموذج المستخدم User Model
النموذج التالي هو نموذج المستخدم User model، ولحسن الحظ، فإن جانغو يأتي مزودًا بنموذج مستخدم مدمج، والذي يوفر الوظائف الأساسية المتعلقة بالأذونات permission والتراخيص authorization. ومع ذلك، سنجرب في مشروعنا هذا شيئًا أكثر تعقيدًا، مثل إضافة صور أفاتار avatar للملف الشخصي وإضافة سيرة ذاتية وبعض المعلومات الأخرى، ولتحقيق ذلك، ستحتاج إلى إنشاء نماذج مستخدم جديدة، والتي نوسع فيها الصنف AbstractUser، ومعنى نوسعه extends أي نرث منه صنف جديد لنخصصه وفق حاجتنا أو نضيف له المزيد من الميزات.
from django.contrib.auth.models import AbstractUser # New user model class User(AbstractUser): avatar = models.ImageField( upload_to='users/avatars/%Y/%m/%d/', default='users/avatars/default.jpg' ) bio = models.TextField(max_length=500, null=True) location = models.CharField(max_length=30, null=True) website = models.CharField(max_length=100, null=True) joined_date = models.DateField(auto_now_add=True) class Meta: verbose_name = 'user' verbose_name_plural = '2. Users' def __str__(self): return self.username
هكذا يبدو صنف AbstractUser في جانغو:
from django.contrib.auth.models import AbstractUser # New user model class User(AbstractUser): avatar = models.ImageField( upload_to='users/avatars/%Y/%m/%d/', default='users/avatars/default.jpg' ) bio = models.TextField(max_length=500, null=True) location = models.CharField(max_length=30, null=True) website = models.CharField(max_length=100, null=True) joined_date = models.DateField(auto_now_add=True) class Meta: verbose_name = 'user' verbose_name_plural = '2. Users' def __str__(self): return self.username
كما هو واضح، يوفر هذا النموذج بعض الحقول الأساسية، مثل الاسم الأول واسم العائلة ونحو ذلك.
ستحتاج بعد ذلك إلى التأكد من أن جانغو يستخدم نموذج المستخدم الجديد كنموذج مستخدم افتراضي، لذلك انتقل إلى ملف إعدادات التطبيق settings.py
وأضف له التوجيه directive التالي:
# Change Default User Model AUTH_USER_MODEL = 'blog.User'
نماذج الفئة Category والوسم Tag والمنشور Post
class Category(models.Model): name = models.CharField(max_length=200) slug = models.SlugField() description = models.TextField() class Meta: verbose_name = 'category' verbose_name_plural = '3. Categories' def __str__(self): return self.name
class Tag(models.Model): name = models.CharField(max_length=200) slug = models.SlugField() description = models.TextField() class Meta: verbose_name = 'tag' verbose_name_plural = '4. Tags' def __str__(self): return self.name
class Post(models.Model): title = models.CharField(max_length=200) slug = models.SlugField() content = RichTextField() featured_image = models.ImageField( upload_to='posts/featured_images/%Y/%m/%d/') is_published = models.BooleanField(default=False) is_featured = models.BooleanField(default=False) created_at = models.DateField(auto_now_add=True) modified_at = models.DateField(auto_now=True) # Each post can receive likes from multiple users, and each user can like multiple posts likes = models.ManyToManyField(User, related_name='post_like') # Each post belong to one user and one category. # Each post has many tags, and each tag has many posts. category = models.ForeignKey( Category, on_delete=models.SET_NULL, null=True) tag = models.ManyToManyField(Tag) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) class Meta: verbose_name = 'post' verbose_name_plural = '5. Posts' def __str__(self): return self.title def get_number_of_likes(self): return self.likes.count()
لاحظ كيف تنفذ ميزة الإعجاب بالمنشورات (Like system) في السطر 13، وهو لا يعتمد على النوع البسيط IntegerField (وهو حقل لتخزين القيم الصحيحة) ولكنه يعمل تمامًا مثل الوسوم Tags. ويمكنك استخدام الدالة get_number_of_likes()
لتحصل على عدد الإعجابات لكل منشور.
نموذج التعليقات Comment model
والآن دعونا نخطو خطوةً نحو الأمام، وننشئ قسمًا مخصصًا للتعليقات لهذا التطبيق:
class Comment(models.Model): content = models.TextField(max_length=1000) created_at = models.DateField(auto_now_add=True) is_approved = models.BooleanField(default=False) # Each comment can receive likes from multiple users, and each user can like multiple comments likes = models.ManyToManyField(User, related_name='comment_like') # Each comment belongs to one user and one post user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) post = models.ForeignKey(Post, on_delete=models.SET_NULL, null=True) class Meta: verbose_name = 'comment' verbose_name_plural = '6. Comments' def __str__(self): if len(self.content) > 50: comment = self.content[:50] + '...' else: comment = self.content return comment def get_number_of_likes(self): return self.likes.count()
إعداد لوحة التحكم في جانغو
والآن حان الوقت لإعداد لوحة التحكم أو لوحة الإدارة في جانغو، لذا افتح ملف admin.py
واكتب فيه التالي:
from django.contrib import admin from .models import * # Register your models here. class UserAdmin(admin.ModelAdmin): list_display = ('username', 'first_name', 'last_name', 'email', 'date_joined') class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} class TagAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} class PostAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('title',)} list_display = ('title', 'is_published', 'is_featured', 'created_at') class CommentAdmin(admin.ModelAdmin): list_display = ('__str__', 'is_approved', 'created_at') admin.site.register(Site) admin.site.register(User, UserAdmin) admin.site.register(Category, CategoryAdmin) admin.site.register(Tag, TagAdmin) admin.site.register(Post, PostAdmin) admin.site.register(Comment, CommentAdmin)
فيما يتعلق بـ CommentAdmin، فإن __str__
تشير إلى الدالة __str__()
في نموذج التعليق. والتي ستعيد أول 50 حرفًا في السلسلة وتدمجها مع السلسلة ...
.
والآن، فعِّل خادم التطوير وتأكد ما إذا كانت كل وظيفة تعمل على النحو الصحيح:
python manage.py runserver
وقبل الانتقال إلى الخطوة التالية، لا تنسَ إضافة بعض المعلومات الجانبية إلى مدونتك.
مراجعة سريعة حول فيو جي إس Vue.js
والآن بعد أن انتهينا من إعداد الواجهة الخلفية، حان الوقت للانتقال إلى الواجهة الأمامية، وبذلك الانتقال سنبدأ باستخدام فيو جي إس Vue.js في الجزء الثاني من مقالنا هذا لإعداد الواجهة الأمامية في تطبيقنا.
قبل الخوض عميقًا، سنبدأ بمراجعة سريعة حول فيو جي إس Vue.js في حال لم تستخدمه من قبل، فيو جي إس Vue.js هو أحد أطر عمل جافا سكريبت JavaScript المخصصة لبناء الواجهات الأمامية، إذ يوفر لك نظامًا بسيطًا معتمدًا على المكونات، ما يسمح لك بإنشاء واجهات مستخدم تفاعلية.
المقصود بـ "معتمدًا على المكونات" يعني أن المكون الجذري App.vue بإمكانه استيراد مكونات أخرى، وهي الملفات ذات الامتداد .vue، ويمكن لهذه المكونات استيراد المزيد من المكونات الأخرى، ما يسمح لك بإنشاء أنظمة معقدة ومتطورة للغاية.
يحتوي الملف النموذجي ذو الامتداد .vue على ثلاثة أقسام، القسم الأول هو <template>
يتضمن شيفرات بلغة HTML أما القسم الثاني فهو <script>
ويتضمن شيفرات بلغة JavaScript، والقسم الأخير <style>
يتضمن شيفرات بلغة CSS.
يمكنك في قسم <script>
التصريح عن ارتباطات bindings جديدة داخل التابع data()
، ويمكنك بعد ذلك عرض هذه الارتباطات داخل قسم <template>
باستخدام الأقواس المزدوجة المتعرجة {{Binding}}
. ستُغطى هذه الارتباطات المُصرَّح عنها في التابع data()
تلقائيًا داخل نظام تفاعل فيو Vue، وهذا يعني أنه عندما تتغير قيمة الارتباط Binding، سيتغير المكون المقابل تلقائيًا، دون الحاجة إلى تحديث الصفحة.
يمكن أن يحتوي القسم <script>
أيضًا على توابع أخرى غير data()
، مثل computed
وprops
. كما يتيح لنا <template>
ربط البيانات باستخدام توجيهات مثل v-bind
و v-on
و v-model
.
إنشاء مشروع Vue.js جديد
يمكن تثبيت وإنشاء تطبيق Vue باستخدام أداة سطر الأوامر Vue، لكننا في هذه المرة سنتبع طريقة مختلفة إذ سنستخدم أداة إنشاء واجهات أمامية تسمى Vite، مع ملاحظة أن الكلمة تُنطق veet وتعني سريع باللغة الفرنسية، والتي أنشأها ذات الشخص الذي أنشأ Vue.js.
انتقل الآن إلى مجلد frontend
ونفذ الأمر التالي:
npm init vue@latest
سيُطلب منك العديد من الخيارات، ولهذا المشروع، كل ما عليك فعله إضافة Vue Router:
✔ Project name: … <your_project_name> ✔ Add TypeScript? … No / Yes ✔ Add JSX Support? … No / Yes ✔ Add Vue Router for Single Page Application development? … No / Yes ✔ Add Pinia for state management? … No / Yes ✔ Add Vitest for Unit testing? … No / Yes ✔ Add Cypress for both Unit and End-to-End testing? … No / Yes ✔ Add ESLint for code quality? … No / Yes ✔ Add Prettier for code formating? … No / Yes Scaffolding project in ./<your_project_name>. . . Done.
إذا كنت ترتاح أكثر مع لغة برمجة جيدة، فيمكنك اختيار تثبيت TypeScript وإن كنت تحتاج إلى التصحيح التلقائي والتنسيق التلقائي للتعليمات البرمجية التي تكتبها، فيمكنك تثبيت ESlint وPrettier، ستؤدي عملية التثبيت هذه إلى إنشاء ملف package.json في دليل مشروعك، والذي يقوم بتخزين الحزم المطلوبة وإصداراتها. ستحتاج بعد ذلك إلى تثبيت هذه الحزم داخل مشروعك.
cd <your_project_name>
npm install
npm run dev
ثمة شيء أخير قبل البدء في إنشاء الواجهة الأمامية للتطبيق، نحن نستخدم في مشروعنا هذا إطار عمل بلغة CSS يُدعى TailwindCSS، لذا لتثبيته، نفذ الأمر التالي:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
سيؤدي ذلك إلى إنشاء ملفين، الأول هو tailwind.config.js
والثاني postcss.config.js
، وإذا كنت تريد معرفة المزيد عن Tailwind فيمكنك الاطلاع على مقال مقارنة بين Bootstrap و Tailwind CSS، كما يمكنك الاطلاع على الدليل الرسمي لـ Tailwind.
انتقل إلى tailwind.config.js
وأضف المسار إلى جميع ملفات قالبك:
module.exports = { content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], };
أنشئ ملف ./src/index.css
وأضف توجيهات @tailwind
لكل طبقة من طبقات Tailwind’s layers.
@tailwind base; @tailwind components; @tailwind utilities;
والآن، استورد الملف الذي أنشأته حديثًا ./src/index.css
إلى ملف ./src/main.js
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import "./index.css"; const app = createApp(App); app.use(router); app.mount("#app");
والآن، من المفترض أن تكون قادرًا على استخدام Tailwind داخل ملفات .vue
، وللتأكد، دعنا نختبر ذلك.
<template> <header> . . . <div class="wrapper"> <HelloWorld msg="You did it!" /> <h1 class="text-3xl font-bold underline">Hello world!</h1> . . . </div> </header> . . . </template>
لاحظ أننا أضفنا عنوان <h1>
بعد <HelloWorld>
، إذ يستخدم العنوان أصناف Tailwind.
مكتبة Vue Router
لاحظ أن مجلد مشروعك مختلف قليلًا في هذه المرة:
يوجد داخل المجلد الرئيسي src مجلد router ومجلد views، إذ يحتوي مجلد router على ملف index.js، والذي يمكنك من خلاله تحديد وجهات مختلفة، بحيث تشير كل وجهة route إلى مكون view موجود داخل مجلد views، ويمكن أن يوسع كل view بعد ذلك إلى مكونات أخرى داخل مجلد المكونات Components. تزودنا Vue بمثال على ذلك في ملف index.js:
import { createRouter, createWebHistory } from "vue-router"; import HomeView from "../views/HomeView.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: "/", name: "home", component: HomeView, }, { path: "/about", name: "about", // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import("../views/AboutView.vue"), }, ], }); export default router;
لاستدعاء موجه router ما، انظر في داخل ملف App.vue، وعوضًا عن الوسم <a>
، يُستخدم الوسم <RouterLink>
المستورد من
حزمة vue-router لإدارة التنقل بين الصفحات في تطبيق Vue.js.
<script setup> import { RouterLink, RouterView } from "vue-router"; . . . </script> <template> <header> . . . <div class="wrapper"> . . . <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> </nav> </div> </header> <RouterView /> </template>
عند إخراج الصفحة، سيُستبدل الوسم <RouterView/>
بالعرض المطابق، وإذا لم تكن ترغب في استيراد هذه المكونات، فكل ما عليك هو استخدام الوسم router-link to="">>
و <router-view>
عوضًا عن ذلك.
إنشاء وجهات routes باستخدام Vue router
سنحتاج في مدونة تطبيقنا إلى إنشاء ست صفحات على الأقل، سنحتاج إلى صفحة رئيسية والتي تعرض قائمة بأحدث الصفحات في المدونة، كما سنحتاج إلى صفحة الأقسام والتي تعرض جميع الأقسام أو الوسوم في المدونة، وصفحة أخرى لتعرض قائمة بالمقالات التي تنتمي إلى الأقسام أو الوسوم، وأخيرًا صفحة المقال التي تعرض محتوى المقال إضافةً إلى التعليقات.
فيما يلي الموجهات التي أنشأناها، إذ يوجه الوسم @ نحو مجلد src.
import { createRouter, createWebHistory } from "vue-router"; import HomeView from "@/views/main/Home.vue"; import PostView from "@/views/main/Post.vue"; import CategoryView from "@/views/main/Category.vue"; import TagView from "@/views/main/Tag.vue"; import AllCategoriesView from "@/views/main/AllCategories.vue"; import AllTagsView from "@/views/main/AllTags.vue"; const routes = [ { path: "/", name: "Home", component: HomeView, }, { path: "/category", name: "Category", component: CategoryView, }, { path: "/tag", name: "Tag", component: TagView, }, { path: "/post", name: "Post", component: PostView, }, { path: "/categories", name: "Categories", component: AllCategoriesView, }, { path: "/tags", name: "Tags", component: AllTagsView, }, ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); export default router;
لاحظ أننا نعمل في هذا المقال على إنشاء الواجهة الأمامية فقط، ولم نصل إلى مرحلة نقل البيانات حتى الآن، لذا لا تقلق حول كيفية العثور على المنشورات أو الأقسام أو الوسوم في الوقت الحالي.
إنشاء العروض والصفحات والمكونات
توضح الصور التالية واجهة المستخدم الأمامية التي أنشأناها لهذا المشروع، ويمكنك إنشاؤها لديك إما باعتماد الشيفرات البرمجية في هذا المقال، أو كتابة الشيفرات البرمجية الخاصة الخاص بك كما يمكنك العثور على الكود البرمجي الكامل من هنا.
- الصفحة الرئيسية Home Page
- جميع التصنيفات All Categories
- جميع الوسوم All Tags
- صفحة تسجيل الدخول Sign In
- صفحة إنشاء حساب Sign Up
- صفحة المنشور المفرد Post
- قسم التعليقات على المنشور Comments
- صفحة الملف الشخصي للمستخدم Profile
- قسم تعليقات المستخدمين User Comments
ترجمة -وبتصرّف- للمقال Create a Modern Application with Django and Vue #1
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.