به دنیای واقعی جنگو خوش آمدید! اگر تا اینجای کار فقط اسکلت‌بندی پروژه را انجام دادیم، حالا وقت آن است که به پروژه رهگیری آگهی های استخدام خودمان حافظه اضافه کنیم. در این درس، سراغ مدل‌ها در جنگو (Django Models) می‌رویم؛ یعنی همان جایی که مشخص می‌کنیم دیتابیس ما قرار است چه اطلاعاتی را از یک آگهی شغلی در خود نگه دارد.

جادوی پایتون به جای کدهای پیچیده SQL

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

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

چرا درک مدل‌ها برای هر برنامه‌نویسی حیاتی است؟

اگر مدل‌های پروژه را اشتباه طراحی کنید، کل ساختمان سایت شما روی زمین لرزانی بنا می‌شود. کلمات کلیدی مثل ساخت دیتابیس در جنگو یا فیلدهای مدل (Model Fields) دقیقاً به همین بخش اشاره دارند. در این درس، ما یاد می‌گیریم که چطور مدل Job را بسازیم تا اطلاعاتی مثل:

  • عنوان شغلی و توضیحات آگهی
  • نام شرکت و موقعیت مکانی
  • تاریخ ثبت و وضعیت وضعیت اپلای

را به شکلی منظم ذخیره کنیم. این اطلاعات، سوخت اصلی تمام بخش‌های بعدی سایت ما یعنی ویوها و قالب‌ها خواهند بود.

در این درس چه چیزی یاد می‌گیرید؟

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

  • انتخاب درست انواع فیلدها در جنگو (از متن‌های کوتاه تا توضیحات طولانی).
  • کار با دستورات حیاتی Migrations برای اعمال تغییرات در دیتابیس.
  • شخصی‌سازی نمایش مدل‌ها در پنل ادمین برای مدیریت راحت‌تر آگهی‌ها.

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

مفهوم ORM در جنگو: پل ارتباطی پایتون و دیتابیس

بسیاری از برنامه‌نویسان وقتی به بخش ذخیره اطلاعات می‌رسند، نگران یادگیری زبان‌های پیچیده‌ای مثل SQL هستند. اما در پروژه JobTrack، ما از قابلیتی به نام ORM جنگو استفاده می‌کنیم که تمام این نگرانی‌ها را از بین می‌برد. ORM مخفف Object-Relational Mapping است، اما اگر بخواهم ساده‌تر بگویم، این ابزار مثل یک مترجم حرفه‌ای عمل می‌کند.

خداحافظی با کدهای طولانی SQL

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

چرا ORM سرعت توسعه شما را چند برابر می‌کند؟

استفاده از این تکنولوژی فقط برای راحتی نیست؛ بلکه امنیت و انعطاف‌پذیری پروژه شما را تضمین می‌کند:

  • امنیت در برابر هکرها: ORM به صورت خودکار جلوی حملات خطرناکی مثل SQL Injection را می‌گیرد.
  • استقلال از نوع دیتابیس: فرقی نمی‌کند از SQLite استفاده می‌کنید یا PostgreSQL؛ شما کدهای پایتونی خودتان را می‌نویسید و جنگو خودش را با زبان آن دیتابیس هماهنگ می‌کند.
  • خوانایی کد: کدهای شما به جای دستورات شلوغ پایگاه داده، شبیه به بقیه بخش‌های برنامه باقی می‌ماند.

تعامل با داده‌ها به سبک پایتون

وقتی از ORM جنگو استفاده می‌کنید، عملیات‌های اصلی روی داده‌ها (CRUD) بسیار لذت‌بخش می‌شود. برای مثال، اگر بخواهید تمام آگهی‌های استخدامی را از دیتابیس بگیرید، به جای یک کوئری پیچیده، کافی است بنویسید: Job.objects.all(). این سادگی به شما اجازه می‌دهد تمرکزتان را روی منطق اصلی سایت بگذارید، نه روی چالش‌های فنی ذخیره‌سازی.

یک تجربه واقعی:

