پیکربندی Models


مقدمه


قبل از شروع این فصل، لازم است با نحوه کار پایگاه داده در جنگو و نقش مدل‌ها در این فرآیند آشنا شویم. از آنجا که اکثر برنامه‌های وب مدرن برای مدیریت و ذخیره‌سازی داده‌ها به یک پایگاه داده رابطه‌ای متکی هستند، جنگو نیز ابزاری قدرتمند و خودکار برای مدیریت این داده‌ها در اختیار توسعه‌دهندگان قرار می‌دهد.

قلب این سیستم، ORM جنگو (Object-Relational Mapper) است — یعنی مبدل شیء-رابطه‌ای — که اجازه می‌دهد با پایگاه داده، نه از طریق دستورات SQL، بلکه با استفاده از کلاس‌های پایتون کار کنیم. هر کلاس در این سیستم، یک مدل نامیده می‌شود و نماینده یک جدول در پایگاه داده است. هر فیلد در این کلاس، معادل یک ستون در جدول می‌باشد و روابط بین مدل‌ها (مثل ForeignKey یا ManyToMany) نیز معادل روابط بین جداول در دیتابیس هستند.

⚠️ یادآوری: خطای "You have 18 unapplied migration(s)"

همان‌طور که به یاد داریم، در هنگام اجرای پروژه با دستور python manage.py runserver، با پیام هشدار زیر مواجه می‌شدیم:

you have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions

این پیام به این معنی است که ساختار پایگاه داده حتی برای اپلیکیشن‌های داخلی جنگو، هنوز در دیتابیس اعمال نشده است. بدون اعمال این migrationها، بخش‌هایی مثل سیستم کاربران، ورود به پنل ادمین و مدیریت sessionها به درستی کار نخواهند کرد. برای رفع این هشدار و پیاده‌سازی ساختار اولیه پایگاه داده، باید سرور را با [Ctrl] + [C] متوقف کرده و دستور زیر را در ترمینال اجرا کنیم:

(venv) python manage.py migrate

با اجرای این دستور جنگو به صورت خودکار تمام migrationهای مربوط به اپلیکیشن‌های داخلی جنگو یا اپلیکیشن‌های ایجاد شده همانند coreapp و clientapp که در INSTALLED_APPS لیست شده‌اند را در پایگاه داده اعمال می‌کند. migrationهای مربوط به مدل‌های  اپلیکیشن‌های داخلی جنگو (مثل auth, admin, sessions و ...) از قبل در پکیج جنگو وجود دارند و نیازی به ساخت migration برای آن‌ها وجود ندارد.

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

اجرای migrate در ابتدای پروژه، فقط مدل‌های داخلی جنگو را پیاده‌سازی می‌کند اما این مرحله ضروری بوده و پایه‌ای برای کار با داده‌ها در آینده فراهم می‌کند.  همانطور که می‌بینیم، فقط migrationهای مربوط به اپلیکیشن‌های داخلی اعمال می‌شوند — چرا که هنوز مدلی برای coreapp و clientapp نساخته‌ایم و migrationی برای اپ‌ها وجود ندارد.

هر کدام از این اپلیکیشن‌ها مدل‌های داخلی دارند و migrationهای از پیش ساخته شده در خودشان دارند

  • auth ← مدل User, Group, Permission
  • admin ← مدل LogEntry
  • sessions ← مدل Session
  • contenttypes ← مدل ContentType

 پس از اجرای موفقیت‌آمیز، جداول لازم برای عملکرد صحیح جنگو در دیتابیس ایجاد شده و می‌توان بدون هشدار، سرور را دوباره اجرا نمود.

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
September 20, 2025 - 12:09:17
Django version 5.2.6, using settings 'tutorial.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
For more information on production servers see: https://docs.djangoproject.com/en/5.2/howto/deployment/

 

پنل ادمین


جنگو به‌طور پیش‌فرض یک پنل مدیریتی قدرتمند و کاربردی در اختیار قرار می‌دهد که امکان مدیریت رکوردهای مدل‌های تعریف‌شده را — بدون نیاز به نوشتن کد اضافی — فراهم می‌کند. این پنل ابزاری ایده‌آل برای مدیران سیستم، توسعه‌دهندگان و حتی کاربران فنی است تا بتوانند به راحتی داده‌ها را مشاهده، ایجاد، ویرایش یا حذف کنند.

از آنجا که در فایل settings.py، اپلیکیشن 'django.contrib.admin' به‌طور پیش‌فرض در لیست INSTALLED_APPS قرار دارد، نیازی به فعال‌سازی نیست. تنها با اجرای سرور و مراجعه به آدرس admin/ ، پنل ادمین در دسترس خواهد بود.

پیش از هرگونه تعامل با پنل مدیریت جنگو، ضروری است که یک حساب کاربری با بالاترین سطح دسترسی — موسوم به سوپریوزر (Superuser) — ایجاد گردد. این حساب، امکان مدیریت کامل مدل‌ها و داده‌های سیستم را فراهم می‌کند.

(venv) python manage.py createsuperuser

یرای ایجاد کاربری superuser، نیاز به نام کاربری، ایمیل (اختیاری) و رمز عبور مورد نظر خواهد بود. 

Username (leave blank to use 'admin'): admin
Email address: admin@admin.com
Password *********
Password (again) *********
Superuser created successfully.

پس از ایجاد کاربری superuser، امکان دسترسی به رابط مدیریتی جنگو فراهم می‌شود. کافی‌ست با وارد کردن اطلاعات تعریف شده برای سوپریوزر در صفحه ورود (/admin)، به محیط پنل ادمین وارد شد.

در صفحه اصلی پنل مدیریت، دو مدل User و Group را مشاهده خواهیم نمود. این مدل‌ها جزو ساختارهای پایه‌ای و پیش‌فرض جنگو هستند که توسط اپلیکیشن django.contrib.auth ارائه می‌شوند و برای مدیریت سیستم احراز هویت و دسترسی‌ها طراحی شده‌اند.

اگر وارد گزینه User شویم، لیست تمامی کاربران ثبت‌شده در سیستم — از جمله کاربر سوپریوزری که در مرحله قبل ایجاد نمودیم— نمایش داده می‌شود. می‌توان روی نام کاربری کلیک کرده و اطلاعات آن را مشاهده، ویرایش یا حتی غیرفعال نمود.. این امکان شامل تغییر نام کاربری، ایمیل، رمز عبور، یا تنظیمات دسترسی (مانند فعال/غیرفعال بودن حساب یا دسترسی‌های ادمین) می‌شود.

همچنین مدل Group این امکان را می‌دهد تا کاربران را در گروه‌هایی با دسترسی‌های مشابه دسته‌بندی نماییم— که برای مدیریت پیشرفته‌تر سطوح دسترسی بسیار کاربردی است.

تعریف مدل‌ها


در جنگو، امکان تعریف مدل‌های پایگاه داده تنها با استفاده از کد پایتون فراهم شده است — بدون نیاز به نوشتن حتی یک خط SQL. برای این کار، کافی است در فایل models.py داخل اپلیکیشن مربوطه، یک کلاس پایتونی ایجاد نموده که از کلاس django.db.models.Model ارث‌بری کند. هر کلاس معادل یک جدول در پایگاه داده خواهد بود و هر فیلد تعریف‌شده در آن، معادل یک ستون در جدول محسوب می‌شود.

coreapp/models.py

from django.db import models
import uuid

