اگر در دنیای جنگو (Django) دست به کد شده باشید، حتماً میدانید که ساختارهای دیتابیس بدون چیدمان درست، خیلی زود پروژه را به بنبست میرسانند. تعریف چند کلاس ساده به عنوان مدل، کار سختی نیست؛ چالش اصلی زمانی شروع میشود که پروژهای با دیتای واقعی دستتان میگیرید و باید روابط پیچیده، کوئریهای بهینه و پایداری دادهها را در محیط پروداکشن مدیریت کنید.
در این مقاله آموزشگاه کندو، بدون تعاریف تئوریک، اصول کاربردی کار با مدل ها در جنگو را در قالب سناریوهای بازار کار بررسی میکنیم. یاد میگیرید چطور روابط One-to-Many و Many-to-Many را بدون افت سرعت پیاده کنید و با متدهای کاستوم، منطق دیتابیس را بهینهتر پیش ببر
مدلها در جنگو و نقش ORM
هر مدل در جنگو، یک کلاس پایتونی ساده است که مثل یک آینه، یک جدول را در دیتابیس شما نمایندگی میکند. یعنی ویژگیهای (Attributes) این کلاس، همان ستونهای جدول دیتابیس شما هستند.
بزرگترین مزیت این ساختار، استفاده از واسطی به نام Object-Relational Mapping است. ORM به شما اجازه میدهد بدون نوشتن حتی یک خط کد SQL خام، تمام عملیات CRUD (ایجاد، خواندن، آپدیت و حذف) را با متدهای پایتونی جلو ببرید.
این ابزار سرعت توسعه شما را چند برابر میکند، کدهای پروژه را ایمن نگه میدارد و انتقال از یک دیتابیس به دیتابیس دیگر (مثلاً سوئیچ از SQLite به PostgreSQL) را بدون تغییر در کدهای اصلی ممکن میسازد. یادگیری عمیق این لایههای ارتباطی، همان مرز باریکی است که متخصصان را در بازار کار متمایز میکند و در یک دوره جنگو عملی، تمرکز اصلی روی همین بخش است.
ساخت اولین مدل در جنگو
فرض کنید میخواهیم سیستم مدیریت یک وبلاگ فنی را بالا بیاوریم. بعد از مراحل اولیه و آموزش نصب جنگو در سیستم، اولین قدم برای کار با دادهها، ایجاد یک اپلیکیشن (مثلاً به نام blog) و تعریف مدلِ پستهای سایت است.
برای شروع، فایل models.py را باز کرده و ساختار زیر را پیاده میکنیم. در این سناریو از پرکاربردترین فیلدهای دیتابیس استفاده شده است:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
def __str__(self):
return self.titleتحلیل فنی فیلدهای بالا:
- CharField و TextField: برای متون محدود (مثل عنوان) حتماً سقف طول (max_length) را مشخص کنید تا دیتابیس فضای بهینهای تخصیص دهد. برای متون طولانی از TextField استفاده میشود.
- SlugField: آپشن unique=True در این فیلد تضمین میکند که آدرس URL هر پست در کل دیتابیس منحصربهفرد باشد که یک نکته کلیدی در سئو است.
- DateTimeField: با تنظیم auto_now_add=True، زمان دقیق ثبت رکورد به صورت اتوماتیک توسط جنگو ذخیره میشود و نیازی به پر کردن دستی آن نیست.
- متد __str__: این متد در پشت صحنه به جنگو میگوید که در پنل ادمین یا ترمینال، به جای نمایش یک آبجکت گنگ، عنوان خود پست را به ما نشان دهد تا مدیریت دیتا سادهتر شود.