«بسیاری از توسعه‌دهندگان بزرگ دنیا اعتراف می‌کنند که حتی با وجود تسلط به SQL، باز هم ترجیح می‌دهند از ORM استفاده کنند. چون مدیریت روابط بین جداول (مثل اتصال یک آگهی به دسته‌بندی خاص) در این روش، بسیار هوشمندانه و بدون خطا انجام می‌شود.»

طراحی مدل Job: تعریف فیلدها و انواع داده (Model Fields)

وقت آن رسیده که فایل models.py را در اپلیکیشن jobs باز کنیم و به آگهی‌های شغلی هویت بدهیم. در این مرحله، ما مشخص می‌کنیم که هر فرصت شغلی در سامانه JobTrack چه ویژگی‌هایی دارد. هر متغیری که اینجا تعریف می‌کنید، تبدیل به یک ستون در جدول پایگاه داده شما می‌شود.

انتخاب هوشمندانه انواع فیلد در مدل‌های جنگو

جنگو برای هر نوع داده‌ای، یک ابزار مخصوص دارد. شما نمی‌توانید برای "نام شرکت" و "توضیحات طولانی شغل" از یک ابزار یکسان استفاده کنید. بیایید با مهم‌ترین Model Fields که در این پروژه نیاز داریم آشنا شویم:

CharField: برای متن‌های کوتاه مثل «عنوان شغل» یا «نام شرکت». حتماً باید برای آن یک max_length تعیین کنید تا فضای دیتابیس هدر نرود.

TextField: این فیلد برای متن‌های طولانی و چندخطی مثل «شرح وظایف» یا «شرایط احراز» ساخته شده است.

IntegerField: اگر می‌خواهید «میزان حقوق» یا «سابقه کار مورد نیاز» را به صورت عدد ذخیره کنید، این فیلد بهترین گزینه است.

EmailField: برای بخش «ایمیل تماس»؛ این فیلد خودش چک می‌کند که متن وارد شده فرمت صحیح ایمیل را داشته باشد.

DateTimeField: برای اینکه بدانیم آگهی چه زمانی ثبت شده است. با تنظیم auto_now_add=True جنگو خودش تاریخ لحظه ثبت را ذخیره می‌کند.

پیاده‌سازی عملی مدل Job

کدهای زیر اسکلت اصلی دیتابیس ما را می‌سازند. دقت کنید که چطور هر ویژگی آگهی را به یک فیلد مخصوص متصل کرده‌ایم:

from django.db import models

class Job(models.Model):
    title = models.CharField(max_length=100)
    company = models.CharField(max_length=150)
    description = models.TextField()
    salary = models.IntegerField(null=True, blank=True)
    is_active = models.BooleanField(default=True)

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

کدی که نوشتید، در واقع شناسنامه و ستون فقرات دیتابیس پروژه JobTrack است. هر خط از این کلاس به جنگو می‌گوید که چطور یک فضای اختصاصی در حافظه برای آگهی‌های شغلی رزرو کند. بیایید این تکه کد را کالبدشکافی کنیم تا ببینیم پشت هر کلمه چه منطقی نهفته است:

۱. ارث‌بری از models.Model

کلاس ما از models.Model ارث‌بری می‌کند. این یعنی ما به جنگو می‌گوییم: «این یک کلاس معمولی پایتون نیست؛ این یک مدل دیتابیس است.» با این کار، تمام قدرت ORM جنگو به این کلاس منتقل می‌شود تا بتواند با پایگاه داده تعامل کند.

۲. بررسی فیلدها و محدودیت‌ها

در این بخش، شما برای هر ویژگی آگهی، یک نوع داده (Data Type) مشخص کرده‌اید:

title و company: از CharField استفاده کردید چون این مقادیر کوتاه هستند. پارامتر max_length بسیار حیاتی است؛ چون به دیتابیس می‌گوید دقیقاً چقدر فضا (مثلاً ۱۰۰ یا ۱۵۰ کاراکتر) برای این نوشته‌ها کنار بگذارد. این کار باعث بهینه شدن حجم پایگاه داده می‌شود.