class Project(models.Model):
    title = models.CharField(max_length=100)
    area = models.CharField(max_length=50)
    content = models.TextField(null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

 

در جنگو، زمانی که کلاسی از models.Model ارث‌بری می‌کند، مشخص می‌کند که کلاس، یک مدل پایگاه داده است و نباید به عنوان یک کلاس معمولی پایتون در نظر گرفته شود. این ارث‌بری باعث می‌شود جنگو فیلدهای تعریف‌شده در کلاس را تحلیل کرده و بر اساس آن‌ها ساختار یک جدول در پایگاه داده را طراحی کند.

با تعریف کلاس فوق، جنگو به‌صورت خودکار جدولی با نام coreapp_project (با توجه به نام اپلیکیشن در اینجا coreapp) در دیتابیس ایجاد خواهد کرد. اما این فرآیند به‌صورت خودکار و فوری اتفاق نمی‌افتد؛

برای تبدیل مدل به جدول فیزیکی در دیتابیس، باید پس از تعریف مدل، ابتدا دستور python manage.py makemigrations را اجرا نموده تا جنگو تغییرات را شناسایی کرده و یک فایل migration در پوشه migrations/ اپلیکیشن — حاوی دستورات لازم برای ایجاد یا تغییر جدول — ایجاد کند، و سپس با اجرای دستور python manage.py migrate، این تغییرات در پایگاه داده اعمال شده و جدول مربوطه ساخته شود..

(venv) python manage.py makemigrations
(venv) python manage.py migrate

با اجرای دستورات، یک جدول با نام coreapp_project در دیتابیس ایجاد شده— که شامل ستون‌های created ،demo ،content ،subject  ،title و id خواهد بود.

جدول ایجاد شده به‌طور خودکار در پنل ادمین جنگو نمایش داده نخواهد شد. برای اینکه بتوان از طریق رابط کاربری ادمین (/admin) با داده‌های این مدل تعامل داشته باشیم — یعنی رکوردها را مشاهده، ایجاد، ویرایش یا حذف کنیم — باید مدل را در سیستم مدیریت جنگو ثبت — register — نماییم. این کار با ویرایش فایل admin.py درون دایرکتوری اپلیکیشن مربوطه انجام می‌شود. کافی‌ست مدل مورد نظر را وارد کرده و با استفاده از متد admin.site.register() آن را در پنل ادمین ثبت نمود.

coreapp/admin.py

from django.contrib import admin
from . import models

# Register your models here.
admin.site.register(models.Project)

 

حال، پنل ادمین جنگو — Admin Interface — به‌صورت خودکار و بدون نیاز به نوشتن هیچ کد اضافی، امکانات کامل برای مدیریت داده‌های مدل Project را در اختیار قرار می‌دهد که می‌توان رکوردها را مشاهده، ایجاد، ویرایش یا حذف نمود— دقیقاً همان‌طور که در سیستم‌های مدیریت محتوا یا پنل‌های مدیریتی حرفه‌ای انتظار می‌رود. این ویژگی، جنگو را به ابزاری بسیار کاربردی برای توسعه سریع و مدیریت روزمره داده‌ها تبدیل می‌کند.

در صورتی که از طریق پنل ادمین جنگو اقدام به ایجاد رکورد جدیدی از مدل Project نماییم — با انتخاب گزینه  Add Project + — فرمی نمایش داده خواهد شد که فیلدهای آن دقیقا مطابق با فیلدهای تعریف‌شده در مدل خواهد بود. باید توجه داشت که فیلدهایی مانند id (کلید اصلی) و created و updated ( به دلیل استفاده از auto_now_add=True و auto_now=True ) در این فرم نمایش داده نمی‌شوند، زیرا مقادیر آن‌ها به‌صورت خودکار توسط جنگو تولید و مدیریت می‌شوند و نیازی به ورود دستی توسط کاربر ندارند.

پس از افزودن رکوردهای جدید به مدل project از طریق پنل ادمین، ممکن است نحوه نمایش این آیتم‌ها در لیست مدل‌ها، کمی عجیب و یا غیرکاربردی به نظر برسد — چرا که جنگو به‌صورت پیش‌فرض، هر نمونه (instance) از مدل را با یک رشته عمومی مانند project object (0516b...) نمایش می‌دهد که فقط شامل نام کلاس و شناسه منحصربه‌فرد (UUID) آن است.

این نحوه نمایش برای کاربران ادمین — و حتی توسعه‌دهندگان — کاربردی نبوده و تشخیص رکوردها را دشوار می‌کند. برای رفع این موضوع و نمایش اطلاعات معنادار (مانند عنوان پروژه)، باید در کلاس مدل، متد استاندارد __str__ (در پایتون ۳) را تعریف نموده تا جنگو بتواند یک نمایش خوانا و معنادار از هر رکورد را در پنل ادمین یا هر جای دیگری که نیاز به نمایش متنی مدل باشد، ارائه دهد.

 

coreapp/models.py

class project(models.Model):
    title = models.CharField(max_length=100)
    area = models.CharField(max_length=50)
    content = models.TextField(null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

    def __str__(self):
        return self.title

پس از تعریف متد __str__ در مدل project و بازگرداندن فیلدی معنادار (مانند title)، با بازگشت به پنل ادمین جنگو، مشاهده خواهد شد که رکوردهای ایجادشده به‌صورت title به‌عنوان نمایش خوانا و قابل‌درک از هر رکورد در لیست‌ها، منوها و فرم‌های انتخاب ظاهر می‌شود. این تغییر ساده، تجربه کاربری در پنل ادمین را به‌طور چشمگیری بهبود بخشیده و مدیریت داده‌ها را برای کاربران ادمین، شفاف‌تر و کارآمدتر می‌کند.

تعریف فیلدها


در قلب هر مدل، فیلدها (Fields) قرار دارند — اجزایی که ستون‌های جدول را تشکیل می‌دهند و نوع، محدودیت‌ها و رفتار داده‌ها را مشخص می‌کنند. در این مقاله آموزشی، به‌صورت پاراگرافی و کاربردی، تمامی بخش‌های لازم برای تعریف یک فیلد در مدل جنگو را بررسی می‌کنیم.

⮜ نام فیلد: همانند یک متغیر پایتون

اولین و ساده‌ترین بخش در تعریف یک فیلد، نام آن است. این نام باید مطابق قوانین نام‌گذاری متغیرهای پایتون باشد — یعنی با حروف، اعداد یا زیرخط (_) شروع شود و حروف بزرگ و کوچک در آن مهم باشند. این نام در کد برای دسترسی به فیلد استفاده می‌شود. جنگو به‌صورت خودکار نام فیلد را برای ستون دیتابیس نرمال‌سازی می‌کند — مثلاً firstName به first_name تبدیل می‌شود — اما در کد همچنان از نام اصلی استفاده می‌گردد.


 

⮜ تایپ فیلد: تعیین جنس داده

تایپ یک فیلد تعیین می‌کند که داده‌های ذخیره شده در آن چه فرمتی داشته و چه محدودیت‌هایی دارند. جنگو مجموعه غنی‌ای از انواع فیلد را ارائه می‌دهد، از CharField برای رشته‌های کوتاه، تا TextField برای متن‌های طولانی، IntegerField برای اعداد صحیح، BooleanField برای مقادیر درست/غلط، و فیلدهای رابطه‌ای مثل ForeignKey و ManyToManyField برای ایجاد ارتباط بین مدل‌ها. انتخاب نوع صحیح فیلد، نه‌ تنها صحت داده‌ها را تضمین می‌کند، بلکه در بهینه‌سازی پایگاه داده و جلوگیری از خطاها نیز بسیار مؤثر است.


 

⮜ پارامترهای اجباری و اختیاری: تنظیم دقیق رفتار فیلد

هر تایپ از فیلد ممکن است به پارامترهای اجباری نیاز داشته باشد. برای مثال، CharField بدون مشخص کردن max_length قابل تعریف نیست — چرا که جنگو باید بداند حداکثر طول مجاز رشته چقدر است. اما فراتر از این، می‌توان با استفاده از پارامترهای اختیاری، رفتار فیلد را دقیق‌تر کنترل نمود. پارامترهایی مثل null که اجازه ذخیره مقدار NULL در دیتابیس را می‌دهد، یا blank که تعیین می‌کند فیلد در فرم‌ها می‌تواند خالی باشد. همچنین می‌توان از default برای تعیین مقدار پیش‌فرض، verbose_name برای نمایش نام خوانا در پنل ادمین و فرم‌ها، یا help_text برای ارائه راهنمایی به کاربر استفاده کرد. این پارامترها انعطاف‌پذیری بالایی در اختیار قرار می‌دهند تا فیلدها را بطئر دقیق مطابق با نیازهای پروژه تنظیم نمود.


 

⮜ فیلدهای رابطه‌ای: اتصال مدل‌ها به یکدیگر

یکی از قدرتمندترین ویژگی‌های جنگو، مدیریت روابط بین مدل‌هاست. با استفاده از فیلدهایی مثل ForeignKey (رابطه یک-به-چند)، OneToOneField (رابطه یک-به-یک) و ManyToManyField (رابطه چند-به-چند)، می‌توان مدل‌ها را به هم متصل نمود. هنگام تعریف این فیلدها، علاوه بر نوع فیلد، باید مدل مقصد را نیز مشخص کرد — یعنی تعیین نمود که این فیلد به کدام مدل دیگر اشاره می‌کند. همچنین پارامتر on_delete برای ForeignKey و OneToOneField اجباری بوده و تعیین می‌کند در صورت حذف رکورد مرتبط، چه اتفاقی بیفتد — به عنوان مثال CASCADE به معنی حذف خودکار رکوردهای وابسته است. با استفاده از related_name نیز می‌توان دسترسی معکوس از مدل مقصد به مدل فعلی را با یک نام دلخواه تعریف نمود.


 

⮜ تنظیمات پیشرفته: ایندکس‌ها، متا و بهینه‌سازی

گاهی برای بهبود عملکرد یا اعمال منطق خاص، نیاز به تنظیمات پیشرفته‌تر هست. این تنظیمات معمولاً در کلاس Meta داخل مدل قرار می‌گیرند. به طور مثال می‌توان با indexes ایندکس‌های دیتابیسی روی فیلدهای پرتکرار ایجاد کرد تا جستجوها سریع‌تر انجام شوند و یا با ordering تعیین نمود که لیست رکوردها به‌صورت پیش‌فرض چگونه مرتب شوند. همچنین unique_together یا constraints می‌توانند برای اعمال محدودیت‌های پیچیده‌تر روی ترکیب فیلدها استفاده شوند. این بخش‌ها اختیاری هستند، اما در پروژه‌های واقعی و حرفه‌ای، نقش حیاتی در عملکرد و صحت داده‌ها دارند.

 

به طور خلاصه می‌توان اشاره کرد که تعریف صحیح و دقیق فیلدها در مدل‌های جنگو، پایه و اساس یک پروژه موفق است. هر فیلد باید با دقت طراحی شود — از نام‌گذاری شفاف گرفته تا انتخاب تایپ مناسب، تعیین محدودیت‌ها و پیکربندی رفتارهای پیش‌فرض. با یادگیری این مفاهیم، نه‌تنها مدل‌های قوی و پایدار ساخته خواهند شد، بلکه توسعه فرم‌ها، پنل ادمین، APIها و منطق کسب‌وکار را نیز تسهیل می‌نمایند. جنگو با ارائه ابزارهای قدرتمند و انعطاف‌پذیر، اجازه می‌دهد تا بدون نگرانی از جزئیات پایگاه داده، روی منطق کسب‌وکار تمرکز کرد. پس می‌بایست در تعریف فیلدها سرمایه‌گذاری کرد — چرا که این فیلدها، ستون‌های فقرات پروژه هستند.

 

تایپ‌های فیلد


وقتی در فریمورک جنگو شروع به ساخت مدل‌های داده‌ای می‌کنیم، اولین و مهم‌ترین تصمیمی که باید بگیریم، انتخاب تایپ فیلد (Field Type) مناسب برای هر ستون داده است. فیلدها در جنگو نه‌تنها ساختار داده‌ها را در پایگاه داده مشخص می‌کنند، بلکه نحوه نمایش، اعتبارسنجی، ذخیره‌سازی و حتی رفتار در فرم‌ها و پنل ادمین را نیز تعیین می‌کنند. در این بخش، به بررسی تمامی انواع رایج و پیشرفته فیلدهای جنگو می‌پردازیم — از ساده‌ترین‌ها تا فیلدهای رابطه‌ای و سفارشی.

در تعریف مدل اشاره شد که هر مدل (Model) یک کلاس پایتونی است که از django.db.models.Model ارث‌بری می‌کند. هر فیلد در این کلاس نیز، یک ویژگی (attribute) است که نمونه‌ای از یکی از کلاس‌های موجود در django.db.models را نگه می‌دارد — مثل CharField یا ForeignKey.

فیلدها ...

  • در زمان اجرای makemigrations و migrate به ستون‌های جدول در دیتابیس تبدیل می‌شوند.
  • در فرم‌های خودکار (ModelForm) و پنل ادمین، ویجت‌های مناسب را ایجاد می‌کنند.
  • اعتبارسنجی داده‌ها را بر اساس نوع و پارامترها انجام می‌دهند.
  • امکان اعمال محدودیت‌های دیتابیسی (مثل UNIQUE, NOT NULL) را فراهم می‌کنند.

انتخاب نوع صحیح فیلد، تأثیر مستقیمی بر کارایی، صحت داده‌ها و تجربه کاربری خواهد داشت.

🧱 دسته‌بندی کلی فیلدها

فیلدهای جنگو را می‌توان به چند دسته کلی تقسیم کرد:

    1. فیلدهای اسکالر (مقادیر ساده) — مثل متن، عدد، تاریخ
    2. فیلدهای رابطه‌ای — برای اتصال مدل‌ها به هم
    3. فیلدهای ساختاریافته و خاص — مثل فایل، تصویر، JSON
    4. فیلدهای عمومی و کمکی — مثل AutoField, GenericIPAddressField

در ادامه، هر دسته را به‌صورت جزء به جزء بررسی می‌کنیم.

فیلدهای اسکالر (Scalar Fields)


فیلدهای اسکالر، ساده‌ترین و رایج‌ترین نوع فیلدها هستند که مقادیر ساده را ذخیره می‌کنند — یعنی هر فیلد فقط یک مقدار ساده (مثل متن، عدد، تاریخ یا بولین) را نگه می‌دارد. این فیلدها مستقیماً به ستون‌های ساده در دیتابیس تبدیل می‌شوند.

الف) فیلدهای متنی:
    • CharField: برای رشته‌های کوتاه با طول مشخص (max_length اجباری است).
    • TextField: برای متن‌های بلند — بدون محدودیت طول.
    • EmailField: اعتبارسنجی ایمیل + محدودیت طول.
    • SlugField: برای URLها — فقط حروف، اعداد، خط تیره و زیرخط.
    • URLField: اعتبارسنجی آدرس اینترنتی.
 
ب) فیلدهای عددی:
    • IntegerField, BigIntegerField, SmallIntegerField: اعداد صحیح با دامنه‌های مختلف.
    • PositiveIntegerField, PositiveSmallIntegerField: فقط اعداد مثبت.
    • DecimalField: اعداد اعشاری با دقت بالا — مناسب امور مالی.
    • FloatField: اعداد اعشاری با دقت متوسط (با خطای گرد کردن).
 
ج) فیلدهای تاریخ و زمان:
    • DateField: فقط تاریخ (YYYY-MM-DD)
    • DateTimeField: تاریخ + زمان (YYYY-MM-DD HH:MM:SS)
    • TimeField: فقط زمان (HH:MM:SS)
 
د) فیلدهای بولی:
    • BooleanField:  مقدار True و یا False

 

class Sample(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)
    is_available = models.BooleanField(default=True)

 

فیلدهای رابطه‌ای (Relational Fields)


این فیلدها برای ایجاد رابطه بین مدل‌ها استفاده می‌شوند و در پایگاه داده به صورت کلید خارجی (Foreign Key) یا جدول واسط (Intermediate Table) پیاده‌سازی می‌شوند. مدل‌های جنگو به طور پیش‌فرض از RDBMSها استفاده می‌کنند که روابط بین مدل‌ها رو پشتیبانی می‌کند. استفاده از روابط در پایگاه داده باعث بهبود عملکرد، جلوگیری از تکرار داده و ایجاد ساختار رابطه‌ای بین داده‌ها می‌شود.

 

الف) ForeignKey — رابطه یک به چند
    • یک رکورد در مدل A به چند رکورد در مدل B اشاره می‌کند. (مثال: یک نویسنده — چند مقاله)
    • on_delete اجباری است — تعیین می‌کند در صورت حذف رکورد والد چه اتفاقی بیفتد.

 

ب) OneToOneField — رابطه یک به یک
    • یک رکورد در مدل A فقط به یک رکورد در مدل B متصل است. (مثال: کاربر — پروفایل کاربری.)
 
ج) ManyToManyField — رابطه چند به چند
    • چند رکورد در مدل A به چند رکورد در مدل B متصل می‌شوند. (مثال: مقاله — تگ‌ها).

 

class Smaple(models.Model):
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    profile = models.OneToOneField('UserProfile', on_delete=models.CASCADE)
    tags = models.ManyToManyField('Tag', blank=True)

 

در بخش روابط مدل‌ها، به‌صورت مفصل و با جزئیات کاربردی به این مفهوم کلیدی خواهیم پرداخت که چگونه می‌توان با استفاده از فیلدهای رابطه‌ای جنگو — مانند ForeignKey، OneToOneField و ManyToManyField — ساختاری منسجم، پایدار و بدون تکرار داده طراحی کرد. این فیلدها نه‌تنها در سطح کد پایتون، بلکه در عمق پایگاه داده نیز با ایجاد کلیدهای خارجی یا جداول واسط، روابط معنادار بین موجودیت‌ها را پیاده‌سازی می‌کنند. هدف نهایی این است که از ذخیره‌سازی تکراری و غیرضروری داده‌ها جلوگیری شود و هر اطلاعاتی فقط در یک نقطه منحصربه‌فرد نگهداری شود — تا در صورت نیاز به به‌روزرسانی، تنها یک تغییر کافی باشد و تمام بخش‌های مرتبط به‌طور خودکار و بدون خطا بازتابی از آن تغییر ارائه دهند. این رویکرد، پایهٔ اصلی یکپارچگی داده‌ها (Data Integrity) و مقیاس‌پذیری سیستم است و درک عمیق آن، یک توسعه‌دهنده معمولی را به یک معمار داده حرفه‌ای تبدیل می‌کند. 

فیلدهای ساختاریافته و خاص (Structured & Specialized Fields)


این فیلدها برای ذخیره داده‌های مرکب، باینری یا رسانه‌ای طراحی شده‌اند. برخلاف فیلدهای اسکالر، این فیلدها ممکن است شامل چند بخش باشند یا نیاز به پردازش خاصی (مثل آپلود فایل یا تجزیه JSON) داشته باشند.

