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

إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الأول: الأساسيات


محمد ناولو2

يعد إطار العمل جانغو Django     إطار ويب كامل full-stack قائم على لغة بايثون Python، ويتبع نمط تصميم MTV (اختصار للنمط البنائي للبرمجيات نموذج-قالب-عرض Model-Template-View). والمقصود بمصطلح إطار ويب متكامل full-stack أنه يمكننا من خلاله إنشاء كل من الواجهة الأمامية frontend والواجهة الخلفية backend معًا.

ومع ذلك، ثمة عيب صغير في هذا الحل، فعندما يطلب المستخدم النهائي صفحة ويب، هنا يجب تصيير rendering الصفحة أولًا في الواجهة الخلفية، ومن ثم تُرسل صفحة HTML المُكوَّنة إلى المستخدم. وفي هذه الحالة، عندما يكون لديك عدد كبير من المستخدمين، سيضع ذلك ضغطًا كبيرًا على خادمك.

لحل هذه المشكلة، عادةً ما يُقسِّم المطورون التطبيق إلى قسمين؛ الواجهة الخلفية والواجهة الأمامية. وفي هذه الحالة، عندما يطلب المستخدم صفحة ويب، لن تُكوِّن الواجهة الخلفية صفحة الويب، بل يكفي أن تجمع البيانات الضرورية فقط وتنقلها إلى الواجهة الأمامية. وهنا يأتي دور جهاز العميل الذي يتمتع عادةً بقدرات حاسوبية عالية، إذ يستخدم هذه البيانات لعرض صفحة الويب داخل المتصفح مباشرةً، وبالتالي تخفيف الضغط على الخادم.

سنتحدث في سلسلة المقالات هذه حول كيفية إنشاء تطبيق حديث من صفحة واحدة باستخدام جانغو Django كواجهة خلفية، وفيو Vue كواجهة أمامية، وغراف كيو إل GraphQL كلغة معالجة API تربطهما معًا.

هذا المقال هو جزء من سلسلة مقالات تتحدث حول كيفية إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue:

مراجعة سريعة عن جانغو

لنبدأ بمراجعة سريعة حول إطار عمل جانغو Django، جانغو هو إطار عمل ويب يعتمد على لغة بايثون ويتبع النمط البنائي MTV، الذي هو اختصار لثلاث كلمات:

  • النموذج M = Model: عبارة عن واجهة تتيح لنا التفاعل مع قاعدة البيانات، مثل استرجاع السجلات أو إنشائها أو تحديثها أو حذفها.
  • القالب T = Template: يمثل الواجهة الأمامية من إطار العمل، وهو الجزء الذي سيراه المستخدمون النهائيون.
  • العرض V = 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) أمام اسم المستخدم، منا يدل على أنك تعمل حاليًا في البيئة الافتراضية.

virtual-env.png

يمكنك الآن إنشاء مشروع جانغو جديد.

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

admin.png

وقبل الانتقال إلى الخطوة التالية، لا تنسَ إضافة بعض المعلومات الجانبية إلى مدونتك.

مراجعة سريعة حول فيو جي إس Vue.js

والآن بعد أن انتهينا من إعداد الواجهة الخلفية، حان الوقت للانتقال إلى الواجهة الأمامية، وبذلك الانتقال سنبدأ باستخدام فيو جي إس 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-welcome.png

مكتبة Vue Router

لاحظ أن مجلد مشروعك مختلف قليلًا في هذه المرة:

vue-router.png

يوجد داخل المجلد الرئيسي 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

سنحتاج في مدونة تطبيقنا إلى إنشاء 6 صفحات على الأقل، سنحتاج‏ إلى صفحة رئيسية والتي تعرض قائمة بأحدث الصفحات في المدونة، كما سنحتاج إلى صفحة الأقسام والتي تعرض جميع الأقسام أو الوسوم في المدونة، وصفحة أخرى لتعرض قائمة بالمقالات التي تنتمي إلى الأقسام أو الوسوم، وأخيرًا صفحة المقال التي تعرض محتوى المقال إضافةً إلى التعليقات.

فيما يلي الموجهات التي أنشأناها، إذ يوجه الوسم @ نحو مجلد 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

homepage.png

  • جميع التصنيفات All Categories

all-categories.png

  • جميع الوسوم All Tags

all-tags.png

  • صفحة تسجيل الدخول Sign In

signin.png

  • صفحة إنشاء حساب Sign Up

signup.png

  • صفحة المنشور المفرد Post

post.png

  • قسم التعليقات على المنشور Comments

comments.png

  • صفحة الملف الشخصي للمستخدم Profile

profile.png

  • قسم تعليقات المستخدمين User Comments

user-comments.png

ترجمة -وبتصرّف- للمقال Create a Modern Application with Django and Vue #1

اقرأ أيضًا


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

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

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



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

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

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

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

  Only 75 emoji are allowed.

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

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

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


×
×
  • أضف...