description: برخلاف عنوان، توضیحات شغل می‌تواند چندین پاراگراف باشد. TextField هیچ محدودیتی در تعداد کلمات ندارد و برای متون طولانی طراحی شده است.

salary (حقوق): استفاده از IntegerField عالی است چون بعداً می‌توانید روی آن فیلتر بگذارید (مثلاً آگهی‌هایی با حقوق بالای ۲۰ میلیون).

نکته فنی: پارامتر null=True اجازه می‌دهد این ستون در دیتابیس خالی بماند و blank=True اجازه می‌دهد در فرم‌های سایت، کاربر بتواند این بخش را پر نکند.

is_active: یک فیلد از نوع BooleanField (بله/خیر). این فیلد برای مدیریت آگهی‌ها فوق‌العاده است. به جای حذف کردن آگهی‌های قدیمی، کافی است این گزینه را روی False بگذارید تا دیگر در سایت نمایش داده نشوند اما سوابق آن‌ها در دیتابیس باقی بماند.

۳. جادوی پیش‌فرض‌ها (Default)

در فیلد is_active از default=True استفاده شده است. این یعنی به محض اینکه یک آگهی جدید ثبت شود، به صورت خودکار "فعال" در نظر گرفته می‌شود، مگر اینکه شما دستی آن را تغییر دهید. این کار باعث می‌شود سرعت ثبت اطلاعات بالاتر برود.

۴. این کد در دیتابیس چه شکلی می‌شود؟

وقتی شما این مدل را می‌سازید، جنگو در دیتابیس (مثلاً SQLite) جدولی به نام jobs_job ایجاد می‌کند که ستون‌های آن دقیقاً همین نام‌هایی است که شما تعریف کرده‌اید.

یک نکته که نباید فراموش کنید:
جنگو به صورت خودکار یک ستون به نام id به این جدول اضافه می‌کند که عددی یکتا برای هر آگهی است (Primary Key). پس نیازی نیست خودتان دستی فیلد id را تعریف کنید.

چرا طراحی دقیق فیلدها اهمیت دارد؟

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

نکته حرفه‌ای برای سئو:

استفاده از فیلدهایی مثل SlugField برای آدرس‌های سایت (URL) معجزه می‌کند. این فیلد اجازه می‌دهد به جای آدرس‌های بی‌معنی مثل /job/1/ آدرس‌های جذابی مثل /job/python-developer/ داشته باشید که هم کاربر بپسندد و هم گوگل رتبه بهتری به آن بدهد.

جادوی Migrations: چطور تغییرات را به دیتابیس بفهمانیم؟

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

گام اول: تهیه لیست تغییرات با makemigrations

هر بار که مدلی را می‌سازید یا فیلد جدیدی به آن اضافه می‌کنید، باید ابتدا دستور زیر را در ترمینال اجرا کنید:

python manage.py makemigrations

با اجرای این دستور، جنگو کدهای شما را بررسی می‌کند و یک فایل جدید در پوشه‌ی migrations اپلیکیشن jobs می‌سازد. این فایل حاوی دستوراتی است که به زبان دیتابیس (SQL) ترجمه شده‌اند، اما شما هنوز می‌توانید آن‌ها را به زبان پایتون ببینید. در واقع، این مرحله فقط «آماده‌سازی» است و هنوز تغییری در پایگاه داده اصلی رخ نداده است.

گام دوم: اعمال نهایی تغییرات با دستور migrate

حالا وقت آن است که تغییرات را نهایی کنید. با اجرای دستور زیر، جنگو فایل‌های ساخته شده در مرحله قبل را برداشته و جداول را در دیتابیس (مثلاً SQLite) ایجاد می‌کند:

python manage.py migrate

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

چرا سیستم Migrations برای توسعه‌دهنده یک نجات‌دهنده است؟