الف) FileField و ImageField
    • برای آپلود فایل و تصویر.
    • نیاز به تنظیم MEDIA_URL و MEDIA_ROOT در فایل تنظیمات پروژه settings.py دارند.
    • ImageField نیازمند کتابخانه Pillow است.

 

ب) JSONField
    • برای ذخیره داده‌های JSON — بسیار منعطف.
    • پشتیبانی از کوئری‌های داخل JSON در دیتابیس‌های مدرن (مثل PostgreSQL).
    • مناسب برای: تنظیمات پویا، لاگ‌های ساختاریافته، داده‌های متغیر بین رکوردها.
 
ج) ArrayField (فقط در PostgreSQL)
    • ذخیره لیستی از مقادیر (مانند لیست تلفن‌های یک کاربر).
    • فقط در PostgreSQL پشتیبانی می‌شود.
 
class Smaple(models.Model):
    avatar = models.ImageField(upload_to='avatars/')
    resume = models.FileField(upload_to='resumes/')
    metadata = models.JSONField(default=dict)
    phones = models.ArrayField(models.CharField(max_length=10))
 
د) HStoreField (فقط در PostgreSQL — قدیمی‌تر)
    • ذخیره داده‌های key-value — جایگزین قدیمی‌تر برای JSONField.

فیلدهای عمومی و کمکی (Generic & Helper Fields)


این فیلدها معمولاً برای مدیریت زیرساخت مدل یا ذخیره اطلاعات سیستمی استفاده می‌شوند. بعضی از آن‌ها به‌صورت خودکار توسط جنگو ایجاد می‌شوند (مثل id) و بعضی برای موارد خاص طراحی شده‌اند.

الف) AutoField / BigAutoField
    • فیلد عددی که به‌صورت خودکار افزایش می‌یابد.
    • معمولاً به عنوان id پیش‌فرض در هر مدل بصورت خودکار توسط جنگو استفاده می‌شود و نیازی به تعریف دستی ندارد.
ب) UUIDField
    • ذخیره شناسه‌های منحصربه‌فرد جهانی.
    • مناسب برای مواردی که id عددی قابل حدس نباشد.
    • نیازمند بکارگیری کتابخانه uuid است.

 

ج) GenericIPAddressField
    • ذخیره آدرس IP (هم IPv4 و هم IPv6).

 

د) BinaryField
    • ذخیره داده‌های باینری خام — مثل فایل‌های رمزگذاری شده یا بایت‌ها.
    • محدودیت در استفاده در فرم‌ها و فیلترها دارد.
import uuid

class smaple(models.Model):
    id = models.BigAutoField(primary_key=True)
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    ip_address = models.GenericIPAddressField()
    encrypted_data = models.BinaryField()

 

هـ) GenericForeignKey (از django.contrib.contenttypes)
    • برای ایجاد رابطه به مدل‌های مختلف — مکانیزم “Generic Relation” (مانند سیستم کامنت که برای مدل‌های مقاله، محصول و ...  کار کند).
    • نیازمند دو فیلد کمکی (content_type و object_id) است.
    • content_type: مشخص می‌کند که رکورد مربوط به کدام مدل است (مثلاً Article یا Product)
    • object_id: مشخص می‌کند که رکورد مربوط به کدام id از آن مدل است
    • content_object: یک فیلد مجازی (غیر فیزیکی) که به شما اجازه می‌دهد مستقیماً به شیء مرتبط دسترسی داشته باشید.
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Comment(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # Refer to Model (Article, Product, ...)    
    object_id = models.UUIDField()                                          # Refer to Model.Object.id  
    content_object = GenericForeignKey('content_type', 'object_id')         # "content_type" & "object_id" Combination to access the object directly

    comment = models.TextField()
    created = models.DateTimeField(auto_now_add=True)

⚠️ اگر در مدل مقصد (مثلاً Article) از UUIDField به عنوان id بجای AutoField پیش‌فرض جنگو استفاده گردد، باید فیلد object_id در مدل Comment نیز از PositiveIntegerField به UUIDField تغییر داده شود — در غیر این صورت، با خطا مواجه خواهیم شد.

پارامترهای فیلد


در فریم‌ورک جنگو، هر فیلد مدل (Model Field) نه تنها نوع داده‌ای که ذخیره می‌کند را مشخص می‌کند، بلکه می‌تواند دارای مجموعه‌ای از گزینه‌ها (Field Options) باشد که رفتار آن فیلد را در سطح پایگاه داده، فرم‌ها، اعتبارسنجی و ORM کنترل می‌کنند.

این گزینه‌ها به دو دسته تقسیم می‌شوند:

  1. گزینه‌های خاص هر نوع فیلد — مانند max_length در CharField که برای آن فیلد اجباری یا اختیاری است.
  2. گزینه‌های مشترک بین تمام فیلدها — که برای هر فیلدی قابل استفاده هستند و رفتار کلی فیلد را تغییر می‌دهند.

پارامترهای مشترک


تمام فیلدهای مدل در جنگو (مانند CharField، IntegerField، ForeignKey و غیره) از مجموعه‌ای از پارامترهای مشترک پشتیبانی می‌کنند. این گزینه‌ها رفتار فیلد را در سطح پایگاه داده، فرم‌ها، اعتبارسنجی و رابط کاربری ادمین کنترل می‌کنند. در ادامه، پرکاربردترین و مهم‌ترین این گزینه‌ها را بررسی می‌کنیم.

✺✳ ┅  null  ┅ ✳✺
  • نوع: bool
  • پیش‌فرض: False
  • کاربرد: تعیین می‌کند که آیا فیلد می‌تواند در پایگاه داده مقدار NULL را بپذیرد یا خیر.

اگر null=True باشد، جنگو اجازه می‌دهد که این فیلد در پایگاه داده مقدار NULL را بپذیرد. در غیر این صورت، فیلد اجباری در سطح پایگاه داده قلمداد خواهد شد و هر تلاشی برای ذخیره NULL منجر به خطا می‌شود.

age = models.IntegerField(null=True)
⚠️ برای فیلدهای متنی (CharField، TextField) معمولا از null=True استفاده نمی‌گردد. به جای آن، از blank=True و ذخیره رشتهٔ خالی ('') استفاده می‌شود. چرا که در پایگاه داده، NULL و رشته خالی دو مفهوم متفاوت هستند و ترکیب آن‌ها می‌تواند منجر به پیچیدگی در کوئری‌ها گردد و استاندارد جنگو پیشنهاد می‌کند برای فیلدهای متنی، فقط از رشته خالی استفاده شود.

✺✳ ┅  blank  ┅ ✳✺
  • نوع: bool
  • پیش‌فرض: False
  • کاربرد: تعیین می‌کند که آیا فیلد می‌تواند در فرم‌ها (و اعتبارسنجی جنگو) خالی باقی بماند یا خیر.

اگر blank=True باشد، فیلد در فرم‌های جنگو (از جمله ModelForm و پنل ادمین) اختیاری در نظر گرفته می‌شود و کاربر می‌تواند آن را خالی بگذارد و در غیر این‌صورت جنگو اجازه Submit فرم را تا زمان پرکردن فیلد به کاربر نخواهد داد.

bio = models.TextField(blank=True)
 
⚠️ تفاوت null و  blank در این است که null مربوط به ذخیره‌سازی در پایگاه داده است ولی blank مربوط به اعتبارسنجی فرم است. این دو گزینه مستقل هستند و معمولاً هنگامی که یک فیلد هم در فرم اختیاری است و هم می‌تواند NULL باشد، هر دو را فعال می‌کنند.
 

✺✳ ┅  choices  ┅ ✳✺
  • نوع: Iterable (مثل لیست یا تاپل)
  • کاربرد: محدود کردن مقادیر مجاز یک فیلد به یک مجموعه از گزینه‌های از پیش تعریف‌شده.

در ادمین پنل و فرم‌ها، این فیلد به صورت Select و options نمایش داده می‌شود.

STATUS_CHOICES = [
    ('ON', 'Active'),
    ('OFF', 'Inactive'),
    ('IDL', 'Idle'),
]

status = models.CharField(max_length=1, choices=STATUS_CHOICES)
 
⚠️ مقدار ذخیره‌شده در پایگاه داده، مقدار اول هر تاپل است (OFF، ON و IDL)  اما مقدار نمایش داده شده در فرم‌ها و پنل ادمین، مقدار دوم هر تاپل خواهد بود ( Inactive، Active و Idle )

✺✳ ┅  default  ┅ ✳✺
  • نوع: هر مقدار ثابت یا یک تابع قابل فراخوانی (callable)
  • کاربرد: تعیین مقدار پیش‌فرض برای فیلد در صورتی که مقداری ارسال نشود.
active = models.BooleanField(default=True)

✺✳ ┅  help_text  ┅ ✳✺
  • نوع: str
  • کاربرد: نمایش یک متن راهنما در کنار فیلد در فرم‌ها.
email = models.EmailField(help_text="Enter your valid mail address ...")
 ⚠️ این متن در HTML فرم‌ها به صورت <span class="helptext">...</span> رندر می‌شود و برای کاربران در رابط کاربری مفید بوده و در پایگاه داده تاثیری ندارد. 

✺✳ ┅  primary_key  ┅ ✳✺
  • نوع: bool
  • پیش‌فرض: False
  • کاربرد: مشخص می‌کند که آیا این فیلد کلید اصلی (Primary Key) مدل باشد.

اگر primary_key=True باشد، این فیلد به عنوان کلید اصلی مدل در نظر گرفته می‌شود.

id = models.UUIDField(primary_key=True, default=uuid.uuid4)
 

⚠️ هر مدل فقط یک فیلد می‌تواند primary_key=True داشته باشد.

⚠️ اگر هیچ فیلدی به عنوان primary_key تعریف نشود، جنگو به‌صورت خودکار یک فیلد id از نوع AutoField اضافه می‌کند.


✺✳ ┅  unique  ┅ ✳✺
  • نوع: bool
  • پیش‌فرض: False
  • کاربرد: تضمین می‌کند که مقدار این فیلد در کل جدول منحصربه‌فرد باشد.
email = models.EmailField(unique=True)
 

⚠️ در پایگاه داده، یک محدودیت انحصاری از نوع UNIQUE ایجاد می‌شود.

⚠️ در صورت تلاش برای ذخیره مقدار تکراری، خطای IntegrityError رخ می‌دهد.


✺✳ ┅  editable  ┅ ✳✺
  • نوع: bool
  • پیش‌فرض: True
  • کاربرد: تعیین می‌کند که آیا این فیلد در فرم‌های خودکار جنگو (مثل ادمین پنل یا ModelForm) نمایش داده شود یا خیر.

اگر editable=False باشد، این فیلد در فرم‌های جنگو نمایش داده نخواهد شد.

id = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4)
 

⚠️ فیلدهای editable=False در پنل ادمین دیده نمی‌شوند و در فرم‌ها نیز شامل نمی‌شوند. اما همچنان در مدل و پایگاه داده وجود دارند و قابل دسترسی هستند.


✺✳ ┅  verbose_name  ┅ ✳✺
  • نوع: str
  • کاربرد: تعیین نام خوانا و انسان‌محور برای فیلد (به جای نام متغیر).

در ادمین پنل و فرم‌ها، نامی که برای verbose_name تعیین می‌شود به عنوان جایگزینی نام فیلد، نمایش داده خواهد شد.

full_name = models.CharField("name", max_length=50)
full_name = models.CharField(max_length=50, verbose_name="name")
 

⚠️ اگر verbose_name تعیین نگردد، جنگو، نام فیلد را با تبدیل _ به فاصله و و بزرگ‌نویسی اول هر کلمه (full_nameFull name) به عنوان نام پیش‌فرض استفاده می‌کند.


 

این گزینه‌ها، ابزارهای قدرتمندی هستند که امکان طراحی مدل‌هایی دقیق، ایمن و کاربرپسند را فراهم می‌آورند. درک تفاوت بین null و blank، استفاده صحیح از choices و default، و به‌کارگیری verbose_name برای بهبود تجربه کاربری، از جمله بهترین روش‌های توسعه با جنگو محسوب می‌شوند.

پارامترهای خاص


علاوه بر پارامترهای مشترکی مانند null، blank و default که در بخش قبلی بررسی شدند، هر کدام از تایپ‌های فیلد در جنگو ممکن است گزینه‌های اختصاصی خود را نیز داشته باشد. و یا به عبارت دیگر، برخی گزینه‌ها فقط برای فیلدهای خاصی معنا دارند و در تعریف آن فیلدها کاربرد دارند. برخی از این گزینه‌ها اجباری هستند (مثل max_length در CharField) و برخی دیگر اختیاری اما مفید.. در ادامه، مهم‌ترین فیلدهای پرکاربرد و گزینه‌های خاص آن‌ها را مرور می‌کنیم.

 

✺✳ ┅  max_length  ┅ ✳✺
  • نوعint
  • فیلدها: CharField, EmailField, SlugField, URLField, FileField, ImageField
  • اجباری: برای CharField و زیرمجموعه‌هایش
  • کاربرد: حداکثر تعداد کاراکترهای مجاز برای فیلد را تعیین می‌کند. در پایگاه داده، این مقدار به عنوان اندازه ستون VARCHAR(n) استفاده می‌شود.
username = models.CharField(max_length=150)
website = models.URLField(max_length=500)
 

