Джанго и модели с несколькими внешними ключами - PullRequest
9 голосов
/ 26 февраля 2012

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

  • Поездка 1
    • Направление 1
    • Пункт назначения 2
    • Задание 1
  • Поездка 2
    • Направление 1
    • Занятие 2

Модель 1025 *
*

  • Поездка <-> TripDestination <-> Пункт назначения (поездка может иметь несколько пунктов назначения)
  • Активность -> Поездка, Активность -> Пункт назначения (активность определяется для поездки в определенном месте / пункте назначения)
    class Destination(models.Model):
        city_name=models.CharField()

    class Trip(models.Model):
        departing_on=models.DateField()
        returning_on=models.DateField()
        destinations=models.ManyToManyField(Destination)

    class Activity(models.Model):
        destination=models.ForeignKey(Destination, null=False)
        trip=models.ForeignKey(Trip, null=False)

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

View

def list_trip(request, template_name = 'trip-list.html'):
    trips = Trip.objects.all()

    # Build a dictionary for activities -- Is this the right thing to do?
    activities = Activity.objects.filter(trip__in=trips)
    activities_by_trips = dict()
    for activity in activities:
        if activity.trip_id not in activities_by_trips:
            activities_by_trips[activity.trip_id] = dict()

        if activity.destination_id not in activities_by_trips[activity.trip_id]:
            activities_by_trips[activity.trip_id][activity.destination_id] = []

        activities_by_trips[activity.trip_id][activity.destination_id].append(activity)

    return render_to_response(template_name, {
        'page_title': 'List of trips',
        'trips': trips,
        'activities_by_trips': activities_by_trips,
    })

Template


{% block content %}
    {% for trip in trips %}
        {{ trip.id }} - {{ trip.name }}

        {% for destination in trip.destinations.all %}
            {{ destination.city_name }}

            ** This is terrible code -- How to fix that **
            {% for key, value in activities_by_trips|dict_lookup:trip.id %}
                {% if value %}
                    {% for key_prime, value_prime in value|dict_lookup:destination.id %}
                       {{ value_prime.description }}
                    {% endfor %}
                {% endif %}
            {% endfor %}
        {% endfor %}
    {% endfor %}
{% endblock %}

Вкратце, кто-нибудь может помочь мне получить сводку обо всех поездках и мероприятиях? Какой лучший способ сделать это? Модель правильная?

Спасибо!

1 Ответ

19 голосов
/ 26 февраля 2012

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

Для каждого внешнего ключа в таблице, Django добавит API-менеджер удобства для наборов объектов на противоположной стороне отношения. Destination будет иметь visit_set, но будет Trip. Аналогично, из-за visit иностранного ключа в Activity каждое посещение будет иметь activity_set.

Первый запуск с моделями:

from django.db import models

# Create your models here.
class Destination(models.Model):
    city_name=models.CharField(max_length=50)

class Trip(models.Model):
    departing_on=models.DateField()
    returning_on=models.DateField()
    destinations=models.ManyToManyField(Destination, through='Visit')

class Visit(models.Model):
    destination=models.ForeignKey(Destination)
    trip=models.ForeignKey(Trip)

class Activity(models.Model):
    name=models.CharField(max_length=50)
    visit=models.ForeignKey(Visit)

Затем давайте немного изменим list_trip, добавил print_trip для ясности того, что происходит в шаблоне:

def list_trip(request, template_name = 'trip-list.html'):
    return render_to_response(template_name, {
        'page_title': 'List of trips',
        'trips': Trip.objects.all(),
        })

def print_trips():
    for trip in Trip.objects.all():
        for visit in trip.visit_set.select_related().all():
            print trip.id, '-', visit.destination.city_name
            for act in visit.activity_set.all():
                print act.name

И, наконец, улучшенный шаблон:

{% block content %}
    {% for trip in trips %}
        {{ trip.id }} - {{ trip.name }}

        {% for visit in trip.visit_set.select_related.all %}
            {{ visit.destination.city_name }}

            {% for act in visit.activity_set.all %}
                 {{ act.name }}
            {% endfor %}
        {% endfor %}
    {% endfor %}
{% endblock %}

Еще есть возможности для улучшения производительности. Обратите внимание, что я использовал select_related. Это будет выполнять предварительную выборку всех адресатов во время выборки посещений, так что для visit.destination.city_name не потребуется повторный вызов БД. Однако это не работает для обратных отношений ManyToMany (в нашем случае все члены activity_set). В Django 1.4 появится новый метод prefetch_related, который также разрешит это.

А пока прочитайте Эффективный обратный поиск , чтобы узнать, как еще больше сократить количество обращений к БД. В комментариях также упоминаются несколько доступных решений.

...