تصور کنید شش ماه بعد تصمیم می‌گیرید فیلد «دورکاری» را به آگهی‌ها اضافه کنید. در حالت عادی باید نگران داده‌های قبلی باشید، اما Migrations در جنگو مثل یک سیستم کنترل نسخه (مثل Git) عمل می‌کند. این سیستم:

  • تمام تاریخچه تغییرات دیتابیس را ذخیره می‌کند.
  • اجازه می‌دهد بدون پاک شدن داده‌های قبلی، ستون‌های جدید اضافه کنید.
  • اگر اشتباهی کردید، به شما اجازه می‌دهد به نسخه‌ی قبلی دیتابیس برگردید.

نکته حیاتی برای رفع ارور:

یکی از رایج‌ترین اشتباهات برنامه‌نویسان تازه‌کار این است که بعد از تغییر در models.py فراموش می‌کنند دستور migrate را بزنند. اگر با ارور no such table مواجه شدید، بدانید که دیتابیس هنوز از وجود مدل‌های جدید شما بی‌خبر است و باید دوباره این دو دستور را اجرا کنید.

متد __str__: شخصی‌سازی نمایش مدل در پنل مدیریت

وقتی در حال طراحی مدل‌های دیتابیس هستید، شاید به این فکر کنید که جنگو چطور قرار است این اطلاعات را به شما نشان دهد. اگر همین حالا مدل Job را رها کنید، جنگو در تمام بخش‌های مدیریتی، هر آگهی را با یک نام گنگ مثل Job object (1) می‌شناسد. این یعنی اگر صدها آگهی داشته باشید، پیدا کردن یکی از آن‌ها شبیه به پیدا کردن سوزن در انبار کاه می‌شود. راه حل این مشکل در متد str در جنگو نهفته است.

نام‌گذاری هوشمندانه برای آگهی‌های شغلی

متد __str__ یک تابع جادویی در پایتون است که به جنگو می‌گوید: «هر وقت خواستی این مدل را به من نشان بدهی، از این فیلد خاص استفاده کن.» برای پروژه JobTrack، بهترین انتخاب این است که هر آگهی با "عنوان شغلی" خودش نمایش داده شود.

کد قبلی را با اضافه کردن این چند خط، به یک نسخه حرفه‌ای تبدیل می‌کنیم:

class Job(models.Model):
    title = models.CharField(max_length=100)
    # ... بقیه فیلدها
    
    def __str__(self):
        return self.title

با همین دو خط ساده، به جای دیدن کلمات بی‌معنی، دقیقاً نام آگهی (مثلاً «برنامه‌نویس پایتون» یا «مدیر پروژه») را خواهید دید. این موضوع در مدیریت پنل ادمین جنگو که در درس‌های آینده با آن کار می‌کنیم، سرعت عمل شما را به شدت بالا می‌برد.

ترکیب فیلدها برای نمایش دقیق‌تر

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

def __str__(self):
    return f"{self.title} - {self.company}"

حالا در لیست‌های مدیریتی، عبارت «برنامه‌نویس پایتون - شرکت آلفا» را مشاهده می‌کنید. این کار باعث می‌شود دیتابیس شما از یک فضای سیاه و سفید و عددی، به یک محیط کاملاً انسانی و خوانا تبدیل شود.

چرا باید همین حالا این متد را اضافه کنیم؟

شاید بپرسید «چرا الان که هنوز سراغ پنل ادمین نرفته‌ایم، این را یاد می‌گیریم؟». دلیلش ساده است: متد str بخشی از ساختار مدل شماست. اگر از همین ابتدا عادت کنید که مدل‌های خود را خوانا طراحی کنید، در مراحل بعدی که می‌خواهیم کاربران و مدیران سایت را بسازیم، با یک محیط آماده و تمیز روبرو می‌شویم.

این متد نه تنها در پنل مدیریت، بلکه در هر جایی که بخواهید لیستی از مدل‌ها را در خروجی چاپ کنید (مثلاً در کنسول یا در صفحات وب) به کمک شما می‌آید. در واقع، شما با این کار دارید به اشیاء بی‌جان دیتابیس، "نام و نشان" می‌دهید.

نکته: خروجی متد __str__ همیشه باید یک رشته (String) باشد. اگر بخواهید مثلاً میزان حقوق (که عدد است) را برگردانید، حتماً باید آن را با استفاده از تابع str() یا f-string به متن تبدیل کنید تا جنگو دچار خطا نشود.