⚠️ برای FileField و ImageField، این گزینه حداکثر طول مسیر ذخیره‌سازی فایل (path) را تعیین می‌کند و نه حجم فایل. 


✺✳ ┅  max_digits  و decimal_places ┅ ✳✺
  • نوعint
  • فیلدها: DecimalField
  • اجباری: برای DecimalField
  • کاربرد: max_digits حداکثر تعداد ارقام مجاز (شامل ارقام قبل و بعد از ممیز) را مشخص کرده و decimal_places تعداد ارقام پس از ممیز را تعیین می‌کند.
price = models.DecimalField(max_digits=10, decimal_places=2)  # e.g. 12345678.99

⚠️ هر دو گزینه برای DecimalField اجباری هستند و max_digits باید حتما بزرگتر از decimal_places باشد.


 ✺✳ ┅  auto_now  و auto_now_add ┅ ✳✺
  • نوعbool
  • پیش‌فرض: False
  • فیلدها: DateField, DateTimeField
  • اختیاری
  • کاربرد: اگر True باشد، در auto_now هر بار که یک رکورد ویرایش شده و ذخیره گردد، به زمان فعلی، بروزرسانی می‌شود و برای ثبت زمان آخرین ویرایش (updated_at) کاربرد دارد، ولی در auto_now_add فقط هنگام ایجاد اولیه رکورد به زمان فعلی تنظیم شده و برای ثبت زمان ایجاد (created_at) بیشترین کاربرد را خواهد داشت
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
 ⚠️ در صورت استفاده از auto_now=True و یا auto_now_add=True، فیلد به‌صورت خودکار editable=False خواهد بود.

✺✳ ┅  on_delete  ┅ ✳✺
  • نوعmodels
  • فیلدها: ForeignKey, OneToOneField, ManyToManyField
  • اجباری: برای فیلدهای رابطه‌ای
  • کاربرد: رفتار سیستم را هنگام حذف رکورد مقصد (related object) تعیین می‌کند.
  • مقادیر رایج:

    • models.CASCADE: حذف تمامی زنجیره‌‌ی رکوردهای وابسته.
    • models.PROTECT: جلوگیری از حذف با خطای ProtectedError.
    • models.SET_NULL: تنظیم فیلد به NULL (نیاز به null=True دارد).
    • models.SET_DEFAULT: تنظیم به مقدار پیش‌فرض (نیاز به default دارد).
author = models.ForeignKey(User, on_delete=models.CASCADE)

✺✳ ┅  related_name  ┅ ✳✺
  • نوعstr
  • فیلدها: ForeignKey, OneToOneField, ManyToManyField
  • اختباری
  • کاربرد: نامی که برای دسترسی معکوس از مدل مقصد به مدل مبدا استفاده می‌شود
