شرح بسيط لـ Python Django Fullstack (مع رسوم توضيحية وكود حقيقي)

Python Django Fullstack : الأساسيات في مقال واحد — كود حقيقي، مخططات وخطوات ملموسة، مقتطفات من دورة مكونة من 24 درسًا.

شرح بسيط لـ Python Django Fullstack (مع رسوم توضيحية وكود حقيقي)

دليل مباشر إلى الهدف: Python Django Fullstack مفكك مع مخططات ورسوم بيانية، أمثلة عملية وأوامر مجربة. كل شيء من دورة منظمة من 8 فصول — إليك أفضلها.

tl;dr
  • لماذا Django
  • Models et migrations
  • Views et URLs
  • Templates et forms
  • Admin
~$ cat ./parcours.md # Python Django Fullstack — 8 فصول
01
لماذا Django
→ Django vs Flask vs FastAPI→ Installation et premier projet+ 1 دروس أخرى
02
النماذج والهجرات
→ Models et fields→ Relations+ 1 دروس أخرى
03
العروض والروابط
→ Function-based views→ Class-based views (CBV)+ 1 دروس أخرى
04
القوالب والنماذج
→ Template language→ Forms et ModelForms+ 1 دروس أخرى
05
الإدارة
→ Customizer ModelAdmin→ Actions et filters custom+ 1 دروس أخرى
06
المصادقة
→ User model et auth views→ Custom User et Profile+ 1 دروس أخرى
07
النشر
→ Settings prod/dev→ Gunicorn + Nginx+ 1 دروس أخرى
08
المشروع النهائي مدونة إلكترونية
→ Chapitre 07 – Projet final : Modeles et admin→ Chapitre 07 – Projet final : Views et templates+ 1 دروس أخرى
🏁
المشروع النهائي
→ تعود بمشروع ملموس وقابل للعرض

التهجيرات

الدورة الأساسية

output
# 1. تعديل نموذج
class Article(models.Model):
    title = models.CharField(max_length=200)
    summary = models.TextField(blank=True)         # جديد

# 2. توليد التهجيرة
python manage.py makemigrations
# Migrations for 'blog':
#   blog/migrations/0002_article_summary.py

# 3. التطبيق
python manage.py migrate

# 4. التحقق
python manage.py showmigrations

عرض الـ SQL المولد

output
python manage.py sqlmigrate blog 0002

# Output :
# BEGIN;
# ALTER TABLE blog_article ADD COLUMN summary TEXT NOT NULL DEFAULT '';
# COMMIT;

التراجع عن تهجيرة

output
python manage.py migrate blog 0001
# التراجع إلى 0001 (إلغاء 0002)

python manage.py migrate blog zero
# إلغاء كل شيء لتطبيق blog

تهجيرة البيانات

output
python manage.py makemigrations --empty --name backfill_slugs blog

# تعديل الملف المولد:
from django.db import migrations
from django.utils.text import slugify

def backfill_slugs(apps, schema_editor):
    Article = apps.get_model("blog", "Article")
    for a in Article.objects.all():
        if not a.slug:
            a.slug = slugify(a.title)
            a.save()

def reverse_func(apps, schema_editor):
    pass

class Migration(migrations.Migration):
    dependencies = [("blog", "0001_initial")]
    operations = [migrations.RunPython(backfill_slugs, reverse_func)]

ضغط التهجيرات

output
# بعد الكثير من التهجيرات، ضغطها:
python manage.py squashmigrations blog 0001 0020

# ينشئ 0001_squashed_0020 الذي يحل محل 20 ملفًا
# يتجنب المطورون الجدد تشغيل 20 تهجيرة

تهجيرات بدون توقف

NOTEلإضافة عمود NOT NULL دون تعطيل الإنتاج:
  1. النشر 1: إضافة عمود قابل للقيم الفارغة
  2. النشر 2: تعبئة البيانات عبر تهجيرة بيانات
  3. النشر 3: تعديل الكود للاستخدام/الضبط
  4. النشر 4: تهجيرة ALTER NOT NULL