کار با Meta در مدل‌ها: مرتب‌سازی و نام‌گذاری فارسی

گاهی اوقات فیلدها و متدها برای مدیریت کامل یک مدل کافی نیستند. شما نیاز دارید به جنگو بگویید که کل این جدول چه رفتاری داشته باشد؛ مثلاً آگهی‌ها بر چه اساسی ردیف شوند یا در محیط مدیریت با چه نامی دیده شوند. اینجاست که کلاس Meta در جنگو وارد بازی می‌شود. این کلاس در واقع تنظیماتِ تنظیماتِ مدل شماست!

مرتب‌سازی خودکار: همیشه جدیدترین‌ها اول باشند

در سامانه JobTrack، هیچ‌کس دوست ندارد ابتدا آگهی‌های سه ماه پیش را ببیند. با استفاده از ویژگی ordering در کلاس متا، می‌توانید کاری کنید که دیتابیس همیشه آگهی‌ها را بر اساس زمان ثبت مرتب کند.

class Job(models.Model):
    # فیلدها...
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']

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

بومی‌سازی و نام‌گذاری فارسی (Verbose Name)

به صورت پیش‌فرض، جنگو نام مدل شما را در همه‌جا (مثل پنل‌های مدیریتی) به همان صورت انگلیسی یعنی Job نشان می‌دهد. برای اینکه یک پنل مدیریتی فارسی و کاربرپسند داشته باشید، از verbose_name استفاده می‌کنیم:

class Meta:
    verbose_name = "آگهی استخدام"
    verbose_name_plural = "آگهی‌های استخدام"

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

چرا استفاده از Meta برای سئو و نظم پروژه مهم است؟

وقتی شما از تنظیمات کلاس متا در مدل استفاده می‌کنید، در واقع دارید زیرساخت داده‌های خود را استاندارد می‌کنید.

  • مدیریت آسان: وقتی تعداد مدل‌های پروژه زیاد شود (مثلاً مدل دسته‌بندی، مدل رزومه و...)، نام‌گذاری درست فارسی مانع از سردرگمی مدیر سایت می‌شود.
  • دقت در خروجی: مرتب‌سازی در لایه مدل (Database Level) بسیار سریع‌تر از مرتب‌سازی در لایه پایتون است. این یعنی سرعت لود صفحات شما بالاتر می‌رود که یکی از فاکتورهای حیاتی سئو در سال ۲۰۲۶ است.

نکته: کلاس Meta باید حتماً داخل کلاس اصلی مدل (به صورت اینتدنت شده) تعریف شود. این کلاس هیچ فیلدی به دیتابیس اضافه نمی‌کند و فقط "رفتار" مدل را تغییر می‌دهد.

ایجاد ارتباط بین مدل‌ها (Foreign Key): اتصال آگهی به دسته‌بندی

دنیای واقعی پر از ارتباطات است و دیتابیس شما هم نباید از این قاعده مستثنی باشد. اگر بخواهید تمام اطلاعات را در یک جدولِ غول‌آسا بچپانید، با اولین تغییر کوچک کل سیستم از هم می‌پاشد. در جنگو، ما با استفاده از روابط بین مدل‌ها (Model Relationships)، جداول را به هم زنجیر می‌کنیم تا داده‌ها هوشمندانه و منظم ذخیره شوند.

انواع ارتباط در دیتابیس به زبان ساده

پیش از اینکه سراغ کدنویسی پروژه JobTrack برویم، بیایید ببینیم کلاً چند نوع رابطه در دنیای پایگاه داده داریم:

۱. یک به یک (One-to-One): مثل رابطه «هر شخص» با «کد ملی». هر آدم فقط یک کد ملی دارد و هر کد ملی هم متعلق به یک نفر است.
۲. یک به چند (One-to-Many): مثل رابطه «یک نویسنده» با «کتاب‌هایش». یک نویسنده می‌تواند ده کتاب بنویسد، اما هر کتاب معمولاً متعلق به یک نویسنده مشخص است. این پرکاربردترین نوع رابطه است.
۳. چند به چند (Many-to-Many): مثل رابطه «دانشجویان» و «واحد‌های درسی». هر دانشجو می‌تواند چندین درس بردارد و هر درس هم توسط چندین دانشجو انتخاب می‌شود.