class Article(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles')
 ⚠️ اگر related_name='+' باشد، دسترسی معکوس غیرفعال می‌شود.

  ✺✳ ┅  allow_unicode  ┅ ✳✺
  • نوعbool
  • فیلدها: SlugField
  • اختیاری
  • کاربرد: اگر True باشد، اجازه استفاده از کاراکترهای غیر-لاتین (مثل فارسی، عربی، چینی) در اسلاگ را می‌دهد.

 

slug = models.SlugField(allow_unicode=True)  # "مقاله-سلام-دنیا"
 

روابط مدل‌ها


در دنیای پایگاه‌داده‌ها و مدل‌سازی داده، یکی از مهم‌ترین اصول طراحی، جلوگیری از تکرار غیرضروری داده‌ها و ایجاد روابط معنادار بین موجودیت‌ها است — و دقیقاً همین جاست که روابط بین مدل‌ها (Relationships) به کمک فیلدهای رابطه‌ای در جنگو نقش حیاتی ایفا می‌کنند. این فیلدها — شامل ForeignKey، OneToOneField و ManyToManyField — اجازه می‌دهند تا مدل‌های مختلف را به یکدیگر متصل نمود و ساختاری منطقی، پایدار و مقیاس‌پذیر ساخت. در پس‌زمینه، جنگو این روابط را یا با استفاده از کلیدهای خارجی (Foreign Key) در دیتابیس پیاده‌سازی می‌کند — یعنی در مدل فرزند، ستونی ایجاد می‌شود که به id رکورد مرتبط در مدل والد اشاره می‌کند — و یا در موارد رابطه چند-به-چند، یک جدول واسط (Intermediate Table) به‌صورت خودکار ساخته می‌شود که جفت‌های مرتبط را نگه می‌دارد.

هدف نهایی این است که داده‌ها فقط در یک مکان ذخیره شوند و هر جا به آن‌ها نیاز بود، از طریق رابطه به آن‌ها دسترسی پیدا کرد — نه با کپی‌کردن و تکرار. این رویکرد نه‌تنها حجم پایگاه داده را کاهش می‌دهد، بلکه یکپارچگی داده‌ها (Data Integrity) را تضمین می‌کند.

بعنوان مثال، اگر نام یک نویسنده تغییر کند، به‌جای ویرایش صدها مقاله، فقط یک رکورد در جدول نویسندگان به‌روز می‌شود و تمام مقالات مرتبط به‌طور خودکار این تغییر را منعکس خواهد کرد.

بدون این روابط، مجبور خواهیم بود اطلاعات را در چندین جا تکرار کنیم — که علاوه بر هدر رفت فضا، خطاهای انسانی، ناسازگاری داده‌ها و سختی در نگهداری سیستم نیز ایجاد می‌شد. بنابراین، فیلدهای رابطه‌ای تنها یک ابزار فنی نیستند، بلکه پایه‌ای اساسی برای طراحی هوشمندانه و حرفه‌ای سیستم‌های نرم‌افزاری هستند که کمک می‌کنند داده‌ها را به‌صورت ساختاریافته، منعطف و بدون افزونگی مدیریت نمود.

رابطه یک به چند - ForeignKey


در سیستم‌های مدیریت پایگاه‌داده‌های رابطه‌ای و به‌ویژه در چارچوب جنگو، رابطه ForeignKey ابزاری بنیادین برای ایجاد ارتباط جهت‌دار بین دو مدل محسوب می‌شود که از نظر منطقی معادل یک رابطه یک به چند (One-to-Many) است. این بدان معناست که یک رکورد در مدل والد — مثلاً User — می‌تواند با چندین رکورد در مدل فرزند — مثلاً Project — مرتبط باشد، در حالی که هر رکورد در مدل فرزند تنها می‌تواند به یک رکورد منحصربه‌فرد در مدل والد ارجاع دهد.

این الگو در مثال‌های واقعی مانند «مسئولیت یک کاربر بر چندین پروژه می‌تواند باشد، در حالی که هر پروژه تنها یک مسئول می‌تواند داشته باشد» به خوبی قابل مشاهده است. • از دید مدل فرزند (Project)، همین رابطه به‌عنوان چند به یک (ManyToOne) تفسیر می‌شود، زیرا چندین رکورد در این مدل به یک رکورد واحد در مدل والد اشاره می‌کنند.

این دوگانگی در نام‌گذاری رابطه — OneToMany از دید والد و ManyToOne از دید فرزند — گاهی برای توسعه‌دهندگان مبتدی گمراه کتنده خواهد بود، اما درک دقیق آن شرط لازم برای طراحی صحیح مدل‌ها و دسترسی کارآمد به داده‌های مرتبط در لایه منطق کسب‌وکار است.

در پیاده‌سازی، فیلد ForeignKey، همواره در مدل فرزند تعریف می‌گردد و به مدل والد متصل می‌شود — چرا که در سطح پایگاه‌داده، این مدل فرزند است که نیازمند ارجاع به اطلاعات مدل والد خواهد بود. در واقع، فرآیندی که در سطح پایگاه‌داده اتفاق می‌افتد به این صورت است که هنگام تعریف یک فیلد ForeignKey در مدل فرزند، یک ستون جدید (مثلاً owner_id) به صورت خودکار در جدول مربوط به مدل فرزند اضافه می‌شود که به کلید اصلی مدل والد (User) اشاره می‌کند.

coreapp/models.py

from django.db import models
from django.contrib.auth.models import User
import uuid

class Project(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects")  # ManyToOne Field
    title = models.CharField(max_length=100)
    area = models.CharField(max_length=50)
    content = models.TextField(null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

    def __str__(self):
        return self.title

در مدل Project که در کد فوق تعریف شده است، رابطه ForeignKey از طریق فیلد owner به مدل User (از اپ django.contrib.auth) متصل شده است که مدل پیش‌فرض جنگو بوده و هنگام ایجاد کاربری superuser شرح مختصری از آن رفت. این تعریف به‌صورت دقیق یک رابطه چند به یک (ManyToOne) را از دید مدل Project پیاده‌سازی می‌کند — یعنی هر پروژه تنها می‌تواند یک مالک (owner) داشته باشد، اما یک کاربر می‌تواند مالک چندین پروژه باشد. این رابطه در سطح پایگاه‌داده با افزودن یک ستون owner_id در جدول project پیاده‌سازی می‌شود که به کلید اصلی (id) جدول auth_user اشاره می‌کند.

⚠️ پارامتر on_delete=models.CASCADE تعیین می‌کند که در صورت حذف کاربر، تمام پروژه‌های مرتبط با او نیز، به‌صورت خودکار حذف گردند — رفتاری که در بسیاری از سیستم‌ها برای حفظ تمامیت داده‌ها منطقی و مطلوب است. 

⚠️ در جنگو، برای دسترسی معکوس — یعنی از مدل والد به مدل فرزند — از ویژگی related_name استفاده می‌شود که در زمان تعریف ForeignKey اختیاری اما بسیار توصیه‌شده است. این ویژگی امکان تعریف یک نام معنادار برای دسترسی به مجموعه رکوردهای فرزند را از مدل والد فراهم می‌کند

⚠️ برای بازتاب این تغییرات در ساختار پایگاه‌داده، الزامی است که دو دستور makemigrations و migrate به ترتیب اجرا شوند

با بررسی داده‌های واقعی در جداول، این رابطه به‌وضوح قابل مشاهده است. در جدول auth_user، کاربری با id = 1 و نام کاربری admin وجود دارد که پیشتر در بخش ایجاد کاربری superuser ایجاد گردید.

TABLE 🡺 auth_user

id    username    email            first_name  last_name   is_superuser     is_staff    is_active  
----  ----------  ---------------  ----------- ---------   --------------   ----------  -----------
1  	  admin       admin@admin.com              			   1  		        1           1  

در مقابل، در جدول coreapp_project، سه رکورد مختلف وجود دارد که همگی در فیلد owner_id مقدار 1 دارند — یعنی هر سه پروژه Fleunt Speech و Coverage Map و Yoga Academy به همان کاربر admin تعلق دارند..

TABLE 🡺 coreapp_project

owner_id	title          area      content    created         updated         id     
----------  -------------  --------  ---------  --------------  --------------  -------
1			Fluent Speech  Health               2025-09-29 ...  2025-09-29 ...  ac26...
1			Coeverage Map  Industry             2025-09-29 ...  2025-09-29 ...  0fac...
1			Yoga Academy   Sport                2025-09-29 ...  2025-09-29 ...  45da...

 

این دقیقاً همان مفهوم One-to-Many است که در این نمونه با مفهوم یک کاربر ←→ چندین پروژه، از دید پایگاه‌ داده می‌باشد، این رابطه با یک کلید خارجی (Foreign Key) پیاده‌سازی شده که تمامیت رابطه را تضمین می‌کند — یعنی هیچ پروژه‌ای نمی‌تواند owner_idای داشته باشد که در جدول auth_user وجود نداشته باشد (مگر در صورتی که on_delete مقدار SET_NULL یا SET_DEFAULT داشته باشد — بخش مربوط به پارامترهای فیلد — که در این مدل پیاده‌سازی نشده است).

این تحلیل نشان می‌دهد که رابطه تعریف‌شده در سطح مدل، نه‌تنها در کد بلکه در ساختار فیزیکی پایگاه‌داده نیز به‌درستی پیاده‌سازی شده و با داده‌های واقعی همخوانی کامل دارد. 

رابطه یک به یک - OneToOne


در رابطه One-To-One، هر رکورد از یک مدل می‌تواند، دقیقاً با یک و فقط یک رکورد از مدل دیگر مرتبط باشد و بالعکس، برخلاف رابطه ForeignKey — رابطه فرزند و والد — در رابطه OneToOne هر دو طرف تنها یک همسر دارند!

One2OneField

نمونه‌های دنیای واقعی


  • هر کاربر (User) فقط یک پروفایل (Profile) دارد و همینطور یک ‌پروفایل نیز فقط متعلق به یک کاربر است.
  • هر شرکت (Company) فقط یک مدیرعامل (Ceo) دارد و همینطور یک مدیرعامل فقط مالک یک شرکت است.
  • هر خودرو (Car) فقط یک شماره شاسی (VIN) دارد و همینطور یک شماره شاسی متعلق به یک خودرو می‌باشد.

برای استفاده از رابطه one-to-one در مدل‌های جنگو باید از گزینه OneToOneField برای فیلد استفاده نمود تا بدین صورت رابطه یک به یک (one-to-one) را تعریف کرد.

clientapp/models.py

# clientapp.models.py
from django.db import models
from django.contrib.auth.models import User

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE) # OneToOne Field
    mobile = models.CharField(max_length=15, unique=True)
    birth = models.DateField(null=True, blank=True)
    avatar = models.ImageField(upload_to='profiles/', null=True, blank=True)
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, unique=True, editable=False)

    def __str__(self):
        return self.user.username

رابطه چند به چند - ManyToMany


در رابطه چند به چند (Many-to-Many یا به اختصار ManyToMany)، هر رکورد از یک مدل می‌تواند با چندین رکورد از مدل دیگر مرتبط شود و بالعکس. این نوع رابطه زمانی کاربرد دارد که بین داده‌های دو مدل، ارتباطِ یک‌طرفه FoerignKey یا یک‌به‌یک OneToOne کافی نباشد. فرض کنیم در یک وب‌سایت پورتفولیوی شخصی برای نمایش لیستی از تجربه پروژه‌های نرم‌افزاری اجراشده، هر پروژه می‌تواند چندین تگ (برچسب) داشته باشد — Python، Django، React و غیره. از سوی دیگر، یک تگ مانند Python می‌تواند به چندین پروژه مختلف تعلق داشته باشد (به عنوان مثال،یک پروژه وب، یک پروژه اسکریپتی و یا یک ربات تلگرام که همگی با پایتون اجرا شده‌ باشند). در چنین شرایطی، رابطه ManyToMany ایده‌آل‌ترین گزینه خواهد بود.

 

Many2Many-Field

 

در سطح پایگاه داده، رابطه چند به چند به‌صورت مستقیم قابل پیاده‌سازی نیست. به همین دلیل، جنگو به‌صورت خودکار یک جدول میانی (Intermediate Table یا Junction Table) ایجاد می‌کند. این جدول دو ستون دارد که هر ستون به id  رکورد یا فیلد کلید اصلی (Primary Key) در مدل مقابل اشاره می‌کند. هر بار که یک تگ به یک پروژه اضافه گردد، جنگو یک رکورد جدید در جدول میانی درج می‌کند که شامل project_id و tag_id خواهد بود.

TABLE 🡺 coreapp_project_tags

  id  project_id                        tag_id
----  --------------------------------  --------------------------------
   1  ac260bde5f1347449f239052420573a7  ab2050e16c94483db24aeab26b0c4330
   2  0fac050182a84fffa37d6033c276d2c2  ab2050e16c94483db24aeab26b0c4330
   3  45da0e0c546a4b0991f665edba168b89  ab2050e16c94483db24aeab26b0c4330
   4  ac260bde5f1347449f239052420573a7  41de4854a25b40ac860c5b28ff4b8c17
   5  ac260bde5f1347449f239052420573a7  50cb432fb44c487daedfd427eac08bcd

 

برای ایجاد یک رابطه چند به چند در مدل‌های جنگو، از فیلد ManyToManyField استفاده می‌گردد. فیلد ManyToManyField فقط در یکی از دو مدل تعریف می‌گردد— به طور معمول، در مدلی که از لحاظ منطقی "مالک" رابطه تلقی می‌گردد. اگر ManyToManyField را در هر دو مدل قرار دهیم، جنگو دو جدول میانی جداگانه ایجاد می‌کند که کاملاً اشتباه است! رابطه چند به چند یک رابطه دوطرفه است و فقط نیاز به یک فیلد در یکی از مدل‌ها دارد.

coreapp/models.py

# coreapp.models.py
from django.db import models
from django.contrib.auth.models import User
import uuid

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

    def __str__(self):
        return self.name

#-------------------------------------------------------------------------------------------

class Project(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects")
    title = models.CharField(max_length=100)
    area = models.CharField(max_length=50)
    content = models.TextField(null=True, blank=True)
    tags = models.ManyToManyField(Tag, related_name='projects')
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)

    def __str__(self):
        return self.title

⚠️ چون پروژه‌ها مالک تگ‌ها محسوب می‌شوند، فیلد ManyToMany در مدل project تعریف شده است.

⚠️ برای بازتاب تغییرات جدید در ساختار پایگاه‌داده، الزامی است که دو دستور makemigrations و migrate به ترتیب اجرا شوند.

⚠️ ثبت مدل Tag ایجاد شده، در admins.py جهت نمایش در پنل ادمین، توسط دستور admin.site.register(models.Tag) می‌بایست صورت پذیرد.

جستجوی رکوردها


در این بخش به فرآیند جستجو و بازیابی داده‌ها از مدل‌های تعریف‌شده در پایگاه داده (که همان جداول یا Tables محسوب می‌شوند) می‌پردازیم و یاد می‌گیریم چگونه از این داده‌ها در تمپلیت استفاده کنیم.

سیستم ORM (Object-Relational Mapper) جنگو یکی از نقاط قوت اصلی آن است. این سیستم با انواع پایگاه داده‌ از جمله MySQL، PostgreSQL، SQLite، Oracle و MariaDB سازگار است و مدیریت داده‌ها را به‌شکل ساده و قدرتمند ممکن می‌سازد. هسته‌ی ORM جنگو بر پایه‌ی QuerySetها بنا شده است. بنابراین نخستین گام برای کار با داده‌ها، آشنایی کامل با مفاهیم Query و QuerySet است.

یک QuerySet در واقع مجموعه‌ای از کوئری‌های پایگاه داده است که برای استخراج اشیاء (objects) از مدل‌ها استفاده می‌شود. علاوه بر این، می‌توان با ارسال پارامترهای دلخواه، نتایج برگشتی را فیلتر یا محدود کرد.

ساختار کلی QuerySet

یک QuerySet به‌طور معمول، به شکل زیر تعریف می‌گردد.

queryVariable = ModelName.objects.method()
  • queryVariable ↔ متغیری است که خروجی در آن ذخیره می‌شود.

  • ModelName ↔ نام مدلی است که قصد داریم داده‌های آن را بازیابی کنیم.

  • objects ↔ واسط پیش‌فرض برای دسترسی به داده‌های مدل.

متدهای متداول در QuerySet

  • all() ↔ تمام رکوردهای مدل را برمی‌گرداند.

  • get() ↔ یک رکورد خاص را بر اساس شرایط مشخص برمی‌گرداند. (اگر رکوردی پیدا نشود یا بیش از یک رکورد موجود باشد، خطا رخ می‌دهد.)

  • filter() ↔ رکوردهایی را برمی‌گرداند که با شرایط تعیین‌شده مطابقت دارند.

  • exclude() ↔ رکوردهایی را برمی‌گرداند که با شرایط تعیین‌شده مطابقت ندارند.

 

⚠️ قبل از ایجاد QuerySet روی یک مدل، باید مطمئن شویم که مدل مربوطه را import کرده‌ایم.
⚠️ از آنجا که وظیفه‌ی برقراری ارتباط با مدل‌ها بر عهده‌ی Viewهاست، این import معمولاً در فایل views.py انجام می‌شود.

در ادامه، مستندات کوئری‌های جنگو، به‌صورت یکپارچه و گسترده‌تر، ارائه می‌گردد. 

متدهای پایه


در اولین گام، نیاز است داده‌های ذخیره‌شده در پایگاه داده فراخوانی شوند — Retrieving Data. جنگو برای این کار مجموعه‌ای از متدهای پرس‌وجو (QuerySet methods) را در اختیار قرار می‌دهد که هر یک کارکرد مشخصی دارند: از جمله بازیابی تمام رکوردها، دریافت یک رکورد منحصربه‌فرد، فیلتر کردن نتایج بر اساس شرایط دلخواه و همچنین حذف رکوردهای نامرتبط از مجموعه نتایج.

این متدها پایه و اساس کار با داده‌ها در ORM هستند و در اکثر پروژه‌ها بیشترین استفاده را دارند.

 

⸺ دریافت همه رکوردها


projects = Project.objects.all()

✅ این متد تمام رکوردهای مدل project را برمی‌گرداند. در عمل معادل SELECT * FROM coreapp_project در SQL است.
📌 کاربرد: زمانی که بخواهیم کل داده‌های یک جدول را نمایش دهیم (به عنوان مثال لیست همه پروژه‌های اجرا شده).


 

⸺ دریافت یک رکورد خاص


projectObj = Project.objects.get(id="4f0928dd-5d68-484c-bd19-aa7a18d27d66")

✅ متد get() فقط یک رکورد را بر اساس شرط مشخص برمی‌گرداند.
⚠️ اگر هیچ رکوردی پیدا نشود ⇄ خطای DoesNotExist
⚠️ اگر بیش از یک رکورد پیدا شود ⇄ خطای MultipleObjectsReturned

📌 کاربرد: زمانی که مطمئن هستیم شرط دقیقاً یک رکورد را مشخص می‌کند (مثل id یا username که منحصربه‌فرد و یا به اصطلاح Unique هستند).


 

⸺ فیلتر کردن رکوردها


projectObjs = Project.objects.filter(subject="Health")

✅ متد filter()، لیستی از رکوردهایی که شرایط را احراز می‌کنند، برمی‌گرداند.
📌 کاربرد: گرفتن لیست داده‌هایی که ویژگی مشترک دارند. ( بعنوان مثال 🡠 همه پروژه‌های اجرا شده در موضوع سلامت Health )


 

⸺ حذف رکوردهای نامرتبط


projectObjs = Project.objects.exclude(subject="Sport")

✅ متد exclude()، برعکس filter() عمل می‌کند؛ یعنی رکوردهایی که مصداق شرط را ندارند، برمی‌گرداند.
📌 کاربرد: وقتی بخواهیم همه داده‌ها به جز یک دسته خاص را نمایش دهیم.( بعنوان مثال 🡠 همه پروژه‌های اجرا شده یه غیر از موضوع ورزش Sports )

متدهای مرتب‌سازی و محدودسازی


پس از بازیابی داده‌ها، گاهی نیاز داریم که رکوردها را مرتب‌سازی — Ordering — یا محدودسازی Limitingکنیم. به عنوان مثال محدودسازی ۱۰ کتاب آخر یک نویسنده یا مرتب‌کردن محصولات فروشگاه بر اساس قیمت. جنگو این کار را به‌سادگی با متدهای order_by و slicing فراهم می‌کند.

⸺ مرتب‌سازی صعودی


projects = Project.objects.all().order_by("title")

همهٔ پروژه‌ها را بر اساس عنوان (title) به‌صورت الفبایی صعودی (از الف به ی) مرتب می‌کند. یعنی پروژه‌هایی که عنوانشان با «آ» شروع می‌شود، اول لیست می‌آیند.


⸺ مرتب‌سازی نزولی


projects = Project.objects.all().order_by("-created")

همهٔ پروژه‌ها را بر اساس فیلد created (معمولاً تاریخ ایجاد) به‌صورت نزولی مرتب می‌کند. علامت منفی (-) نشان‌دهندهٔ ترتیب معکوس است؛ یعنی جدیدترین پروژه‌ها اول لیست ظاهر می‌شوند.


⸺ محدود کردن تعداد رکوردها


projects = Project.objects.all()[:5]

فقط 5 رکورد اول از لیست پروژه‌ها را برمی‌گرداند. این روش معمولاً برای نمایش «آخرین پروژه‌ها» یا «محدود کردن نتایج در صفحه‌بندی» استفاده می‌شود. این کار در پس‌زمینه با LIMIT 5 در SQL پیاده‌سازی می‌شود و بسیار کارآمد است.


⸺ رد کردن چند رکورد اول


projects = Project.objects.all()[5:]

اولین 5 رکورد را نادیده می‌گیرد و از ششمین رکورد به بعد را برمی‌گرداند. استفاده مستقیم از slice در پروژه‌های واقعی به‌جای Paginator جنگو توصیه نمی‌شود، چون Paginator امن‌تر و بهینه‌تر است.

متدهای کمکی پرکاربرد


گاهی تنها به یک رکورد خاص (مثل اولین یا آخرین مورد) یا اطلاعات خلاصه‌ای مانند تعداد کل رکوردها نیاز داریم. جنگو برای این موارد متدهای کمکی ساده‌ای مانند first(), last(), count() و exists() را فراهم کرده است تا بتوانیم بدون نوشتن کوئری‌های پیچیده یا اضافی، به‌صورت کارآمد و خوانا با داده‌ها کار کنیم.

 

⸺ اولین رکورد


اولین رکورد از مجموعه نتایج را برمی‌گرداند (بر اساس ترتیب پیش‌فرض یا order_by). اگر رکوردی وجود نداشته باشد، مقدار None را برمی‌گرداند — خطا نمی‌دهد.

projectObj = Project.objects.first()

⸺ آخرین رکورد


آخرین رکورد از مجموعه نتایج را برمی‌گرداند. مانند first()، در صورت عدم وجود داده، None بازمی‌گرداند.

projectObj = Project.objects.last()

💡 نکته: first() و last() بر اساس ترتیب تعیین‌شده توسط order_by() عمل می‌کنند. اگر order_by مشخص نشده باشد، ترتیب بر اساس primary key در نظر گرفته می‌شود.


⸺ تعداد رکوردها


تعداد کل رکوردها را به‌صورت یک عدد صحیح برمی‌گرداند. این متد بهینه‌تر از len(Project.objects.all()) است، چون در پس‌زمینه از SELECT COUNT(*) استفاده می‌کند و داده‌های واقعی را fetch نمی‌کند.

projectObj = Project.objects.count()

⸺ بررسی وجود رکورد


بررسی می‌کند که آیا حداقل یک رکورد با شرط مورد نظر وجود دارد یا خیر. نتیجه یک مقدار True یا False است. این روش بهینه‌ترین راه برای چک کردن وجود داده است، چون فقط یک سطر از دیتابیس را بررسی می‌کند و داده‌های اضافه‌ای را بارگیری نمی‌کند.

projectObj = Project.objects.filter(area="AI").exist()
 

متدهای مقایسه‌ای (Lookups)


اغلب اوقات نیاز خواهد بود که داده‌ها را بر اساس شرایط دقیق‌تری—مانند «بزرگ‌تر از»، «کوچک‌تر از»، «شامل یک عبارت» یا «بین دو تاریخ»—فیلتر کنیم. جنگو برای این منظور از سیستم عبارات جستجو (lookup expressions) پشتیبانی می‌کند که با استفاده از دو زیرخط (__) پس از نام فیلد اعمال می‌شوند.

 

⸺ مساوی


projectObj = Project.objects.filter(title="Fluent Speech")

رکوردهایی که فیلد title آن‌ها دقیقاً برابر با مقدار داده‌شده باشد. (معادل با استفاده از = در SQL)

💡 نیازی به __exact نیست؛ این رفتار پیش‌فرض filter() است.


⸺ شامل بودن (Contains)


آخرین رکورد از مجموعه نتایج را برمی‌گرداند. مانند first()، در صورت عدم وجود داده، None بازمی‌گرداند.

projectObjs = Project.objects.filter(title__contains="AI")

رکوردهایی که فیلد title آن‌ها عبارت "AI" را در هر جایی داشته باشند (حساس به بزرگ‌وکوچکی حروف).

💡 برای جستجوی غیرحساس به بزرگ‌وکوچکی حروف، از __icontains استفاده می‌شود.


⸺ بزرگ‌تر یا مساوی


projectObjs = Project.objects.filter(created__gte="2023-01-01") # gte = greater than or equal

رکوردهایی که تاریخ ایجاد (created) آن‌ها بزرگ‌تر یا مساوی تاریخ داده‌شده باشد.


⸺ کوچک‌تر یا مساوی


projectObj = Project.objects.filter(updated__lte='2023-01-01') # lte = less than or equal

رکوردهایی که تاریخ اخرین تغییرات آن‌ها (updated) کوچک‌تر یا مساوی تاریخ مشخص‌شده باشد.


⸺ بین دو مقدار (Range)


projectObj = Project.objects.filter(created__range=('2024-01-01', '2025-01-01'))

رکوردهایی که مقدار فیلد created آن‌ها بین دو تاریخ (شامل خود دو سر) باشد.

💡 معادل با  BETWEEN '2024-01-01' AND '2025-01-01' در SQL


⸺ داخل یک لیست (In)


projectObjs = Project.objects.filter(area__in=["Health", "Sport"])

رکوردهایی که فیلد subject آن‌ها یکی از مقادیر داخل لیست باشد.

💡 معادل با subject IN ('AI', 'Sports', 'Health') در SQL.


⸺ مقدار تهی (Null)


projectObjs = Project.objects.filter(content_isnull=False)

رکوردهایی که فیلد content آن‌ها مقدار NULL نباشد (یعنی داده داشته باشد).

💡 برای یافتن رکوردهایی که content آن‌ها خالی است: content__isnull=True.

💡 isnull فقط برای فیلدهایی که null=True دارند معنی دارد. برای فیلدهای CharField معمولاً بهتر است از __exact="" یا __isempty (در صورت نیاز) استفاده کرد.

متدهای تغییر و حذف داده‌ها


سیستم ORM جنگو فقط برای خواندن داده‌ها طراحی نشده است؛ بلکه امکان بروزرسانی و حذف داده‌ها را نیز به‌صورت ایمن و کارآمد فراهم می‌کند—بدون نیاز به نوشتن دستورات SQL دستی.

 

⸺ بروزرسانی رکوردها


متد update()، رکورد(های) مطابق با شرط را مستقیماً در پایگاه داده به‌روزرسانی می‌کند.

Project.objects.filter(id="4f0928dd-5d68-484c-bd19-aa7a18d27d66").update(title="Fluent Speech AI")
💡 بسیار سریع و کارآمد (یک کوئری SQL اجرا می‌شود).
⚠️ از سیگنال‌های save() و اعتبارسنجی مدل عبور نمی‌کند!

⸺ حذف رکوردها


همهٔ پروژه‌هایی که فیلد content آن‌ها خالی (NULL) باشد را به‌طور کامل از پایگاه داده حذف می‌کند.

Project.objects.filter(content__isnull=True).delete()

💡 حذف انبوه با یک دستور ساده.
⚠️ این عملیات غیرقابل بازگشت است و از سیگنال pre_delete/post_delete پشتیبانی می‌کند (برخلاف update).

متدهای مدل‌های رابطه‌ای


یکی از مهم‌ترین توانایی‌های ORM جنگو، کار با داده‌های مرتبط بین مدل‌ها است. در پروژه‌های واقعی، داده‌ها معمولا به‌صورت مستقل ذخیره نمی‌شوند؛ مثلاً یک کاربر ممکن است یک پروفایل داشته باشد، یک نویسنده می‌تواند چند کتاب نوشته باشد، یا در یک پروژه چندین تکنولوژی استفاده شده باشد.

برای مدیریت و جستجو در این روابط، جنگو مجموعه‌ای از کوئری‌های مخصوص فیلدهای رابطه‌ای فراهم کرده است که امکان می‌دهد

  • داده‌های مرتبط را خیلی راحت استخراج نمود.

  • روی فیلدهای مدل‌های مرتبط فیلتر یا مرتب‌سازی انجام داد.

  • داده‌های وابسته را با کمترین تعداد Query ممکن بازیابی کرد.

این بخش دقیقا روی این موضوع تمرکز خواهد داشت و در نهایت یاد می‌گیریم چگونه با استفاده از کوئری‌ها:

  1. روابط یک به چند (One-to-Many) مثل نویسنده و کتاب

  2. روابط چند به چند (Many-to-Many) مثل پروژه‌ها و تکنولوژی‌ها

  3. روابط یک به یک (One-to-One) مثل کاربر و پروفایل

را مدیریت نمود. همچنین به نکات مهمی مثل استفاده از lookups و بهینه‌سازی Queryها با select_related و prefetch_related هم خواهیم پرداخت.

🔹 جمع‌بندی
  1. روابط در جنگو سه نوع اصلی دارند: ForeignKey، ManyToManyField، OneToOneField.

  2. می‌توانیم با استفاده از __ به فیلدهای مدل‌های مرتبط دسترسی پیدا کنیم.

  3. متدهای select_related و prefetch_related برای بهینه‌سازی Queryها ضروری هستند.

  4. مدیریت داده‌های رابطه‌ای در جنگو بسیار ساده و مشابه نوشتن کوئری‌های SQL است، اما با قابلیت‌های سطح بالاتر و امن‌تر.

 

رابطه‌ی ForeignKey


گفتیم که در مدل‌های رابطه‌ای داده‌ها، یکی از رایج‌ترین انواع روابط، رابطه یک به چند (One-to-Many) که در جنگو با ForeignKeyField تعریف می‌گردد. همجنین مدلی برای مدیریت پروژه‌ها ایجاد کردیم که هر پروژه از مدل فرزند Project به یک کاربر (مالک) از مدل والد User تعلق داشته باشد. ولی در عین حال، کاربران می‌توانستند مالک چندین پروژه باشند.

class Project(models.Model):
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="projects")  # ManyToOne Field
    ...