فیلدهای پرکاربرد مدلها در پروژههای واقعی
برای طراحی یک دیتابیس بهینه، باید بدانید هر دیتا را در چه فیلدی قرار دهید تا هم حجم دیتابیس الکی بالا نرود و هم ولیدیشن (Validation) دادهها در لایه مدل به درستی انجام شود. در ادامه، مهمترین فیلدهای کاربردی را دستهبندی کردهایم:
۱. فیلدهای متنی و شناسهها
- EmailField: ساختار ورودی را چک میکند تا حتماً فرمت ایمیل داشته باشد.
- UUIDField: برای زمانهایی که نمیخواهید ID عددی و متوالی (مثل 1, 2, 3) در URL لو برود؛ یک شناسه ۳۲ کاراکتری کاملاً منحصربهفرد و تصادفی تولید میکند.
۲. فیلدهای عددی
- IntegerField: برای ذخیره اعداد صحیح (مثلا تعداد موجودی انبار).
- DecimalField: حیاتی برای قیمتها و مباحث مالی. این فیلد دو آرگومان اجباری max_digits (کل ارقام) و decimal_places (ارقام بعد از اعشار) را میگیرد تا دقت محاسبات ریاضی در دیتابیس حفظ شود.
۳. فیلدهای وضعیت و منطقی
- BooleanField: فقط مقدار True یا False میگیرد. پرکاربرد برای وضعیتهایی مثل فعال/غیرفعال بودن کاربر یا تایید شدن یک کامنت.
۴. فیلدهای فایل و رسانه
- FileField و ImageField: برای آپلود اسناد و تصاویر. با آرگومان upload_to=’uploads/’ مسیر ذخیرهسازی فایلها را در پوشه Media مشخص میکنید. (نکته: برای استفاده از ImageField باید کتابخانه Pillow روی پروژه نصب باشد).
۵. فیلدهای زمان و تاریخ
- DateField و DateTimeField: اگر آرگومان auto_now=True را به آنها بدهید، هر بار که رکورد آپدیت یا ویرایش شود، زمان تغییرات به طور خودکار بهروزرسانی میشود (مناسب برای فیلد updated_at).
روابط بین مدلها؛ مدیریت دیتای متصل به هم
در پروژههای واقعی، مدلها ایزوله نیستند. قدرت اصلی و از مهمترین کاربرد های جنگو در توسعه وب، توانایی آن در مدیریت روابط پیچیده بین جداول با کمترین میزان کدنویسی است. سه نوع رابطه اصلی داریم که با سناریوهای ملموس آنها را پیاده میکنیم:
۱. رابطه یکبهچند (One-to-Many / ForeignKey)
زمانی استفاده میشود که یک رکورد در جدول A بتواند به چند رکورد در جدول B متصل شود، اما عکس آن برقرار نباشد. مثل رابطه «نویسنده» و «پستها».
class Author(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='posts')نکته فنی: آپشن on_delete=models.CASCADE یعنی اگر یک نویسنده حذف شد، تمام پستهای او هم به صورت خودکار حذف شوند. با related_name میتوانید از سمت نویسنده، خیلی راحت به پستهایش دسترسی داشته باشید (مثلاً author.posts.all).
۲. رابطه چندبهچند (Many-to-Many)
زمانی که یک رکورد از جدول A به چند رکورد از جدول B وصل شود و برعکس. مثل رابطه «پستها» و «تگها» (یک پست چند تگ دارد و یک تگ میتواند روی چند پست بنشیند).
class Tag(models.Model):
title = models.CharField(max_length=50)
class Post(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(Tag, related_name='posts')نکته فنی: جنگو به صورت خودکار یک جدول واسط (Join Table) در دیتابیس میسازد تا آیدیهای هر دو طرف را در آن نگهداری کند.
۳. رابطه یکبهیک (One-to-One)
وقتی یک رکورد در جدول A دقیقاً و فقط به یک رکورد در جدول B متصل باشد. پرکاربردترین سناریو، ساخت پروفایل اختصاصی برای کاربران است.
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
github_link = models.URLField(blank=True)کاربرد: با این کار دیتای پایه کاربر (مثل ایمیل و پسورد) را از دیتای جانبی (مثل بیوگرافی) جدا نگه میدارید تا لود اولیه جدول اصلی کاربر سبک بماند.
مدیریت مهاجرتها (Migrations)
تعریف مدلها در پایتون کافی نیست؛ دیتابیس باید از این تغییرات باخبر شود. سیستم Migrations در جنگو مثل یک سیستم کنترل نسخه (Git) برای دیتابیس شما عمل میکند. هر تغییری که در مدلها میدهید، باید طی دو گام زیر به جدولهای واقعی تبدیل شود:
۱. ساخت فایل مهاجرت: با دستور زیر، جنگو تغییرات مدل را بررسی کرده و یک فایل پایتونی در پوشه migrations میسازد که نقشه تغییرات است:
python manage.py makemigrations۲. اعمال روی دیتابیس: با اجرای دستور زیر، آن نقشهها تبدیل به کوئریهای SQL شده و روی دیتابیس فیزیکی پیاده میشوند:
python manage.py migrateخطای رایج: چالش اضافه کردن فیلد جدید (Non-nullable Fields)
اگر مدلی از قبل در دیتابیس رکورد داشته باشد و شما یک فیلد جدید (مثلاً updated_at) بدون مقدار پیشفرض به آن اضافه کنید، هنگام makemigrations با یک خطای معروف مواجه میشوید. جنگو از شما میپرسد: «تکلیف رکوردهای قدیمی چیست؟»
- راهکار اول: در ترمینال گزینه ۱ را بزنید و یک مقدار پیشفرض موقت (مثل timezone.now) تعیین کنید.
- راهکار اصولی: در لایه مدل، آرگومانهای null=True یا blank=True را به فیلد جدید اضافه کنید تا دیتابیس مقادیر خالی را برای رکوردهای قبلی بپذیرد.

کار با QuerySet؛ استخراج بهینه دادهها از دیتابیس
وقتی مدلها و روابط شکل گرفتند، زمان بیرون کشیدن دیتا به کمک کوئریستهاست. جنگو ابزارهای قدرتمندی برای فیلتر و تحلیل دادهها در اختیارتان میگذارد که یادگیری آنها، بخش مهمی از مسیر هر دوره برنامه نویسی جامع و تخصصی است:
فیلتر و مرتبسازی ابزارهای پایه
برای استخراج رکوردهای خاص از متد filter استفاده میکنیم. جنگو از سیستم Field Lookups (با دو آنداسکور __) برای شرطهای پیچیدهتر استفاده میکند:
# گرفتن پستهایی که در عنوان آنها کلمه Django هست و منتشر شدهاند
posts = Post.objects.filter(title__contains='Django', is_published=True)
# مرتبسازی بر اساس جدیدترینها
latest_posts = Post.objects.order_by('-created_at')محاسبات و تحلیلهای پیشرفته (Aggregate و Annotate)
- aggregate (تجميع دادهها): برای زمانی است که میخواهید یک خروجی واحد از کل جدول بگیرید (مثل میانگین قیمت یا تعداد کل پستها). خروجی این متد یک دیکشنری پایتونی است:
from django.db.models import Count
total_posts = Post.objects.aggregate(Count('id')) # {'id__count': 12}- annotate (دیتاهای محاسباتی متصل): به شما اجازه میدهد به هر رکورد، یک فیلد محاسباتیِ پویا اضافه کنید. مثلاً میخواهید نویسندهها را بکشید، اما کنار نام هر نویسنده، تعداد پستهایش هم ضمیمه شده باشد:
authors = Author.objects.annotate(num_posts=Count('posts'))
print(authors[0].num_posts)خط قرمز عملکرد: چالش N+1 Loops
بزرگترین اشتباه تازهکارها این است که روابط ForeignKey یا ManyToMany را درون یک حلقه for لود میکنند. این کار باعث میشود به ازای هر رکورد، یک کوئری جداگانه به دیتابیس زده شود و سرور زیر بار سنگین برود.
- راهحل: برای روابط یکبهچند از متد ()select_related و برای روابط چندبهچند از ()prefetch_related در ابتدای کوئری خود استفاده کنید تا دیتای متصل، در همان کوئری اول به صورت Join شده لود شود.
متدهای مهم مدل؛ کاستومایز کردن رفتار دادهها
یکی از قابلیتهای قدرتمند مدلها در جنگو، امکان اورراید (Override) کردن یا بازنویسی متدهای پیشفرض کلاس برای تزریق رفتارهای سفارشی به ساختار دادهها است.
بازنویسی متد save
فرض کنید میخواهید قبل از ذخیره شدن پست در دیتابیس، فیلد slug به صورت خودکار از روی عنوان (title) ساخته شود تا کاربر نیازی به پر کردن دستی آن نداشته باشد:
from django.utils.text import slugify
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)نکته : حتماً باید در انتهای متد، (*super().save(*args, kwargs را صدا بزنید تا منطق اصلی ذخیرهسازی جنگو قطع نشود.
بازنویسی متد delete
اگر بخواهید به جای حذف فیزیکی و کامل دیتا از دیتابیس (Hard Delete)، از حذف نرم (Soft Delete) استفاده کنید تا دیتاها قابل بازیابی باشند، میتوانید متد دلیت را دستکاری کنید:
class Post(models.Model):
is_deleted = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()تنظیمات Meta در مدلها
کلاس داخلی Meta درون مدلها، اطلاعاتی درباره خودِ مدل (Metadata) به جنگو میدهد؛ یعنی مشخص میکند جدول دیتابیس با چه قوانینی رفتار کند و در پنل ادمین چطور نمایش داده شود.
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
ordering = ['-price']
verbose_name = "محصول"
verbose_name_plural = "محصولات"پرکاربردترین آپشنهای کلاس Meta:
- ordering: ترتیب پیشفرض لود شدن دیتاها را مشخص میکند. مثلاً [‘price-‘] یعنی محصولات همیشه به صورت پیشفرض از گرانترین به ارزانترین مرتب شوند.
- verbose_name و verbose_name_plural: نامهای فارسی و خوانا برای مدل در پنل مدیریت جنگو تعیین میکند تا به جای نمایش کلمات انگلیسی مثل “Products”، ظاهر ادمین کاملاً بومی و استاندارد شود.
- db_table: اگر میخواهید نام پیشفرض جدول در دیتابیس را به اسم دلخواه خود تغییر دهید، از این آپشن استفاده کنید.
بهینهسازی Queryها
کاهش تعداد خطوط ارتباطی با دیتابیس، مرز بین یک کد جونیور و یک سیستم پروداکشنی پایدار است. همانطور که پیشتر اشاره شد، رفع خطای لوپ N+1 با دو متد زیر انجام میشود:
استفاده از select_related (روابط یکبهچند و یکبهیک)
این متد پشت صحنه یک کوئری SQL JOIN میزند و دیتای جدول متصل را همراه با جدول اصلی یکبار برای همیشه میکشد:
# غلط: به ازای هر پست، یک کوئری برای کشیدن نویسنده میزند
posts = Post.objects.all()
# درست: همه پستها و نویسندهها را در یک کوئری Join شده میکشد
posts = Post.objects.select_related('author').all()استفاده از prefetch_related (روابط چندبهچند)
جنگو برای روابط چندبهچند نمیتواند از JOIN ساده استفاده کند. متد prefetch_related دیتای جدول دوم را در یک کوئری مجزا اما بهینهشده به صورت یکجا میکشد و در لایه پایتون آنها را به هم مپ میکند:
# بهینهسازی لود تگهای پستها
posts = Post.objects.prefetch_related('tags').all()دو نکته تجربی برای سرعت بیشتر:
- ()onlyو ()defer: اگر جدولی دارید که یک فیلد متنی سنگین دارد، با ()defer میتوانید بگویید آن فیلد لود نشود مگر زمانی که صراحتاً صدایش میزنید.
- ()exists: اگر فقط میخواهید چک کنید آیا رکوردی با این مشخصات وجود دارد یا نه، به جای ()count یا ()filter از ()exists استفاده کنید تا دیتابیس بدون لود کردن دیتا، فقط یک خروجی Boolean سبک برگرداند.
نتیجهگیری
تسلط بر مدلها و ابزار ORM در جنگو، زیربنای ساخت سیستمهای پایدار و پرسرعت است. در این مسیر، صرفاً نوشتن کلاسهای پایتونی اهمیت ندارد؛ بلکه هنر اصلی شما در انتخاب درست فیلدها، مدیریت هوشمندانه روابط و ترجیح دادن متدهای بهینهسازی مثل select_related و prefetch_related به کوئریهای خام و سنگین است. با پیادهسازی این نکات تجربی و مهندسیشده، کدهای شما آمادگی لازم برای مواجهه با ترافیکهای واقعی پروداکشن را پیدا میکنند و کیفیت خروجی فنی شما یک لول بالاتر میرود.
سوالات متداول (FAQ)
۱. چه زمانی باید از null=True و چه زمانی از blank=True در لایه مدل استفاده کنیم؟ گزینه null مربوط به اجازه ذخیره مقدار خالی در دیتابیس است، اما blank مربوط به لایه ولیدیشن و الزامی بودن یا نبودن پر شدن فیلد در فرمهای جنگو است.
۲. تفاوت اصلی بین دستور makemigrations و migrate در چیست؟ دستور اول تغییرات کدهای پایتونی مدل شما را اسکن کرده و فایل نقشه (Migration File) میسازد؛ دستور دوم این نقشهها را تبدیل به جدولهای واقعی در دیتابیس میکند.
۳. آیا استفاده از ORM جنگو همیشه بهترین گزینه است یا گاهی باید SQL خام بنویسیم؟ برای اکثر پروژهها ORM به دلیل امنیت و سرعت توسعه بهترین است؛ اما برای کوئریهای گزارشگیری به شدت پیچیده و آماری، نوشتن SQL خام عملکرد بهتری دارد.