إعادة تسمية نموذج

output
# إذا أعدت تسمية Article -> Post، سيقترح Django
# إنشاء نموذج جديد + حذف القديم (فقدان البيانات!)

# الحل: تعديل التهجيرة المولدة
operations = [
    migrations.RenameModel("Article", "Post")
]

# نفس الشيء للحقول
operations = [
    migrations.RenameField("Post", "summary", "excerpt")
]

Django vs Flask vs FastAPI

NOTEالهدف — فهم فلسفة Django ومتى تختاره.

مقارنة سريعة

المعيارDjangoFlaskFastAPI
الفلسفةBatteries includedMicro frameworkAPI first, async
ORMمدمجSQLAlchemy خارجيSQLAlchemy خارجي
Adminمولد تلقائيًايُكتب يدويًايُكتب يدويًا
Authكاملإضافاتكتابة JWT يدويًا
TemplatesDjango templatesJinja2Jinja2 اختياري
Async أصليجزئي (4.x)لانعم
التعلمأطولبسيط جدًامتوسط

متى تختار Django

متى لا تختار Django

هندسة MTV

output
+----------+   request    +------+   query   +-------+
|  Browser | -----------> | View | --------> | Model |
+----------+              +------+           +-------+
     ^                       |                   |
     |                       v                   v
     |                  +----------+         +-------+
     +----------------- | Template |<--------|  DB   |
                        +----------+         +-------+

# M = Model (ORM، قاعدة البيانات)
# T = Template (HTML)
# V = View (منطق التحكم، في MVC هو الـ Controller)

منظومة Django

الإصدارات وLTS

NOTEإصدارات Django:
  • Django 5.0+ : الحالي
  • Django 4.2 LTS : مدعوم حتى 2026
  • الدورة: إصدار جديد كل 8 أشهر
  • LTS كل سنتين، مدعومة 3 سنوات

الشركات التي تستخدم Django

الملخص

NOTEللتذكر
  • Django = batteries included لتطبيقات الويب المعقدة
  • هندسة MTV (Model, Template, View)
  • مثالي: SaaS، CMS، التجارة الإلكترونية، لوحات التحكم
  • API نقي: يفضل DRF أو FastAPI

الفصل 07 – المشروع النهائي: النماذج والإدارة

دفتر الشروط

NOTEالهدف — بناء متجر إلكتروني بسيط يحتوي على: كتالوج، سلة، طلبات، إدارة أعمال.

هيكل المشروع

output
myshop/
  manage.py
  myshop/
    settings/
    urls.py
    wsgi.py
  accounts/
    models.py            # User custom
    views.py
    urls.py
  catalog/
    models.py            # Category, Product
    views.py
    admin.py
  cart/
    cart.py              # classe Cart (session)
    views.py
  orders/
    models.py            # Order, OrderItem
    views.py
    admin.py
  templates/
  static/
  media/

accounts/models.py

output
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    phone = models.CharField(max_length=20, blank=True)
    address = models.TextField(blank=True)
    city = models.CharField(max_length=100, blank=True)
    postal_code = models.CharField(max_length=10, blank=True)
    country = models.CharField(max_length=2, default="CA")
    
    class Meta:
        ordering = ["-date_joined"]
    
    def __str__(self):
        return self.username

catalog/models.py