پیاده‌سازی رابطه یک به چند با Foreign Key

در پروژه ما، هر «آگهی استخدام» باید به یک «دسته‌بندی» (مثلاً برنامه‌نویسی، مارکتینگ یا حسابداری) متصل باشد. از آنجایی که یک دسته‌بندی می‌تواند هزاران آگهی داشته باشد، اما هر آگهی فقط در یک دسته‌بندی قرار می‌گیرد، ما از Foreign Key در جنگو استفاده می‌کنیم.

ابتدا یک مدل برای دسته‌بندی می‌سازیم و سپس آن را به مدل Job متصل می‌کنیم:

class Category(models.Model):
    name = models.CharField(max_length=50)

    class Meta:
        verbose_name = "دسته‌بندی"
        verbose_name_plural = "دسته‌بندی‌ها"

    def __str__(self):
        return self.name

class Job(models.Model):
    title = models.CharField(max_length=100)
    # ایجاد ارتباط با کلید خارجی
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='jobs')
    # ... بقیه فیلدها

پارامتر on_delete چه کاربردی دارد؟

وقتی از کلید خارجی (Foreign Key) استفاده می‌کنید، باید به جنگو بگویید که اگر یک دسته‌بندی پاک شد، تکلیف آگهی‌های آن چه شود؟

CASCADE: اگر دسته‌بندی «برنامه‌نویسی» حذف شود، تمام آگهی‌های زیرمجموعه آن هم به صورت خودکار پاک می‌شوند.

SET_NULL: دسته‌بندی پاک می‌شود اما آگهی‌ها باقی می‌مانند و مقدار دسته‌بندی آن‌ها خالی (Null) می‌شود.
انتخاب ما در اینجا CASCADE است تا دیتابیس همیشه تمیز و بدون داده‌های یتیم باقی بماند.

چرا این مدل‌سازی برای سئو و توسعه عالی است؟

استفاده از روابط مدل‌ها در جنگو فقط یک بحث فنی نیست؛ این کار به شما اجازه می‌دهد در آینده صفحات سایت را بر اساس دسته‌بندی فیلتر کنید. مثلاً یک صفحه اختصاصی برای «آگهی‌های برنامه نویسی پایتون» بسازید. این ساختار منظم، هم برای گوگل خوشایند است (به دلیل لینک‌سازی داخلی بهتر) و هم برای کاربر که به سرعت به شغل مورد نظرش می‌رسد.

ایجاد راه بازگشت با این ترفند

تصور کنید یک جاده یک‌طرفه از آگهی به سمت دسته‌بندی ساخته‌اید. در حالت عادی، هر آگهی می‌داند متعلق به چه دسته‌ای است (مثلاً آگهی «برنامه نویس پایتون» می‌داند که در دسته «برنامه‌نویسی» قرار دارد). اما اگر در میدانِ «دسته‌بندی» بایستید و بخواهید بدانید چه آگهی‌هایی به شما اشاره می‌کنند، مسیر مسدود است!

اینجاست که related_name='jobs' مثل یک جاده بازگشت عمل می‌کند. بیایید این مفهوم را با جزییات و به زبان ساده کالبدشکافی کنیم:

۱. نام‌گذاری جاده برگشت

وقتی در مدل Job (آگهی)، فیلد کلید خارجی را تعریف می‌کنید، با نوشتن related_name='jobs' در واقع دارید به جنگو می‌گویید: «اگر روزی سراغ یک دسته‌بندی رفتم، با چه اسمی صدا بزنم تا تمام آگهی‌هایش را به من بدهد؟»
شما این اسم را jobs گذاشته‌اید چون خروجی آن، لیستی از شغل‌هاست.

۲. قدرت استخراج داده‌های مرتبط