✺✳ دسترسی مستقیم و معکوس (Direct & Reverse Access) ✳✺ 

با تعریف جنگو، ForeignKey،  به‌صورت خودکار امکان دسترسی معکوس را فراهم می‌کند. یه عبارتی، علاوه بر اینکه از طربق پروژه می‌توان به مالک آن دسترسی داشت، از کاربر نیز می‌توان به لیست پروژه‌هایش دسترسی پیدا کرد.

 

از سمت فرزند به والد (Forward)


— دریافت مالک یک پروژه

projectObj = Project.objects.get(id="ac260bde5f1347449f239052420573a7")
owner = projectObj.owner

⚠️ در اینجا، owner نام فیلدی است که در مدل پروژه (Project) تعریف شده و یک رابطه یک‌به‌چند (OneToMany) یا همان ForeignKey را با مدل User ایجاد کرده است.

 

⮜ از سمت والد به فرزندان (Reverse)


— دریافت تمام پروژه‌های یک کاربر

owner = User.objects.get(id=1) 

# Method 1: If related_name is not set (Django default)
projects = owner.project_set.all()

# Method 2: When related_name is set to "projects" (recommended)
projects = owner.projects.all() 

⚠️ اگر در تعریف ForeignKey از پارامتر related_name استفاده نموده باشیم، نباید از نام پیش‌فرض (modelname_set) استفاده کنیم. در غیر این صورت با خطای AttributeError مواجه خواهیم شد.

 

✺✳ فیلتر کردن بر اساس روابط (Lookups Expressions) ✳✺ 

یکی از قابلیت‌های قوی ORM جنگو، lookup expressions است که اجازه می‌دهد تا با استفاده از دو زیرخط  (__)، داده‌ها را بر اساس فیلدهای مدل‌های مرتبط جستجو نمود.

 

از سمت فرزند به والد (Forward)


— دریافت تمام پروژه‌های یک کاربر

projectObjs = Project.objects.filter(owner__username="admin")

⚠️ در اینجا، owner نام فیلدی است که در مدل پروژه (Project) تعریف شده و یک رابطه یک‌به‌چند (OneToMany) یا همان ForeignKey را با مدل User ایجاد کرده است.

 

از سمت والد به فرزندان (Reverse)


— دریافت تمام کاربرانی که حداقل یک پروژه در حوزه "ai" دارند

# Method 1: If related_name is not set (Django default)
userObjs = User.objects.filter(project__area__icontains="ai")

# Method 2: When related_name is set to "projects" (recommended)
users = User.objects.filter(projects__area__icontains="ai")

⚠️ project نام مدل مرتبط است ( در صورت عدم تعریف پارامتر related_name، جنگو به‌صورت پیش‌فرض، از نام مدل به صورت کوچک‌شده استفاده می‌کند). اما اگر related_name="projects" تعریف شده باشد، باید از آن استفاده کرد.

رابطه‌ی OneToOne


در مدل‌های رابطه‌ای، رابطه یک به یک (One-to-One) زمانی استفاده می‌شود که هر رکورد از جدول A دقیقا با یک رکورد از جدول B مرتبط باشد و برعکس. این نوع رابطه در جنگو با استفاده از فیلد OneToOneField پیاده‌سازی می‌شود.

یکی از رایح‌ترین متداول‌ترین سناریوهای استفاده از OneToOneField در پروژه‌های جنگو، ایجاد یک مدل پروفایل کاربری (Profile) برای گسترش مدل کاربر و ذخیره اطلاعات اضافی است. این کار زمانی ضروری می‌شود که بخواهیم از مدل User پیش‌فرض جنگو استفاده کنیم (بدون سفارشی‌سازی آن)، اما نیاز به فیلدهای بیشتری مانند تاریخ تولد، شماره تلفن، بیوگرافی یا تصویر پروفایل داشته باشیم. استفاده از OneToOneField در این مورد کاملا منطقی خواهد بود چرا که هر کاربر فقط یک پروفایل دارد و هر پروفایل فقط به یک کاربر تعلق دارد.

رابطه یک به یک در جنگو، یک راه‌حل تمیز و ایمن برای گسترش مدل‌های موجود (به‌ویژه مدل User) بدون تغییر ساختار اصلی آن‌هاست. با استفاده از OneToOneField، می‌توان به‌راحتی اطلاعات جانبی را در مدل جداگانه‌ای ذخیره کرده و همچنان از تمام قابلیت‌های ORM جنگو (مانند دسترسی معکوس و فیلتر پیشرفته) بهره برد.

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE) # OneToOne Field

✺✳ دسترسی مستقیم و معکوس (Forward & Reverse Access) ✳✺ 

در OneToOneField، تفاوت منطقی بین forward و reverse محو می‌شود چرا که هر دو سمت یک شیء واحد را برمی‌گردانند. پس نیازی به تفکیک خاصی نیست و از دید کاربر، انگار دو طرف رابطه هم‌سطح هستند.

💡 در رابطه یک‌به‌یک، رابطه معکوس همیشه یک رکورد خواهد بود و نه لیستی از رکوردها ( QuerySet ) پس نیازی به _set و حتی .all() نیست (چون چندتا نیست!). و نام آن همان نام مدل کوچک‌شده است ( همانند profile، برای مدل Profile ). 

⮜ دسترسی مستقیم از سمت هر دو مدل - Forward


— دسترسی به پروفایل از طریق کاربر

user = User.objects.get(username="admin")
profile = user.profile
⚠️ اگر پروفایل برای کاربر وجود نداشته باشد، این خط با خطای DoesNotExist مواجه می‌شود. برای جلوگیری، می‌توان از hasattr یا get_or_create استفاده نمود.
 

— دسترسی به کاربر از طریق پروفایل

