آموزش جنگو (Basic) : جلسه هجده | روابط یا relationships در دیتابیس

در این جلسه به بررسی روابط دیتابیسی نظیر many-to-many یا one-to-one خواهیم پرداخت . با ما همراه باشید .آموزش جنگو : جلسه هجده | روابط یا relationships در دیتابیسروابط یا relationships مدل ها در جنگوما در اول کتاب مقداری درباره روابط دیتابیسی توضیح داده بودیم . (این روابط در sql وجود دارند و اگر شما با زبان sql اشنا هستید میتوانید این بخش را رد کنید) . مشخصا یکی از قدرت های دیتابیس ها این است که بتوانید فیلد ها را از جدول های مختلف به یکدیگر مرتبط کنید (یعنی یک فیلد بتواند درون خود رکورد های جدول دیگری را ذخیره کند). انواع مختلف آن many_to_many , many_to_oneو one_to_one هستند .ابتدا به سراغ بررسی فیلد های many_to_one یا foriengkey می رویم .مدل ها : Many to oneبرای اینکه یک فیلد foriengkey بسازید ,کافیست مانند هر فیلد دیگری آن را بصورت یک ویژگی در کلاس خود ایجاد کنید . گرچه اولین مقداری که این نوع فیلد ها (فیلد های روابطی) دریافت می کنند , نام مدلی (در واقع همان نام کلاس) است که می خواهید با رکورد های آن رابطه ای ایجاد کنید .فرض کنید مدلی داریم که انواع ماشین ها را درون خود میتواند ذخیره کند . این مدل یک فیلد دارد که در آن باید سازنده را مشخص کنیم و ما نام سازنده ها را درون مدلی دیگر ذخیره کرده ایم . نام مدل اول car و نام مدل دوم Manufacturer است . اگر قرار باشد هر ماشین فقط یک سازنده داشته باشد ولی هر سازنده چند مدل ماشین را بتواند بسازد باید کد شما به شکل زیر باشد :(منظور این است باید در مدل car از foriengkey استفاده کنید)from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...اگر نگاهی به ادمین پنل بیندازید خواهید فهمید که در فیلدی که برای car تعریف کرده اید ,فیلد به صورت یک منو در آمده است . این منو حاوی شی های کلاس Manufacturer است . شما همانطور که گفتیم میتوانید فقط یک عدد از بین آنها را انتخاب کنید ولی شی های دیگر در کلاس car می توانند دوباره آن شی را انتخاب کنند . پس این به این معناست که تمام شی های کلاس Manufacturer در دسترس خواهد بود ولی هر شی در کلاس car فقط و فقط میتواند یکی از آنها را انتخاب کند در حالی که ان گزینه برای بقیه اشیا کلاس car نیز در دسترس خواهد بود .نکته : اگر مثال های بیشتری بخواهیم درباره این نوع از رابطه بزنیم میتوانیم فرض کنیم که شما یک مدل مربوط به ذخیره کردن کامنت ها دارید ,علاوه بر این یک مدل user برای خود تعریف کرده اید که محل ذخیره کاربران است . حال اگر بخواهید هر کامنت فقط مربوط به یک کاربر باشد (آن کاربر کامنت را نوشته باشد) ولی هر کاربر بتواند چندین کامنت بگذارد ,باید از رابطه foriengkey در مدل کامنت ها استفاده کنید .نکته : مقدار on_delete برابر با models.CASCADE قرار داده شده است . در واقع on_delete میگوید که اگر آن شی که در فیلد Foriengkey انتخاب شده است ,حذف شد ,چه بلایی بر سر فیلد ما بیاید ؟ در اینجا ما آن را روی CASCADE قرار داده ایم که یعنی با حذف رکورد انتخابی در فیلد ,رکوردی که دارای فیلد شما است نیز حذف شود . معمولا اکثر فیلد های رابطه ای جنگو روی این حالت قرار می گیرند .مدل ها : Many to Manyبرای تعریف یک فیلد many_to_many کافیست از models.ManytomanyField استفاده کنید . این فیلد نیز مانند دیگر فیلد ها میتواند به عنوان یک ویژگی در کلاس تعریف شود . این فیلد یک argument دریافت میکند که ضرور ی است . مقدار این argument نام کلاسی است که شما می خواهید با آن ارتباط داشته باشید .برای مثال در وبسایت رسمی جنگو مثال زیر آورده شده است :بر فرض که مدل pizza چندین topping داشته باشد که مقادیر آن رکورد های مدل topping است . شما باید در مدل pizza فیلدی ایجاد کنید که از نوع manytomany باشد زیرا هر pizza میتواند چند topping و هر topping می تواند مربوط به چند pizza باشد . کد مثال به شکل زیر است :from django.db import models class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)همانند فیلد های Foriengkey میتوانید در ManytomanyField ها نیز روابط بازگشتی و روابطی با مدل هایی که هنوز ایجاد نشده اند ,ایجاد کنید . (نگران نباشید ! این فقط یک اطلاع بود ! در ادامه به بررسی آنها خواهیم پرداخت)پیشنهاد جنگو این است که نام فیلد های ManytomanyField به شکل جمع باشد ,برای مثال : toppings ,ولی این مورد اختیاری است و فقط یک پیشنهاد از طرف جنگو است .نکته مهم در استفاده از این نوع فیلد این است که تفاوتی نمیکند که شما به کدام مدل ارتباطی ساخته اید ,مهم این است که فقط در همان مدل فیلد ManytomanyField ایجاد شود .در مورد اینکه باید در کدام مدل manytomany ساخته شود ,میتوان گفت که مدلی باید حاوی این فیلد باشد که در ادمین پنل بتوانید از طریق فرم ها آن را تغییر دهید . در مثال بالا نیز میتوان این گونه گفت و باید به نکته دوم نیز توجه داشته باشیم :هر پیتزا (pizza) شامل چند نوع چاشنی (topping) است . اینکه بخواهیم بگوییم هر چاشنی شامل چند نوع پیتزا است اصلا منطقی نیست ! پس باید فیلد های روابطی ما در مدل اصلی ساخته شوند و نه مدل های مربوط به آن (البته استثنا نیز خواهیم داشت) .همچنین این فیلد چند argument نیز قبول می کند تا بتواند کارایی بهتری داشته باشد , همه ی آنها را در ادامه توضیح خواهیم داد .علاوه بر این ها شما میتوانید مثال های بیشتری نیز در آدرس زیر ببینید .https://docs.djangoproject.com/en/4.1/topics/db/examples/many_to_many/مدل ها : مدل واسطهگاهی نیاز است که هنگامی که دو مدل را به یکدیگر ارتباط می دهیم ,اطلاعاتی را از ارتباط میان آنها در دیتابیس ذخیره کنیم . برای مثال فرض کنید ما موسیقی دان هایی را درون دیتابیس ثبت کرده ایم . همچنین گروه های نوازندگی را نیز درون دیتابیس داریم که هر کدام شامل چند موسیقی دان هستند . (رابطه manytomany) ولی ما می خواهیم اطلاعاتی را مانند تاریخ افزوده شدن موسیقی دان به گروه نوازندگی را درون رابطه manytomany خود ثبت کنیم . در این موارد از مدلی استفاده میکنیم تا میان مدل های ما واسط شود و اطلاعات اضافی را درون آن نگه می داریم .در این مورد ها باید در فیلد manytomanyfield ایجاد شده از ارگومان through استفاده کنیم و به مدلی که می خواهیم آن را واسط قرار دهیم اشاره کنیم . به کد زیر که برای مثال بالا نوشته ایم نگاه کنید :from django.db import models class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self) return self.name class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)به یاد داشته باشید هنگامی که از مدل های واسطه استفاده میکنید باید روابط foriengkey را میان دو مدلی که آنها را به یکدیگر متصل کردید ,برقرار کنید . این به شما نحوه عملکرد دقیق هر رکورد در مدل Membership را نشان خواهد داد .حال که در مدل مرجع Manytomanyfield را تعریف کرده اید و مدل واسطه خود را تعریف کرده اید , میتوانید به استفاده از آن بپردازید .ابتدا شی هایی (رکورد هایی) را در هر مدل ایجاد میکنیم و سپس از مدل واسطه یا میانی خود استفاده میکنیم :$ python manage.py shellو سپس کد های زیر را تست می کنیم :>>> ringo = Person.objects.create(name=&quotRingo Star&quot) >>> paul = Person.objects.create(name=&quotPaul McCartney&quot) >>> beatles = Group.objects.create(name=&quotThe Beatles&quot) >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), ... invite_reason=&quotNeeded a new drummer.&quot) >>> m1.save() >>> beatles.members.all() <QuerySet [<Person: Ringo Star>]> >>> ringo.group_set.all() <QuerySet [<Group: The Beatles>]> >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), ... invite_reason=&quotWanted to form a band.&quot) >>> beatles.members.all() <QuerySet [<Person: Ringo Star>, <Person: Paul McCartney>]>اکنون خواهید دید بدون نیاز به استفاده از مدل واسطه و تنها با رجوع کردن به فیلد members در مدل Group میتوان به نفراتی که آنها را در مدل واسطه ایجاد کردیم (person) دسترسی پیدا کرد .همچنین میتوانید با استفاده از تابع های ()set() , add() , create برای فیلد members مقادیری را تعیین کنید ولی باید مقدار through_defualt را برای آن تعریف کنید . به مثال زیر نگاه کنید :>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)}) >>> beatles.members.create(name=&quotGeorge Harrison&quot, through_defaults={'date_joined': date(1960, 8, 1)}) >>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})این نیز ممکن است که بخواهید نمونه یا شی هایی را از مدل واسطه (Membership) مستقیما ایجاد کنید که به آن پرداختیم . (مثال اول)همچنین شما میتوانید از تابع و متود ()remove برای حذف یک شی درون فیلد manytomany استفاده کنید .>>> Membership.objects.create(person=ringo, group=beatles, ... date_joined=date(1968, 9, 4), ... invite_reason=&quotYou've been gone for a month and we miss you.&quot) >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]> >>> # This deletes both of the intermediate model instances for Ringo Starr >>> beatles.members.remove(ringo) >>> beatles.members.all() <QuerySet [<Person: Paul McCartney>]>شما میتوانید از متود ()clear نیز برای پاک کردن تمامی مقدار های درون فیلد Manytomany استفاده کنید . (members) . به مثال زیر نگاه کنید :>>> # Beatles have broken up >>> beatles.members.clear() >>> # Note that this deletes the intermediate model instances >>> Membership.objects.all() <QuerySet []>بسیار خب ,اکنون شما بیشتر متود ها و توابعی که برای manytomanyfield استفاده می شود را خوانده اید . حال بیایید کمی query و جستجو در این نوع از فیلد ها را بررسی کنیم .شما میتوانید از این نوع فیلد برای پایه قرار دادن مقداری که جستجو می شود استفاده کنید . مانند هر query دیگر :# Find all the groups with a member whose name starts with 'Paul' >>> Group.objects.filter(members__name__startswith='Paul') <QuerySet[<Group: The Beatles>]>در مثال بالا تمامی رکورد هایی که در جدول و مدل Group قرار دارند و مقادیری که درون فیلد members آنها قرار دارد (در ادامه از members به name اشاره کردیم که منظور در واقع name اشیایی است که در مدل person قرار دارند زیرا فیلد members در اصل به مدل person ربط داده شده است ) بررسی می شوند و جنگو چک میکند که کدامین یک از آنها با Paul شروع میشود . در ادامه به ما شی ای که دارای این ویژگی ها است را نشان داده است .همچنین در مدل های واسطه نیز این قانون برقرار است .# Find all the members of the Beatles that joined after 1 Jan 1961 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) <QuerySet[<Person: Ringo Starr]>در مثال بالا ما تمامی member هایی که تاریخ عضویت (date_joined) آنها بزرگتر از 1 Jan1961 بود را پیدا کرده ایم و نمایش دادیم . (مقدار بازگشتی به صورت یک queryset خواهد بود)علاوه بر این ممکن است نیاز داشته باشید تا به اطلاعاتی که درون مدل میانی ذخیره کرده اید دسترسی پیدا کنید. برای این کار فقط کافی است تا مدل میانی را با استفاده از مقدار هایی که به دو مدل دیگر مربوط هستند پیدا کنید و سپس به مقدار های فیلد های آنها دسترسی پیدا کنید . به مثال زیر نگاه کنید :>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'مدل ها : One to Oneشما میتوانید از روابط onetoone به وسیله ی OneToOneField استفاده کنید . مانند فیلد های دیگر ,این فیلد نیز به صورت یک ویژگی برای کلاس مدل تعریف می شود .شما از این فیلد میتوانید برای ایجاد primary_key استفاده کنید . (در مبحث زبان sql , primary_key به فیلدی گفته میشود که برای هر رکورد خاص است و دو رکورد مختلف نمیتوانند یک مقدار را در آن فیلد داشته باشند .) البته این به این معنا نیست که هر فیلد OneToOneField دارای این ویژگی است . بلکه به این معناست که میتوانید همچین کاری را انجام دهید . (کاری که جنگو به صورت اتوماتیک برای فیلد id انجام می دهد)این نوع از فیلد های آرگومانی اجباری دریافت میکنند که نام کلاس مرتبط خواهد بود . (نام کلاسی که با آن رابطه برقرار می شود)from django.db import models class Person(models.Model): name = models.CharField(max_length=128)بیایید مثالی برای استفاده از این فیلد ها بزنیم تا بهتر متوجه شوید . بر فرض مثال شما مدلی حاوی مکان ها دارید . این مدل به صورت طبیعی باید دارای فیلد هایی مانند ادرس ,شماره تلفن ,کد پستی و غیره باشد . حال در کنار این مدل ,مدل دیگری حاوی رستوران های شهر وجود دارد . اینکه بخواهیم دوباره فیلد هایی مانند آدرس ,کد پستی , تلفن را در این مدل تعریف کنیم ,اضافه کاری به نظر می رسد . بجای اینکار شما از یک رابطه onetoone استفاده میکنید تا هر رستوران بتواند یک مکان را در یکی از فیلد های خود داشته باشد . (این یعنی دو رستوران نمیتوانند یک مکان را داشته باشند ) پس هر رستوران شامل یک مکان و هر مکان شامل یک رستوران میتواند باشد .برای مثال دوم و کاربردی تر میتوانیم به صفحه پروفایل اشاره کنیم . هر پروفایل به یک کاربر متعلق است و هر کاربر فقط یک پروفایل دارد . در اینجا باید از رابطه onetoone برای ایجاد آنها استفاده کنیم .البته در نسخه های قبلی جنگو هر بار که یک رابطه onetoone در یک مدل تعریف می شد ,فیلد حاوی رابطه به صورت اتوماتیک به عنوان primary_key استفاده می شد . این قابلیت در نسخه 4.1 جنگو وجود ندارد و شما می توانید چندین رابطه onetoone برای یک مدل ایجاد کنید بدون اینکه به مشکلی برخورد کنید .در این جلسه به بررسی روابط دیتابیسی در جنگو پرداختیم . در جلسه بعدی به بررسی Meta options و چند نکته درباره مدل ها در جنگو خواهیم پرداخت .