بدون این ویژگی، کدنویسی شما سخت و طولانی می‌شود. اما با این جادوی کوچک، فرض کنید می‌خواهید در صفحه دسته‌بندی «برنامه‌نویسی»، تعداد کل آگهی‌های آن را نمایش دهید. کافی است بنویسید:

my_category.jobs.all()

my_category: همان دسته‌بندی خاص (مثلاً برنامه‌نویسی) است.

.jobs: همان نامی است که خودتان در مدل انتخاب کردید (جاده برگشت).

.all(): یعنی تمام آگهی‌هایی که به این دسته‌بندی متصل هستند را برای من بیاور.

۳. چرا این جزییات برای پروژه JobTrack حیاتی است؟

اگر این نام را انتخاب نکنید، جنگو خودش یک نام پیش‌فرض و کمی زشت مثل job_set انتخاب می‌کند. اما وقتی آگاهانه از jobs استفاده می‌کنید:

  • کد شما خواناتر می‌شود: هر کسی کد را ببیند، می‌فهمد که category.jobs یعنی لیست شغل‌های آن دسته.
  • در ویوها (Views) معجزه می‌کند: وقتی می‌خواهید در صفحه اصلی سایت، آگهی‌ها را بر اساس دسته‌بندی تفکیک کنید، این دستور ساده جایگزین ده‌ها خط کد پیچیده می‌شود.

یک مثال ملموس:

فرض کنید دسته‌بندی «طراحی گرافیک» را دارید. با دستور category.jobs.count() می‌توانید در یک ثانیه بفهمید چند آگهی فعال برای طراحان در سایت موجود است. این یعنی مدیریت هوشمند داده‌ها با کمترین زحمت!

چک‌لیست نهایی مدل‌ها برای ورود به پنل ادمین

قبل از اینکه در درس بعدی سراغ ساخت superuser بروید، مطمئن شوید که این ۵ مورد در فایل models.py شما تیک خورده‌اند:

  • تعریف فیلدهای حیاتی: آیا فیلد title ، company و category را دقیقاً مطابق کدها تعریف کرده‌اید؟ (فراموش نکنید که max_length برای فیلدهای متنی اجباری است).
  • ثبت جاده برگشت (Related Name): آیا در فیلد ForeignKey عبارت related_name='jobs' را اضافه کردید؟ (این کار باعث می‌شود در آینده گزارش‌گیری از دسته‌بندی‌ها مثل آب خوردن ساده شود).
  • خوانایی با متد __str__: آیا این متد را به کلاس Job و Category اضافه کرده‌اید؟ (اگر نه، در پنل ادمین فقط لیست‌های بی‌روحی مثل Job object را خواهید دید).
  • نظم در کلاس Meta: آیا ordering را تنظیم کردید تا آگهی‌های جدید همیشه در صدر باشند؟ (این یعنی احترام به وقتِ مدیر سایت!).
  • تثبیت دیتابیس (The Big Migrate): و مهم‌تر از همه، آیا بعد از آخرین تغییرات، دستور makemigrations و سپس migrate را اجرا کردید؟ (اگر یادتان رفته باشد، پنل ادمین با دیدن فیلدهای جدید دچار سرگیجه می‌شود و ارور می‌دهد!).

یک تقلب کوچک 

اگر می‌خواهید در پنل ادمین همه‌چیز فارسی و شکیل باشد، مطمئن شوید که برای هر فیلد یک نام نمایشی یا همان verbose_name هم گذاشته‌اید. مثلاً:
title = models.CharField(max_length=100, verbose_name="عنوان شغلی")

گام بعدی ما چیست؟

حالا که خیالتان از بابت دیتابیس راحت شد، وقت آن است که «کلیدِ طلایی» ورود به سایت را بسازیم.

در درس بعدی: پنل مدیریت (Django Admin)، یاد می‌گیریم چطور با دستور createsuperuser یک مدیر کل بسازیم و وارد محیط گرافیکی و جذاب جنگو شویم تا اولین آگهی استخدامی واقعی‌مان را ثبت کنیم.

آماده‌اید برای اولین بار محیط مدیریت سایت JobTrack را از نزدیک ببینید؟