profile = Profile.objects.get(id=1)
user = profile.user

⚠️ این دسترسی همیشه ممکن است (چون هر پروفایل حتما به یک کاربر مرتبط است).

 

✺✳ فیلتر کردن بر اساس روابط (Lookups Expression) ✳✺ 

یادآور می‌شویم که در رابطه‌های OneToOne نیز جنگو همچنان اجازه می‌دهد تا با استفاده از lookupهای معکوس (__)، بر اساس فیلدهای مدل‌های مرتبط جستجو کنیم.

 

— دریافت کاربرانی که قبل از سال ۲۰۰۰ متولد شده‌اند.

users = User.objects.filter(profile__birth__lte="2000-01-01")

⚠️ اگر related_name را در OneToOneField تغییر داده باشیم (مثلا به user_profile)، باید از همان نام در lookup استفاده کنیم

 

— دریافت تمام پروفایل‌هایی که کاربری‌شان بعد از تاریخ خاصی ایجاد شده‌اند.

profiles = Profile.objects.filter(user__date_joined__gte="2023-01-01")

رابطه‌ی ManyToMany


در مدل‌های رابطه‌ای داده‌ها، رابطه چند به چند (Many-to-Many) زمانی استفاده می‌شود که هر رکورد از جدول A بتواند با چندین رکورد از جدول B مرتبط باشد و برعکس. این نوع رابطه در جنگو با استفاده از فیلد ManyToManyField پیاده‌سازی می‌شود.

یکی از رایج‌ترین سناریوهای استفاده از ManyToManyField در پروژه‌های جنگو، برچسب‌گذاری (tagging) است. در مدلی که تعریف نمودیم، هر پروژه می‌تواند دارای چندین برچسب باشد (مانند «Django»، «JavaScript»، «Python») و هر برچسب نیز می‌تواند به چندین پروژه تعلق داشته باشد. در این حالت، رابطه بین مدل Project و مدل Tag به‌وضوح از نوع چند به چند است.

class Project(models.Model):
    ...
    tags = models.ManyToManyField(Tag, related_name='projects')
    ...

این رابطه کاملا منطقی و کاربردی خواهد بود چرا که یک در یک پروژه می‌تواند هم «Django» استفاده گردد و هم «JavaScript»، و در عین حال، برچسب «Python» می‌تواند به ده‌ها پروژه دیگر نیز اختصاص داده شود.

رابطه چند به چند در جنگو، امکان مدل‌سازی انعطاف‌پذیر و واقع‌گرایانه‌ای را فراهم می‌کند. با استفاده از ManyToManyField، می‌توان به‌راحتی ارتباطات پیچیده بین موجودیت‌ها را مدیریت کرد و از قابلیت‌های پیشرفته ORM جنگو — از جمله دسترسی معکوس، فیلتر کردن بر اساس روابط، و افزودن/حذف برچسب‌ها — بهره برد، بدون نیاز به مدیریت دستی جدول واسط (junction table).


✺✳ دسترسی مستقیم و معکوس (Forward & Reverse Access) ✳✺ 

 

⮜ دسترسی معکوس - Reverse


— دریافت پروژه‌های دارای یک برچسب

tag = Tag.objects.get(name="ِDjango")

# Method 1: If related_name is not set (Django default)
projects = tag.project_set.all()

# Method 2: When related_name is set to "projects" (recommended)
projects = tag.projects.all()

⚠️ اگر در تعریف ManyToManyField از پارامتر related_name استفاده نموده باشیم، نباید از نام پیش‌فرض (modelname_set) — جنگو به‌صورت پیش‌فرض، از نام مدل به صورت کوچک‌شده بهره می‌برد — استفاده کنیم. در غیر این صورت با خطای AttributeError مواجه خواهیم شد.

 

⮜ دسترسی مستقیم - Forward


— دریافت تمام برچسب‌های مرتبط با یک پروژه خاص

project = Project.objects.get(id="ac260bde-5f13-4744-9f23-9052420573a7")
tags = project.tags.all()

⚠️ در اینجا، tags نام فیلدی است که در مدل پروژه (Project) تعریف شده و یک رابطه چندبه‌چند (ManyToMany) را با مدل Tag ایجاد کرده است.

 

✺✳ فیلتر کردن بر اساس روابط (Lookups Expression) ✳✺ 

lookupهای معکوس با مشخصه دو زیرخط  (__)، در رابطه‌های ManyToMany نیز جنگو همچنان اجازه می‌دهند بر اساس فیلدهای مدل‌های مرتبط جستجو کنیم.

 

⮜ فیلتر کردن معکوس - Reverse Lookup


— دریافت تمام پروژه‌هایی که دارای یک برچسب خاص هستند (فیلتر کردن برچسب‌ها بر اساس پروژه)

# Method 1: If related_name is not set (Django default)
tags = Tag.objects.filter(project__area="Sport")

# Method 2: When related_name is set to "projects" (recommended)
tags = Tag.objects.filter(projects__area="Sport")
 
⚠️این کوئری ممکن است برچسب‌ها را تکراری نشان دهد اگر به چند پروژه مرتبط باشند. برای جلوگیری از تکرار، می‌بایست از .distinct() استفاده کنیم
tags = Tag.objects.filter(projects__title__icontains="AI").distinct()

 

⮜ فیلتر کردن مستقیم - Forward Lookup


— دریافت تمام پروژه‌هایی که دارای یک برچسب خاص هستند (فیلتر کردن برچسب‌ها بر اساس پروژه)

projects = Project.objects.filter(tags__name="Python")
projects = Project.objects.filter(tags__name__icontains="java")

⚠️ در اینجا، tags نام فیلدی است که در مدل پروژه (Project) تعریف شده و یک رابطه چندبه‌چند (ManyToMany) را با مدل Tag ایجاد کرده است.

 

✺✳ متدهای RelatedManager ✳✺ 

فرآیند افزودن (add) و حذف (remove) رکوردها در یک رابطه ManyToManyField در جنگو بسیار ساده و شهودی است، چون جنگو به‌طور خودکار یک سیستم مدیریت ویژه (RelatedManager) برای این فیلد ایجاد می‌کند که متدهای کاربردی‌ مثل add(), remove(), clear(), و set() را فراهم می‌کند.

 

+ افزودن رکوردها به رابطه (add)


—  به صورت مستقیم (Forward) از سمت مدل Project 

fsProject = Project.objects.get(title="Fluent Speech")
jsTag = Tag.objects.get(name="JavaScript")
djTag = Tag.objects.get(name="Django")

fsProject.tags.add(djTag)
fsProject.tags.add(jsTag, djTag)

—  به صورت معکوس (Reverse) از سمت مدل Tag (با استفاده از related_name)

djTag = Tag.objects.get(name="Django")
fsProject= Book.objects.get(title="Fluent Speech")
ygProject = Book.objects.get(title="Yoga Academy")

djTag.projects.add(fsProject, ygProject)
 

— حذف رکوردها از رابطه (remove)


—  به صورت مستقیم (Forward) از سمت مدل Project 

fsProject = Project.objects.get(title="Fluent Speech")
djTag = Tag.objects.get(name="Django")

fsProject.tags.remove(djTag)

 

—  به صورت معکوس (Reverse) از سمت مدل Tag (با استفاده از related_name)

djTag = Tag.objects.get(name="Django")
fsProject= Book.objects.get(title="Fluent Speech")

djTag.projects.remove(fsProject)

💡 اگر رابطه‌ای وجود نداشته باشد، remove() خطا نمی‌دهد — ساکت عمل می‌کند.

💡 فقط رابطه حذف می‌شود، رکوردهای اصلی (Project یا Tag) پاک نمی‌شوند.

 

× پاک کردن تمام رابطه‌ها (clear)


—  به صورت مستقیم (Forward) از سمت مدل Project 

fsProject = Book.objects.get(title="Fluent Speech")
fsProject.tags.clear()

 

—  به صورت معکوس (Reverse) از سمت مدل Tag (با استفاده از related_name)

djTag = Tag.objects.get(name="Django")
djTag.projects.clear()

💡 clear() فقط رابطه‌ها را پاک می‌کند، نه خود رکوردها.

 

جایگزینی کامل مجموعه (set)


—  به صورت مستقیم (Forward) از سمت مدل Project 

fsProject= Book.objects.get(title="Fluent Speech")
tagObjs = Tag.objects.filter(name__in=["Django", "JavaScript"])

fsProject.tags.set(tagObjs)

💡 تمام رابطه‌های قبلی پاک می‌شوند.

💡 فقط رابطه‌های جدید ایجاد می‌شوند.

💡 می‌توانید لیستی از شیءها یا حتی لیستی از IDها بدهید

fsProject.tags.set(["ab2050e16c94483db24aeab26b0c4330", "41de4854a25b40ac860c5b28ff4b8c17"])  # id = UUID
💡 اگر clear=False را نیز اضافه نماییم، فقط رابطه‌های جدید اضافه خواهند شد و عملکردی همانند add،خواهیم داشت، اما پیش‌فرض clear=True می‌باشد

بهینه‌سازی جستجوهای رابطه‌ای


در جنگو، وقتی از ORM برای بازیابی داده‌ها استفاده می‌کنیم، اگر روابط بین مدل‌ها (مثل ForeignKey یا ManyToManyField) زیاد باشد، ممکن است تعداد زیادی query به پایگاه داده ارسال شود. این پدیده به عنوان "N+1 Query Problem" شناخته می‌شود.

برای جلوگیری از این اتفاق، جنگو دو ابزار قدرتمند در اختیار قرار داده است:

  • select_related برای روابط ForeignKey / OneToOneField

  • prefetch_related برای روابط ManyToMany / reverse ForeignKey


— مشکل N+1


فرض کنیم می‌خواهیم همه‌ی پروژه‌ها را به همراه نام مالک آن‌ها نمایش دهیم:

projects = Project.objects.all()
for project in projects:
    print(project.title, project.owner.username)

🔻 اتفاقی که می‌افتد:

  • ابتدا یک query برای گرفتن همه‌ی پروژه‌ها زده می‌شود.

SELECT * FROM project;
  • سپس برای هر پروژه، یک query جدا برای owner (کاربر مرتبط) اجرا می‌شود.

SELECT * FROM user WHERE id = 1;
SELECT * FROM user WHERE id = 2;
SELECT * FROM user WHERE id = 3;

اگر ۱۰۰ پروژه داشته باشیم ← ۱۰۰ + ۱ = ۱۰۱ Query به پایگاه داده ارسال می‌گردد و این N+1 problem نام دارد و عملکرد را به شدت کند می‌کند.

استفاده از select_related و یا prefetch_related، تعداد کوئری‌ها را به حداقل می‌رساند .

 

— استفاده از select_related


برای روابط ForeignKey یا OneToOneField 

projects = Project.objects.select_related('owner').all()
for project in projects:
    print(project.title, project.owner.username)

اکنون فقط یک کوئری به پایگاه داده زده می‌شود و جنگو داده‌های هر پروژه را به‌ همراه مالک آن، همزمان دریافت می‌کند. در حقیقیت جنگو، با JOIN داده‌ها در SQL، داده‌های Proejct و User را همزمان واکشی می‌کند. و زمانی که project.owner.username را صدا می‌زنیم، دیگر نیازی به کوئری جدید نیست — چون داده‌ها از قبل preload شده‌اند.

استفاده از select_related، یکی از ضروری‌ترین تکنیک‌های بهینه‌سازی در جنگو است. در پروژه‌های واقعی، نادیده گرفتن آن می‌تواند منجر به کاهش شدید عملکرد و تجربه‌ی کاربری ضعیف شود. همیشه هنگام دسترسی به فیلدهای مدل‌های مرتبط، می‌بایست بررسی گردد که آیا می‌توان از select_related برای ارتباط‌های یک‌به‌چند و یک‌به‌یک و یا prefetch_related برای ارتباط‌های چندبه‌چند به منظور کاهش تعداد کوئری‌ها استفاده نمود.

 

— استفاده از prefetch_related


برای روابط ManyToMany یا reverse ForeignKey 

projects = Project.objects.prefetch_related('tags').all()
for project in projects:
    print(project.title, [tag.name for tag in project.tags.all()])

در این حالت جنگو دو Query اجرا می‌کند  و برای هر پروژه، لیست تگ‌های مربوط به آن را از داده‌های پیش‌بارگذاری‌شده استخراج می‌کند.

  1. یک کوئری برای واکشی تمام پروژه‌ها.

  2. یک کوئری دیگر برای واکشی تمام تگ‌های مرتبط با آن پروژه‌ها در یک مرحله.

با استفاده از prefetch_related، جنگو به‌صورت هوشمندانه در سطح پایتون، داده‌های واکشی‌شده را با هم تطبیق (match) می‌دهد و نتایج را در حافظه (Cache) ذخیره می‌کند تا به هنگام دسترسی به project.tags.all()، دیگر نیازی به کوئری جدید نباشد. در حقیقت جنگو، ابتدا مدل اصلی را fetch کرده، سپس یک کوئری جداگانه برای مدل مرتبط اجرا می‌کند و نتایج را در پایتون ترکیب می‌کند و در این مسیر از IN lookup برای محدود کردن نتایج استفاده می‌کند.

 

 — ترکیب select_related و prefetch_related


