روابط مدل‌ها


در دنیای پایگاه‌داده‌ها و مدل‌سازی داده، یکی از مهم‌ترین اصول طراحی، جلوگیری از تکرار غیرضروری داده‌ها و ایجاد روابط معنادار بین موجودیت‌ها است — و دقیقاً همین جاست که روابط بین مدل‌ها (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) می‌بایست صورت پذیرد.