Робитаҳои байни моделҳо
Чаро ҷадвалҳоро мепайвандем
Курси таълимӣ якчанд иштирокчии сабтиномшуда дорад. Агар номи курсро ба ҳар сабтином нусхабардорӣ кунем, барои иваз кардани ном бисёр сатрҳоро дигар кардан лозим мешавад ва хатои навишт як курсро ба чанд ном ҷудо мекунад. Ба ҷойи нусхаҳо ишора ба як сабти курсро нигоҳ медоранд.
Робитаи бисёр ба як маъно дорад: ҳар сабтином як курс ва ҳар курс сабтиномҳои зиёд дорад. ForeignKey ин робитаро тавсиф мекунад. Дар ҷадвали сабтином адади course_id нигоҳ дошта мешавад; дар Python ҳам бо адад ва ҳам бо объекти course кор кардан мумкин аст.
Омода кардани мисол
Мисолҳои зеринро пайдарпай дар файли нав иҷро кунед. Барои робитаҳои баръакс барнома бояд дар феҳристи Django бошад. Дар ин ҷо модули хурди learn-ро бе лоиҳаи ҷудо сабт мекунем. Дар лоиҳаи одӣ барнома ҷузвдонест дар INSTALLED_APPS ва ҷадвалҳоро migration месозад.
related_name="enrollments" ба курс менеҷери робитаи баръакс медиҳад. on_delete=models.CASCADE маъно дорад: ҳангоми нест кардани курс тавассути ORM сабтиномҳои он ҳам нест шаванд. PROTECT ҳангоми мавҷуд будани вобастагиҳо несткуниро манъ мекунад; SET_NULL сабтиномҳоро нигоҳ медорад, вале null=True мехоҳад. Интихоб ба маънои маълумот вобаста аст.
import sys, types, tempfile
import django
from django.conf import settings
sys.modules["learn"] = types.ModuleType("learn")
sys.modules["learn"].__path__ = [tempfile.gettempdir()]
settings.configure(
INSTALLED_APPS=["learn"], USE_I18N=False,
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
)
django.setup()
from django.db import models, connection
class Course(models.Model):
title = models.CharField(max_length=80)
class Meta:
app_label = "learn"
class Enrollment(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name="enrollments")
student = models.CharField(max_length=40)
class Meta:
app_label = "learn"
with connection.schema_editor() as editor:
editor.create_model(Course)
editor.create_model(Enrollment)
a = Course.objects.create(title="Python")
b = Course.objects.create(title="SQL")
e = Enrollment.objects.create(course=a, student="Ada")
Enrollment.objects.create(course_id=a.pk, student="Lin")
Enrollment.objects.create(course=b, student="Ada")
print(e.course_id, e.course.title)
Натиҷа:
1 Python
Самти мустақим ва баръакс
e.course як объектро бармегардонад. a.enrollments менеҷери маҷмӯи сабтиномҳост, на як сабтином. Он all, filter, count ва order_by дорад. Ҳангоми сохтан тавассути ин менеҷер Django курсро худаш мегузорад.
course_id ва course-ро омехта накунед: ба якум калид, ба дуюм намунаи Course медиҳанд. Сабти калиди мавҷуднабуда метавонад маҳдудияти калиди хориҷиро вайрон кунад.
a.enrollments.create(student="Sam")
print(list(a.enrollments.order_by("id").values_list("student", flat=True)))
print(b.enrollments.count())
Натиҷа:
['Ada', 'Lin', 'Sam']
1
Ҷустуҷӯ аз рӯйи робита ва сатрҳои такрорӣ
Ду зерхат ба ҷадвали пайвастшуда мегузарад: course__title номи курси сабтином аст. Дар самти баръакс enrollments__student курсҳоро аз рӯйи иштирокчӣ меёбад. ORM JOIN, яъне пайвасти ҷадвалҳо аз рӯйи калидро месозад.
Як курс метавонад якчанд бор мувофиқ ояд, агар чанд сабтином ба шарт ҷавоб диҳад. distinct() ин такрорҳоро мебардорад. Ин аз ҳисобкунӣ фарқ мекунад: бардоштани сатрҳои такрорӣ ва ҳисоб кардани шумора амалҳои ҷудоанд.
print(list(Enrollment.objects.filter(course__title="Python").order_by("id").values_list("student", flat=True)))
print(list(Course.objects.filter(enrollments__student__in=["Ada", "Lin"]).order_by("id").values_list("title", flat=True)))
print(list(Course.objects.filter(enrollments__student__in=["Ada", "Lin"]).distinct().order_by("id").values_list("title", flat=True)))
Натиҷа:
['Ada', 'Lin', 'Sam']
['Python', 'Python', 'SQL']
['Python', 'SQL']
Ҳисобкунӣ ва боркунии объектҳои пайвастшуда
annotate ба ҳар объект майдони ҳисобшаванда меафзояд. Count("enrollments") сатрҳои пайвастшударо мешуморад; барои курси бе иштирокчӣ натиҷа сифр аст. filter ва order_by-и баъдӣ аннотатсияро истифода бурда метавонанд. Сабти пайвастшударо ҳисоб кунед, на худи курсро: Count("id") дар ин ҷо маънои дигар дорад.
select_related("course") курсро якҷоя бо сабтином дар як дархост бор мекунад. Он барои ForeignKey ва OneToOneField мувофиқ аст. Барои маҷмӯи самти баръакс одатан prefetch_related("enrollments") истифода мешавад: дархости ҷудо маҷмӯъҳоро бор карда, онҳоро дар Python мепайвандад.
from django.db.models import Count
from django.shortcuts import get_object_or_404
from django.http import Http404
Course.objects.create(title="HTML")
print(list(Course.objects.annotate(size=Count("enrollments")).order_by("id").values_list("title", "size")))
for item in Enrollment.objects.select_related("course").order_by("id")[:2]:
print(item.student, item.course.title)
try:
get_object_or_404(Course, pk=999)
except Http404:
print(404)
Натиҷа:
[('Python', 3), ('SQL', 1), ('HTML', 0)]
Ada Python
Lin Python
404
Тарзи кори дохилӣ
Робита ба рақами билети китобхона монанд аст: сабт рақамро нигоҳ медорад, на нусхаи ҳамаи маълумоти хонандаро. Ҳангоми дастрасӣ ба e.course Django дар ҳолати зарурӣ курсро аз пойгоҳ хонда, объектро дар ҳамин намуна нигоҳ медорад. Ҳангоми паймоиши сабтиномҳои зиёд бе боркунии пешакӣ як дархост барои сабтиномҳо ва боз яктоӣ барои курси ҳар кадом пайдо мешавад: масъалаи N+1.
Ҳангоми филтр аз рӯйи робита SQL ҷадвалҳоро мепайвандад. Ҳисоб бо аннотатсия гурӯҳбандӣ истифода мекунад ва филтр аз рӯйи агрегат метавонад HAVING шавад. get_object_or_404 танҳо набудани объектро ба Http404 табдил медиҳад; хатоҳои дигари пойгоҳро пинҳон намекунад.
Робитаҳои дигар: OneToOneField барои як профил ба ҳар корбар ва ManyToManyField барои тегҳо, ки дар ҳар ду тараф маҷмӯъҳо имконпазиранд. Дар ҳолати охир Django ҷадвали миёнаравро истифода мебарад. Як сутуни tag_id барои ҳамаи тегҳо кофӣ намебуд.
Хатоҳои маъмул ва ислоҳҳо
Enrollment.objects.create(course=1, ...)ба ҷойи объект адад дода,ValueErrorмеорад.course_id=1ёcourse=объектистифода баред.a.enrollments.studentмайдонро аз менеҷер гирифтанӣ шуда,AttributeErrorмедиҳад. Аввал сабтҳоро интихоб кунед:a.enrollments.all().Enrollment.objects.all()дар саҳифаи як курс сабтиномҳои дигаронро нишон медиҳад. Маҷмӯъро боa.enrollmentsёfilter(course=a)маҳдуд кунед.SET_NULLбеnull=Trueхатои санҷиши модел медиҳад.NULL-ро иҷозат диҳед ё сиёсати дигари несткуниро интихоб кунед.- Хондани
item.course.titleдар давра беselect_relatedдархостҳои зиёдатӣ месозад. Ба QuerySet-и паймошаванда боркунии пешакӣ илова кунед.
Ёддошт
| Самт ё вазифа | Навишт |
|---|---|
| Робита | ForeignKey(Course, on_delete=..., related_name="enrollments") |
| Объект / калид | item.course / item.course_id |
| Маҷмӯи баръакс | course.enrollments.all() |
| Филтр аз рӯйи робита | filter(course__title="Python") |
| Бардоштани такрорҳо | distinct() |
| Шумораи сатрҳои пайваст | annotate(size=Count("enrollments")) |
| Боркунии як объект / маҷмӯъ | select_related / prefetch_related |
| Ёфтан ё додани 404 | get_object_or_404(Model, pk=...) |