در پروژه‌های واقعی، معمولاً نیاز داریم همزمان با مدل‌های مرتبط یک‌به‌چند (ForeignKey) و روابط چند‌به‌چند (ManyToManyField) کار کنیم. در چنین شرایطی، ترکیب هوشمندانه select_related و prefetch_related بهترین راه‌حل برای جلوگیری از N+1 Query Problem است.

 

projects = Project.objects.select_related('owner').prefetch_related('tags')
for project in projects:
    print(f"{project.title} | {project.owner.username} | {[tag.name for tag in project.tags.all()]}")

📊 این ترکیب:

  • select_related ← فقط برای owner (ForeignKey)

  • prefetch_related ← برای tags (ManyToMany)

با این ترکیب، جنگو فقط سه کوئری به پایگاه داده ارسال می‌کند.

  1. یک JOIN بین داده‌های مدل‌های Project و User ← برای واکشی همزمان پروژه‌ها و اطلاعات مالک (owner) آن‌ها (با استفاده از select_related).
  2. یک کوئری برای واکشی idهای پروژه‌ها (در عمل، این بخش در کوئری اول گنجانده می‌شود).
  3. یک کوئری جداگانه برای واکشی تگ‌های مرتبط ← شامل دو جدول:
    • جدول میانی (مثلا project_tags) که ارتباط بین پروژه و تگ را نگه می‌دارد
    • جدول Tag برای گرفتن نام و سایر فیلدهای تگ‌ها ← این کوئری از WHERE tag_id IN (...) استفاده می‌کند تا فقط تگ‌های مرتبط با پروژه‌های انتخاب‌شده را بگیرد (با استفاده از prefetch_related).

در نتیجه، هیچ کوئری اضافی‌ در حلقه اجرا نمی‌شود — حتی اگر صدها پروژه و هزاران تگ وجود داشته باشد!

— اگر فقط از select_related استفاده می‌کردیم، دسترسی به project.tags.all() باعث ارسال یک کوئری جداگانه برای هر پروژه می‌شد.

— و اگر فقط از prefetch_related استفاده می‌کردیم، دسترسی به project.owner.username نیز همین مشکل را ایجاد می‌کرد.


ترتیب فراخوانی مهم نیست

💡 پیشنهاد: اگر نیاز به فیلتر یا مرتب‌سازی خاصی روی tags وجود داشته باشد، می‌توان از کلاس Prefetch استفاده نمود.

projects = Project.objects.select_related('owner').prefetch_related(
    Prefetch('tags', queryset=Tag.objects.filter(is_active=True).order_by('name'))
)

نمایش داده‌ها در تمپلیت


پس از تعریف مدل‌ها و ایجاد روابط بین آن‌ها — مانند ForeignKey، OneToOneField یا ManyToManyField — نوبت به نمایش داده‌ها در رابط کاربری می‌رسد. این مرحله در الگوی CRUD با عنوان عملیات Read شناخته می‌شود، چرا که هدف آن بازیابی و نمایش اطلاعات ذخیره‌شده در پایگاه‌داده است. در Django، این فرآیند معمولاً با ترکیب QuerySetها برای استخراج داده و Viewها برای ارسال آن‌ها به تمپلیت‌ها پیاده‌سازی می‌شود.

اصطلاح CRUD مخفف چهار عملیات پایه‌ای «ایجاد» (Create)، «خواندن» (Read)، «به‌روزرسانی» (Update) و «حذف» (Delete) است و هسته اصلی تعامل هر سامانه نرم‌افزاری با داده‌ها محسوب می‌شود. این چهار عملیات چارچوبی استاندارد برای مدیریت چرخه حیات داده‌ها در پایگاه‌های اطلاعاتی فراهم می‌کنند و در تقریباً تمام اپلیکیشن‌های تعاملی — از سیستم‌های مدیریت محتوا گرفته تا شبکه‌های اجتماعی و فروشگاه‌های آنلاین — به‌کار گرفته می‌شوند. هر بار که کاربری داده‌ای را وارد می‌کند، آن را مشاهده می‌کند، ویرایش می‌کند یا پاک می‌کند، در واقع یکی از عملیات CRUD را اجرا کرده است.

در میان این چهار عملیات، بخش Read نقشی حیاتی دارد؛ زیرا بدون آن، هیچ داده‌ای برای کاربر قابل مشاهده نخواهد بود. حتی اگر اطلاعات به‌درستی در پایگاه‌داده ذخیره شده باشند، تا زمانی که خوانده و به رابط کاربری ارسال نشوند، عملا وجود خارجی برای کاربر ندارند. هدف عملیات Read، دریافت داده‌های ذخیره‌شده در مدل‌ها و نمایش آن‌ها در تمپلیت برای کاربر نهایی است. بخش «ارسال داده‌ها به تمپلیت» در واقع قلب تپنده عملیات Read محسوب می‌شود؛ چرا که بدون این مرحله، حتی اگر داده‌ها با موفقیت از دیتابیس بازیابی شوند، کاربر هرگز آن‌ها را مشاهده نخواهد کرد.

در جنگو، این فرآیند معمولا با سه گام انجام می‌شود:

  1. استخراج داده‌ها در View با استفاده از QuerySetها
  2. ارسال داده‌ها به تمپلیت از طریق یک دیکشنری به نام context

  3. نمایش داده‌ها در HTML Template با کمک تگ‌ها، متغیرها و فیلترهای زبان تمپلیت جنگو

برای پیاده‌سازی این فرآیند، ابتدا باید داده‌های مدل — از جمله روابط معکوس (reverse relationships) — را در View بازیابی کرد. سپس این داده‌ها را در قالب یک دیکشنری (context) به تمپلیت ارسال نمود. در نهایت، در خود تمپلیت، با بهره‌گیری ازقابلیت‌های زبان تمپلیت جنگو — شامل متغیرها (Variables)، فیلترها (Filters) و تگ‌های تمپلیت (Template Tags) — داده‌ها به‌صورت مناسب نمایش داده می‌شوند.

 

در اینجا قصد داریم لیست تمام پروژه‌های تعریف‌شده در مدل Project را در مسیر /projects/ نمایش دهیم، به‌طوری که با انتخاب عنوان هر پروژه، کاربر به صفحه جزئیات آن (در مسیر /read-project/) هدایت شود.

 

جمع‌آوری داده‌ها در View


ابتدا در فایل views.py، یک تابع برای بازیابی تمام رکوردهای مدل Project و ارسال آن‌ها به تمپلیت تعریف می‌کنیم

coreapp/views.py

from . import models

def Projects(request):
  projectObjs = models.Project.objects.all()
  context = {'projects':projectObjs}
  return render(request, 'projects.html', context)
    • متد objects.all() تمامی رکوردهای مدل Project را از پایگاه داده واکشی می‌کند.
    • داده‌ها درون یک دیکشنری با کلید 'projects' قرار گرفته و به تمپلیت ارسال می‌شوند.

 

تعریف الگوی URL


پس از ایجاد تابع view، نیاز است مسیر مربوطه را در فایل urls.py ثبت کنیم تا درخواست‌های ارسالی به /projects/ به تابع View مربوطه هدایت شوند.

coreapp/urls.py

urlpatterns = [
    ... ,
    path('projects/', views.Projects, name='Projects'),
    ... ,
]

 

نمایش داده‌ها در تمپلیت


در نهایت، در فایل تمپلیت projects.html، لیستی از عناوین پروژه‌ها را با استفاده از حلقهٔ {% for %} نمایش خواهیم داد.

coreapp/templates/projects.html

{% extends 'base.html' %}
{% block title %} {{ block.super}} | Projects {% endblock %}
{% block content %}
  <h1>Projects List</h1>
  <ul>
    {% for project in projects %}
    <li> {{ project.title }} </li>
    {% empty %}
      <li>No Project Found</li>
    {% endfor %}
  </ul>
{% endblock %}
💡 استفاده از تگ {% empty %} در حلقه‌ها، روشی تمیز برای نمایش پیام جایگزین در صورت عدم وجود داده است.

در ننیجه با مراجعه به مسیر آدرس  http://127.0.0.1:8000/projects/ لیستی از تمامی پروژه‌ها به نمایش درخواهد آمد.

این پیاده‌سازی، نمونه‌ای کلاسیک از عملیات Read در الگوی CRUD است. داده‌ها از دیتابیس خوانده شده، به تمپلیت ارسال گشته و به‌صورت کاربرپسند نمایش داده می‌شوند — همان‌گونه که کاربران انتظار دارند.

 

نمایش جزئیات هر پروژه (Read Details)


حال، طبق شرحی که داده شد بنا داریم فرایند را تکمیل کنیم، به‌گونه‌ای که با کلیک روی عنوان هر پروژه ، کاربر به صفحه جزئیات آن هدایت شود. برای این منظور، ابتدا یک تابع View جدید برای نمایش جزئیات پروژه تعریف می‌کنیم.

coreapp/views.py

from . import models

def ProjectRead(request, uid):
  projectObj = models.Project.objects.get(id = uid)
  context = {'project': projectObj}
  return render(request, 'project-read.html', context)
    • پارامتر uid مقداری است که از آدرس URL دریافت می‌شود (در ادامه توضیح خواهیم داد چگونه).
    • متد get(id=uid) رکورد مربوط به آن شناسه را از مدل Project واکشی می‌کند.
    • داده‌ی واکشی‌شده در متغیر context قرار می‌گیرد و برای نمایش در تمپلیت، ارسال می‌گردد.

 

پس از ایجاد تابع view، نیاز است مسیر مربوطه را نیز در فایل urls.py ثبت کنیم تا درخواست کاربر به درستی هدایت شود.

coreapp/urls.py

urlpatterns = [
    ... ,
    path('project-read/<str:uid>/', views.ProjectRead, name='ProjectRead'),
    ... ,
]
    • project-read/<str:uid>/ — شکلی پویا (Dynamic) دارد.
    • <str:uid> بخش متغیر مسیر است.
    • پیشوند str: تعیین می‌کند که نوع داده‌ی ورودی، رشته (string) باشد.
    • نام uid همان کلیدی است که مقدار دریافتی از URL را به View ارسال می‌کند.

 

در نتیجه تابع ProjectRead() دقیقا می‌داند باید کدام پروژه را از پایگاه‌داده واکشی کند.

🔸 دلیل اینکه مسیر را به این شکل (<str:uid>) تعریف نمودیم این است که شناسه‌ی هر پروژه معمولا به‌صورت رشته‌ی یکتا (UUID) در مدل ذخیره می‌شود. بنابراین باید نوع داده‌ی ورودی را str تعریف کنیم تا جنگو بتواند مقدار UUID را به‌درستی دریافت و پردازش کند.

به‌عبارت دیگر، با این ساختار:

    • می‌توان به هر پروژه از طریق آدرس منحصربه‌فردش دسترسی پیدا نمود.
    • مسیرها خواناتر و استانداردتر می‌شوند.
    • عملیات بازیابی داده‌ها در View ساده‌تر و مستقیم‌تر انجام می‌گیرد.

 

در مرحله پایانی، تمپلیت project-read.html با دریافت اطلاعات پروژه از view، داده‌ها — مانند عنوان، حوزه کاری، تگ‌ها و توضیحات — را به‌شکل ساختارمند و قابل‌درک در رابط کاربری به نمایش درمی‌آورد. 

coreapp/templates/project-read.html

{% extends 'base.html' %}

{% block title %} {{ block.super}} | {{ project.title }} {% endblock %}

{% block content %}

  <h1> {{ project.title }} </h1>
  <h3> Project Area 🡢 {{ project.area }} </h3>

  <hr align="left" width="20%">

  <p> Technologies 🡢 
    {% for tag in project.tags.all %}
      <strong> #{{ tag.name }}{% if not forloop.last %},{% endif %} </strong>
    {% endfor %}
  </p>

  <hr align="left" width="20%">

  <p>{{ project.content|linebreaksbr }}</p>

{% endblock %}
  • از {{ project.field_name }} برای دسترسی به فیلدهای پروژه استفاده می‌گردد.

  • حلقه‌ی for تگ‌های مرتبط را نمایش می‌دهد.

  • فیلتر linebreaksbr خطوط متن را به <br> در HTML تبدیل می‌کند تا متن خواناتر شود.

 

برای تکمیل فرایند لازم هست در لیست پروژه‌ها، لینک هر عنوان را به صفحه جزئیات آن متصل نموده تا با کلیک بر روی عنوان، کاربر به صفحه‌ی مربوطه منتقل شود.

coreapp/templates/projects.html

{% extends 'base.html' %}
{% block title %} {{ block.super}} | Projects {% endblock %}
{% block content %}
  <h1>Projects List</h1>
  <ul>
    {% for project in projects %}
    <li> 
      <a href="{% url "ProjectRead" project.id %}">{{ project.title }} </a> <!-- Define Redirected Links -->
    </li> 
    {% endfor %}
  </ul>
{% endblock %}

حال در صورت انتخاب عنوان هر پروژه در مسیر آدرس /projects/، کاربر به صفحه جزئیات آن (در مسیر /read-project/<id>) هدایت خواهد شد.

این ساختار پایه، اساس نمایش داده‌ها در بسیاری از صفحات وب — از لیست محصولات تا نمایش جزئیات کاربران — محسوب می‌شود.