django: есть ли черная магия на дополнительных полях в отношениях «многие ко многим»? - PullRequest
1 голос
/ 19 августа 2011

Это из официального документа Джанго: https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships

Модели похожи на:

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __unicode__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __unicode__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person)
    group = models.ForeignKey(Group)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

В конце приводим такой пример:

# 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))
[<Person: Ringo Starr]

Мой вопрос заключается в том, как модель Person узнает об атрибутах группы и членства, поскольку они не определены в ней. Это просто магия многоборья со сквозной или какая-то универсальная магия в Джанго?

Если бы я выполнил тот же запрос, я бы подумал, что следующий код более естественен (с точки зрения orm Джанго, а не с бизнес-точки зрения):

Membership.objects.filter(group__name='The Beatles', date_joined__gt=date(1961,1,1))).select_related('person')

Редактировать Я прочитал документ еще раз и нахожу такое задом наперед универсальным. В пункте https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships, упоминается:

Это работает и в обратном направлении. Чтобы ссылаться на «обратные» отношения, просто используйте строчное название модели.

Я никогда не использовал этот обратный запрос раньше. Поэтому, когда я впервые увидел это, он ударил меня в мозг. Извините, что эта тема оказывается глупой. Но я надеюсь, что это поможет людям, которые пропускают очень (тонкую) строку в этом абзаце.

Ответы [ 2 ]

2 голосов
/ 19 августа 2011

Это не магия полей ManyToMany;это универсальный из django ORM с отношениями внешнего ключа (включая ManyToMany).

Например, если у вас есть

class Musician(models.Model):
    name = models.CharField(max_length=128)
    band = models.ForeignKey("Band")

class Band(models.Model):
    name = models.CharField(max_length=128)
    genre = models.CharField(max_length=50)

Вы можете сделать что-то вроде Musician.objects.filter(band__name='The Beatles') Запрос можно увидетьс django-debug-toolbar или из оболочки django с:

from django.db import connection
connection.queries

и будет составлен из сложного оператора JOIN.SQL-запрос, созданный ORM, будет выглядеть примерно так:

SELECT "appname_musician"."id", "appname_musician"."name", "appname_musician"."band_id" 
    FROM "appname_musician" 
    INNER JOIN "appname_band" ON ("appname_musician"."band_id" = "appname_band"."id") 
    WHERE "appname_band"."name" = "The Beatles";

РЕДАКТИРОВАТЬ: Если вы сделали Band.objects.filter(musician__name = 'Ringo Starr'), ORM переведет его в:

SELECT "appname_band"."id", "appname_band"."name", "appname_band"."genre"
    FROM "appname_band" 
    INNER JOIN "appname_musician" ON ("appname_musician"."band_id" = "appname_band"."id") 
    WHERE "appname_musician"."name" = "Ringo Starr";

ORMзнает отношения и может преобразовать ваш запрос ORM в соответствующий SQL.Неважно, что у группы нет музыкального объекта;вы смогли дать django четкую просьбу: я хочу, чтобы все объекты группы были связаны с музыкантом с именем «Ringo Starr».

Я думаю, что вы, возможно, зациклены на мысли об инкапсуляции объектов (Например, в группе нет музыканта, как вы можете фильтровать на его основе?Это не черная магия, поскольку запрос фильтра ясен и недвусмысленен - ​​и принимает в качестве аргументов не члены объекта Band;но команды для фильтрации.Например, вы можете сделать Band.objects(name__icontains = "The"), даже если в Band нет объекта name__icontains.ORM преобразует ваш запрос в SQL, который легко выполнить быстро.


EDIT2: Ваш последний пример:

class Musician(models.Model):
    name = models.CharField(max_length=128)
    initial_band = models.ForeignKey("Band", related_name = 'initial_band_members')
    final_band = models.ForeignKey("Band", related_name = 'final_band_members')

class Band(models.Model):
    name = models.CharField(max_length=128)
    genre = models.CharField(max_length=50)

try: ## create some objects
    cream,_ = Band.objects.get_or_create(name="Cream", genre="Classic Rock")
    derek, _ = Band.objects.get_or_create(name="Derek and the Dominos", genre="Classic Rock")
    beatles, _ = Band.objects.get_or_create(name="The Beatles", genre="Classic Rock")
    wings, _ = Band.objects.get_or_create(name="Wings", genre="Classic Rock")
    Musician.objects.get_or_create(name="Eric Clapton", initial_band=cream, final_band=derek)
    Musician.objects.get_or_create(name="Paul McCartney", initial_band=beatles, final_band=wings)
    Musician.objects.get_or_create(name="John Lennon", initial_band=beatles, final_band=beatles)

except:
    pass

Тогда запросы типа Band.objects.filter(musician__name='Paul McCartney') не работают (FieldError), но запросы типа Band.objects.filter(initial_band_members__name='Paul McCartney') будут возвращать найденные Битлз со следующим SQL (используя sqlite в качестве базы данных; команда зависит от серверной части БД):

SELECT "testing_band"."id", "testing_band"."name", "testing_band"."genre" 
  FROM "testing_band" 
  INNER JOIN "testing_musician" ON ("testing_band"."id" = "testing_musician"."final_band_id") 
  WHERE "testing_musician"."name" = Paul McCartney '
1 голос
/ 19 августа 2011

Возможно, посмотрите на https://docs.djangoproject.com/en/dev/topics/db/queries/ для вашего ответа.В общем случае используются соединения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...