Как мне «группировать по» в django, используя фильтр для внешнего ключа

#django

#django

Вопрос:

Я пытаюсь отфильтровать участников по имени и вернуть участников и дочерние компании. Мои модели:

 class Member( models.Model ):
    first_name = models.CharField( max_length=40, blank=False, null=False )
    last_name = models.CharField( max_length=40, blank=False, null=False )

 class Affiliation( models.Model ):

    member = models.ForeignKey( Member )
    company = models.ForeignKey( Company )
    date_entered = models.DateField( null = False, blank = False )
    date_exited = models.DateField( null = True, blank = True )

 class Company( models.Model ):
    name = models.CharField( max_length = 100 )
  

и затем я хочу выполнить поиск по участнику, найти всех, чье имя соответствует некоторой строке, и показать их всех вместе с их принадлежностью к компании. Итак, для поиска я мог бы сделать:

 def member_search(request):

    member_list = Member.objects.filter(
                Q( first_name__icontains=request.POST['q'].lower() )  |
                Q( last_name__icontains=request.POST['q'].lower() ) )

    return render_to_response('member/member_list.html', {'member_list': member_list } )
  

или

 def member_search2(request):

    affiliation_list = Affiliation.objects.filter(
                Q( member__first_name__icontains=request.POST['q'].lower() )  |
                Q( member__last_name__icontains=request.POST['q'].lower() ) )

    return render_to_response('member/member_list2.html', {'affiliation_list': affiliation_list } )
  

В первом случае шаблон будет выглядеть примерно так:

 <ul>
    {% for member in member_list %}
        <li>
            <a href="/member/{{ member.pk }}">view</a>

            <a href="/member/{{ member.pk }}/update">edit</a>

            {{ member.first_name }} {{ member.last_name }}

            {{ member.affiliation.all ??? }}

        </li>
    {% endfor %}
</ul>
  

но я не вижу, как выполнить обратный возврат через внешний ключ принадлежности. Я подозреваю, что мой фильтр придется изменить, но я не понимаю, как.

и во втором случае это будет что-то вроде:

 <ul>
    {% for affiliation in affiliation_list %}
        <li>
            <a href="/member/{{ affiliation.member.pk }}">view</a>

            <a href="/member/{{ affiliation.member.pk }}/update">edit</a>

            {{ affiliation.member.first_name }} {{ affiliation.member.last_name }}

            {{ affiliation.member.company.name }}

        </li>
    {% endfor %}
  

но затем я получаю несколько строк для участника, если они связаны с несколькими компаниями. Я посмотрел на совокупность, но в данном случае это неясно, поскольку я тоже хочу перечислить компании.

В итоге я хочу:

 member 1 ( company 1 [entry date], company 2 [entry date] )
member 2 ( company 1 [entry date] )
member 3 ( company 2 [entry date] )
  

Это, скорее всего, дубликат другого вопроса, но я не нашел этот вопрос :-). Кажется, что это обычная вещь, которую кто-то хотел бы сделать.

Комментарии:

1. Подумав об этом немного больше, я думаю, что хочу склониться ко второму методу, как мне бы очень хотелось member 1 (company 1 [entry date]) и т.д.

Ответ №1:

Вот что вы можете попробовать:

Для 1-го вы можете получить связи с affiliation_set

 {{ member.affiliation_set.all }}
  

Для 2-го вам нужно получить список значений, которые вы хотите показать. После этого вы можете использовать regroup тег фильтра шаблона для соответствующей группировки dict. Пример кода

 def member_search2(request):

    affiliation_list = Affiliation.objects.filter( #your filter ...)
                      .values('member__first_name', 'member__last_name', 
                              'company__name', `date_entered') 
      ....
  

И в шаблоне

 {% regroup affiliation_list|dictsort:"member__first_name" by 
       member__first_name as aflist %}

<ul>
{% for af in aflist %}
    <li> {{af.grouper }}
    <ul>
     {%for m in af.list %}
        <li> {{m.company__name}} : {{m.date_entered}} </li>
     {%endfor%}
    </ul>
    </li>
{%endfor%}
</ul>
  

Комментарии:

1. кроме того, как бы я отсортировал по last_name, first_name?

2. Позвольте мне перефразировать мой вопрос… Я хотел бы сгруппировать по члену__id, поскольку у людей может быть одно и то же имя, и я хотел бы отсортировать по фамилии, имени. Я пытался {% regroup affiliation_list|dictsort:"member__last_name" by member__id as aflist %} , но идентификатор участника был указан в {{af.grouper }} . Итак, как бы я поместил туда их имя и отсортировал его по member__last_name,member__first_name ?

Ответ №2:

Первый метод намного проще и по-прежнему дает вам желаемый результат — как показывает Рохан, вам просто нужно использовать {{ member.affiliation_set.all }} для каждого участника.

Однако это будет очень дорого, поскольку каждый вызов — это еще один запрос к базе данных, а переход от принадлежности к компании — это еще один вызов базы данных каждый раз. Вы можете сделать это намного дешевле, используя prefetch_related в своем первоначальном вызове базы данных:

 member_list = Member.objects.prefetch_related('affiliation__toppings').filter(...)
  

Еще одна вещь, о которой стоит подумать, хотя это не обязательно поможет в вашем текущем сценарии, заключается в том, что отношения между участником и компанией на самом деле являются отношениями «многие ко многим», с аффилированностью в виде сквозной таблицы. Вы можете добавить дополнительные функции, не изменяя структуру базы данных, объявив это явно:

 class Member( models.Model ):
    ...
    companies = models.ManyToManyField('Company', through='Affiliation')
  

и теперь объекты-члены могут делать member.companies.all() , чтобы напрямую получать все свои компании.

Комментарии:

1. Спасибо за помощь. Приветствуется.

2. Это последнее логическое изменение, верно? (отсюда и комментарий о том, что структура базы данных не меняется?) На самом деле это было бы очень полезно.