روابط مدل‌ها


در دنیای پایگاه‌داده‌ها و مدل‌سازی داده، یکی از مهم‌ترین اصول طراحی، جلوگیری از تکرار غیرضروری داده‌ها و ایجاد روابط معنادار بین موجودیت‌ها است — و دقیقاً همین جاست که روابط بین مدل‌ها (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")
    title = models.CharField(max_length=200)
    subject = models.CharField(max_length=500)
    content = models.TextField(null=True, blank=True)
    demo = models.URLField(null=True, blank=True)
    created = models.DateTimeField(auto_now_add=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 به ترتیب اجرا شوند

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


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

برای استفاده از رابطه one-to-one در مدل‌های جنگو باید از گزینه OneToOneField برای فیلد استفاده نمود تا بدین صورت رابطه یک به یک(one-to-one) را تعریف کرد. برای درک بهتر، مدلی را فرض می‌گیریم که نام شرکت­های خودرو سازی را یدک می­کشد و همچنین مدل دیگری که مدیرعامل شرکت­ها را شامل می‌شود. این دو مدل مفهوم فیلد OneToOne را ایجاد خواهند کرد چرا که:

- یک شرکت خودروسازی تنها می‌تواند یک مدیرعامل داشته باشد.


- یک نفر می‌تواند تنها در یک شرکت خودروسازی به عنوان میدعامل مشغول به کار شود.

فیلد company در مدل ceo بصورت OneToOne به مدل company مرتبط شده است. بدین صورت از طریق فیلد company به تمامی اطلاعات مدل company دسترسی خواهد داشت. همچنین در مدل company، از طریق مدل ceo به تمامی فیلدهای آن دسترسی خواهیم داشت.

حال با فرض داشتن داده­های دو مدل به­صورت ذیل، می‌توان جستجوی رکوردها کوئری - را انجام داد


با کوئری از سمت مدل company:


و با کوئری از سمت مدل ceo:

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



در این نوع رابطه چند رکورد از یک مدل می‌تواند با چندین رکورد از مدل دیگر مرتبط باشند. برای مثال یک پروژه در وب‌سایت بر مبنای بستر پیاده‌سازی آن پروژه، می‌تواند چندین برچسب یا تگ از جمله Python، React و ... داشته باشد و همینطور یک برچسب نیز می‌تواند متعلق به چندین پروژه باشد.


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

برای استفاده از رابطه ManyToMany در مدل‌های جنگو باید از فیلد ManyToManyField استفاده کرد و این فیلد رابطه چند به چند را خواهد ساخت، چون خصوصیت تگ در مدل پروژه وجود دارد، ما این فیلد را در مدل projects قرار خواهیم داد.


پس از ایجاد مدل tag، حال می­بایست نسبت به تعریف فیلد ManyToMany در مدل projects اقدام کنیم

نکته: ذکر این نکته حائز اهمیت می­باشد که بعد از ایجاد و یا انجام هر تغییری در ساختار مدل­ها، می­بایست دستورات makemigrations و سپس migrate را اجرا کنیم تا تغییرات در پایگاه داده نیز اعمال شوند؛ و همچنین بعد از ایجاد هر مدل، می­بایست نسبت به ثبت آن در اینترفیس ادمین از مسیر فایل admin.py از داخل دایرکتوری اپ اقدام کرد:


coreapp/admin.py

حال که تمامی مراحل فرایند ایجاد، تغییر و ثبت مدل­ها را طی کردیم، نگاهی به نحوه عملکرد فیلد ManyToMany تعریف شده در مدل projects خواهیم انداخت. قبل از انجام آن می­بایست چند آیتم در جدول tag اضافه کنیم تا درک بهتری از نحوه عملکرد داشته باشیم.

در اینجا لیستی از تگ­ها به جدول tag اضافه کردیم. حال اگر به سراغ آیتم­های مدل projects برویم، فیلد tags ایجاد شده به این صورت خواهد بود.