Django Queryset с филтриране по обратен външен ключ

Имам следния модел Django:

class Make:
   name = models.CharField(max_length=200)

class MakeContent:
   make = models.ForeignKey(Make)
   published = models.BooleanField()

Бих искал да знам дали е възможно (без да пиша директно SQL) да генерирам набор от заявки, който съдържа всички Makes и свързаните с всеки MakeContents, където published = True.


person Julian A.    schedule 25.03.2011    source източник
comment
Бихте ли били по-конкретни относно въпроса си?   -  person endre    schedule 25.03.2011


Отговори (5)


Django не поддържа метода select_related() за обратни търсения на външен ключ, така че най-доброто, което можете да направите, без да напускате Python, са две заявки към база данни. Първото е да вземете всички Makes, които съдържат MakeContents където published = True, а второто е да вземете всички MakeContents където published = True. След това трябва да превъртите и да подредите данните, както искате. Ето една добра статия за това как да направите това:

http://blog.roseman.org.uk/2010/01/11/django-patterns-part-2-efficient-reverse-lookups/

person Spike    schedule 25.03.2011
comment
вижте метода prefetch_related(), за да рационализирате двете заявки, които споменавате. - person B Robster; 19.03.2013

Да, мисля, че искаш

make = Make.objects.get(pk=1)
make.make_content_set.filter(published=True)

или може би

make_ids = MakeContent.objects.filter(published=True).values_list('make_id', flat=True)
makes = Make.objects.filter(id__in=make_ids)
person Jason Christa    schedule 25.03.2011
comment
.values трябва да се замени с .values_list, ако не греша. - person exfizik; 08.01.2012
comment
Вашият първи кодов фрагмент не работи. Той получава всички MakeContents за една марка, където е необходимо MakeContents за всички марки. _set работи за един обект, но не и за набор от заявки. - person knite; 07.09.2012
comment
Какъв е смисълът да добавям flat = True? Идентификаторите вече ще бъдат уникални по дефиниция и гарантирането, че са уникални, може да изисква допълнително изчисление. - person pintoch; 27.09.2014
comment
pintoch, flat=True не предлага нищо свързано с уникалността. Той причинява само връщане на единични стойности, а не на кортежи, когато се изисква само едно поле. - person Rob; 16.04.2019
comment
Вярвам, че make.make_content_set трябва да бъде make.makecontent_set в по-новите версии на Django (използвам 2.2). - person Sam Creamer; 23.04.2019

Знам, че това е много стар въпрос, но отговарям. Тъй като мисля, че отговорът ми може да помогне на другите. Смених модела малко по следния начин. Използвал съм Django 1.8.

class Make(models.Model):
    name = models.CharField(max_length=200)

class MakeContent(models.Model):
    make = models.ForeignKey(Make, related_name='makecontent')
    published = models.BooleanField()

Използвах следния набор от заявки.

Make.objects.filter(makecontent__published=True)

Надявам се, че ще помогне.

person user1012513    schedule 03.04.2017
comment
когато има множество MakeContent, сочещи към един и същ Make, това ще дублира записите Make. Нещо като select from make right join makecontent on make.id=makecontent.make_id с множество редове в MakeContent, имащи едно и също make_id - person Shadi; 05.09.2018
comment
Можете да използвате Make.objects.filter(makecontent__published=True).distinct() - person user1012513; 06.09.2018

Позволете ми да преведа формулирания отговор на Спайк в кодове за бъдещите зрители. Моля, обърнете внимание, че всеки „Make“ може да има от нула до няколко „MakeContent“

Ако питащият иска да запита „Make“ с ПОНЕ ЕДИН „MakeContent“, чието публикувано=True, тогава вторият фрагмент на Джейсън Криста отговаря на въпроса.

Фрагментът е еквивалентен на

makes = Make.objects.select_related().filter(makecontent__published=True).distinct()

Но ако питащият иска да направи заявка за „Make“ с ALL „MakeContent“, чието публикувано=True, тогава следвайки „makes“ по-горе,

import operator
make_ids = [m.id for m in makes if 
    reduce(operator.and_, [c.published for c in m.makecontent_set.all()] ) 
]
makes_query = Make.objects.filter(id__in=make_ids)

съдържа желаната заявка.

person SYK    schedule 27.04.2013
comment
Мисля, че само този отговор се придържа към въпроса - person lengxuehx; 14.04.2018
comment
Това е, което искам, но се чудя дали има наказание за производителност за избиране на all then distinct() - person MiDaa; 02.08.2018

Още веднъж, не е ясно какъв е въпросът, но ако е желателно всички свързани MakeContent обекти да са били публикувани, това може да работи:

Make.objects.exclude(MakeContent_set__published=False)

И ако поне един от тях (както беше в други отговори):

Make.objects.filter(MakeContent_set__published=True)
person Ghra    schedule 28.01.2020