output
from django.db import models
from django.urls import reverse
from django.utils.text import slugify

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=120, unique=True)
    description = models.TextField(blank=True)
    parent = models.ForeignKey("self", null=True, blank=True,
                                on_delete=models.CASCADE, related_name="children")
    
    class Meta:
        verbose_name_plural = "Categories"
        ordering = ["name"]
    
    def __str__(self): return self.name
    
    def get_absolute_url(self):
        return reverse("catalog:category", kwargs={"slug": self.slug})

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="products")
    name = models.CharField(max_length=200)
    slug = models.SlugField(max_length=220, unique=True)
    description = models.TextField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    image = models.ImageField(upload_to="products/%Y/%m/")
    stock = models.PositiveIntegerField(default=0)
    available = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["slug"]),
            models.Index(fields=["available", "-created_at"]),
        ]
    
    def __str__(self): return self.name
    
    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)
    
    def get_absolute_url(self):
        return reverse("catalog:product", kwargs={"slug": self.slug})
    
    @property
    def in_stock(self):
        return self.available and self.stock > 0

orders/models.py

output
class Order(models.Model):
    class Status(models.TextChoices):
        PENDING = "pending", "En attente"
        PAID = "paid", "Paye"
        SHIPPED = "shipped", "Expedie"
        DELIVERED = "delivered", "Livre"
        CANCELED = "canceled", "Annule"
    
    user = models.ForeignKey("accounts.User", on_delete=models.PROTECT, related_name="orders")
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
    address = models.TextField()
    city = models.CharField(max_length=100)
    postal_code = models.CharField(max_length=10)
    country = models.CharField(max_length=2)
    total = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    paid_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        ordering = ["-created_at"]
    
    def __str__(self): return f"Order #{self.id}"

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="items")
    product = models.ForeignKey("catalog.Product", on_delete=models.PROTECT)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField(default=1)
    
    @property
    def subtotal(self): return self.price * self.quantity

catalog/admin.py

output
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ["name", "parent", "product_count"]
    prepopulated_fields = {"slug": ("name",)}
    search_fields = ["name"]
    
    def product_count(self, obj):
        return obj.products.count()

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ["name", "category", "price", "stock", "available"]
    list_filter = ["available", "category", "created_at"]
    list_editable = ["price", "stock", "available"]
    search_fields = ["name", "description"]
    prepopulated_fields = {"slug": ("name",)}
    list_per_page = 50

orders/admin.py

output
class OrderItemInline(admin.TabularInline):
    model = OrderItem
    extra = 0
    readonly_fields = ["product", "price", "quantity", "subtotal"]
    can_delete = False

@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ["id", "user", "status", "total", "created_at", "paid_at"]
    list_filter = ["status", "created_at"]
    search_fields = ["id", "user__username", "user__email"]
    inlines = [OrderItemInline]
    actions = ["mark_shipped"]
    date_hierarchy = "created_at"
    
    @admin.action(description="Marquer comme expedie")
    def mark_shipped(self, request, queryset):
        queryset.update(status=Order.Status.SHIPPED)
va-plus-loin

يغطي هذا المقال المقتطفات الأكثر فائدة — الدورة الكاملة Python Django Fullstack (8 فصول، 24 درسًا، تمارين مصححة ومشروع نهائي) تأخذك إلى النهاية.

./acceder-au-cours-complet cours gratuit : Vibe Coding

الأسئلة الشائعة

كم من الوقت يستغرق تعلم Python Django Fullstack؟
مع تقدم منظم (8 فصول، 24 درسًا قصيرًا وعمليًا)، يمكن الوصول إلى مستوى تشغيلي في بضعة أسابيع بمعدل 30 إلى 60 دقيقة يوميًا. المهم هو تطبيق كل مفهوم فورًا.
هل هناك متطلبات سابقة؟
تكفي أساسيات الحاسوب. إذا كنت تعرف استخدام الطرفية وقراءة كود بسيط، فأنت جاهز.
من أين نبدأ عمليًا؟
طبّق أوامر هذا المقال، ثم تابع الدورة الكاملة Python Django Fullstack: تربط الـ24 درسًا بالترتيب مع تمارين ومشروع نهائي.

📬 هل تريد تلقي هذا النوع من الأدلة كل أسبوع؟ اشترك مجانًا — كود حقيقي، بدون كلام زائد.