Как правильно выполнить запрос к внешнему ключу в Django?

#django #foreign-keys #django-queryset

#django #внешние ключи #django-queryset

Вопрос:

У меня есть группа моделей Cluster , Server и Service , которые имеют связь, через ForeignKey, один ко многим, как показано в следующем фрагменте:

 class Server(models.Model):
    cluster = models.ForeignKey(Cluster, null=True, on_delete=models.PROTECT)
    shortname = models.CharField(max_length=20, null=False)
    .
    .
    .


class Service(models.Model):
    host_server = models.ForeignKey(Server, null=False, on_delete=models.PROTECT)
    name = models.CharField(max_length=20, null=False)
    technology = models.CharField(max_length=20, null=False)
    .
    .
    .

  

И теперь я пытаюсь получить набор Servers в запросе вместе с соответствующим Services использованием

 def infra(request, cluster_name):

    cluster = Cluster.objects.get(name__iexact=cluster_name)
    servers = Server.objects.filter(cluster_id=cluster.id).order_by('shortname')
    services = Service.objects.filter(host_server__in=servers).order_by('id')

    template = 'webapp/infra.html'
    context = {
        'cluster':cluster,
        'servers':servers,
        'services':services,
        }
    return render(request, template, context)
  

Однако в представлении я получаю все службы в одном наборе запросов вместо набора запросов для каждого, Server содержащего только Services связанные с ним.

В качестве обходного пути я использовал следующее:

 <h5>Services</h5>
  <ul class="list-group">
    {% for service in services %}
      {% if service.host_server_id == server.id %}
        <li class="list-group-item">{{ service.name }}</li>
      {% endif %}
    {% endfor %}
  </ul>
  

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

Возможно ли улучшить или этот первоначальный подход правильный?

Спасибо!

Ответ №1:

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

ПРИМЕЧАНИЕ: без related_name обратного менеджера было бы доступно из service_set

models.py

 class Service(models.Model):
    host_server = models.ForeignKey(Server, null=False, on_delete=models.PROTECT, related_name="services")
  

views.py

 def infra(request, cluster_name):

    cluster = Cluster.objects.get(name__iexact=cluster_name)
    # we use `prefetch_related` to speedup the query
    servers = Server.objects.filter(cluster_id=cluster.id).prefetch_related('services').order_by('shortname')
    return render(request, 'webapp/infra.html', {
        'cluster':cluster,
        'servers':servers,
    })
  

infra.html

 
  <ul class="list-group">
    {% for server in servers %}
      <li class="list-group-item">{{ server.name }}
        <ul>
        {% for service in server.services.all %}
          <li class="list-item">{{ service.name }}</li>
        {% endfor %}
        </ul>
      </li>
    {% endfor %}
  </ul>
  

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

1. Спасибо! Это определенно то, чего я пытался достичь. Я даже просмотрел ссылку, которую вы упомянули, но пример был не таким понятным, как ваш.