Django和具有多个外键的模型

时间:2022-07-05 07:32:17

I am new to Django and I've been impressed so far by its capabilities. I am playing with more complex models and I am have problem to use them properly. Using Django 1.3, I am trying to write a summary page which would present the three models below with the following structure. In other words, a list of trips with their destinations and activities.

我是Django的新手,到目前为止,我的能力给我留下了深刻的印象。我正在玩更复杂的模型,我有问题正确使用它们。使用Django 1.3,我正在尝试编写一个摘要页面,它将使用以下结构显示下面的三个模型。换句话说,列出他们的目的地和活动的旅行。

  • Trip 1
    • Destination 1
    • Destination 2
    • Activity 1
  • 旅程1目的地1目的地2活动1

  • Trip 2
    • Destination 1
    • Activity 2
  • 旅程2目的地1活动2

Models

  • Trip <-> TripDestination <-> Destination (a trip can have multiple destinations)
  • 行程< - > TripDestination < - >目的地(行程可以有多个目的地)

  • Activity -> Trip, Activity -> Destination (an activity is defined for a trip at a specific location/destination)
  • 活动 - >旅行,活动 - >目的地(为特定地点/目的地的旅行定义活动)

    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)

I am trying to write a view which would generate a page with the structure presented above. The main problem I am having right now is to display the activities for a specific trip and destination. As you can see in the code below, I am building a dictionary and I doubt it is the right thing to do. In addition, the view becomes

我正在尝试编写一个视图,该视图将生成具有上述结构的页面。我现在面临的主要问题是显示特定旅行和目的地的活动。正如您在下面的代码中看到的,我正在构建一个字典,我怀疑这是正确的做法。此外,视图变为

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 %}

In brief, can someone please help me to get a summary of all the trips and activities? What's the best way to accomplish that? Is the model correct?

简而言之,有人可以帮助我得到所有旅行和活动的摘要吗?实现这一目标的最佳方法是什么?模型是否正确?

Thanks!

1 个解决方案

#1


15  

There is plenty of room for improvement. By using through on ManyToManyField you can explicitly define the join table, which we can conveniently consider as a single visit to a city during a particular trip. During that visit we had activities, so activity should have a foreignkey to a visit.

还有很大的改进空间。通过在ManyToManyField上使用through,您可以明确定义连接表,我们可以方便地将其视为在特定旅行期间对城市的单次访问。在那次访问期间,我们有活动,所以活动应该有一个外国的访问。

For each foreignkey in a table, Django will add API convenience manager for sets of objects on the opposite side of the relationship. Destination will have a visit_set, but so will Trip. Similarly, because of visit foreignkey in Activity each visit will have an activity_set.

对于表中的每个外键,Django将为关系的另一侧的对象集添加API便利管理器。目的地将有一个visit_set,但Trip也是如此。同样,由于访问Activity中的foreignkey,每次访问都会有一个activity_set。

First start with the models:

首先从模型开始:

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)

Then lets change list_trip a bit, added print_trip for clarity of what is going on in template:

然后让我们稍微更改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

And finally the improved template:

最后是改进的模板:

{% 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 %}

There is still some more room for improvement performance wise. Notice I used select_related. That will prefetch all destinations at the time visits are fetched, so that visit.destination.city_name will not incur another db call. However this doesn't work for reverse ManyToMany relationships (in our case all members of activity_set). Django 1.4 will come out with new method called prefetch_related which will resolve that as well.

还有一些改进的改进空间。注意我使用了select_related。这将在获取访问时预取所有目标,因此visit.destination.city_name不会引发另一个db调用。但是,这对于反向ManyToMany关系(在我们的例子中是activity_set的所有成员)都不起作用。 Django 1.4将推出名为prefetch_related的新方法,它也可以解决这个问题。

In the mean time, read up on Efficient reverse lookups for an idea how to even further reduce the number of DB hits. In the comments few readily available solutions are mentioned as well.

与此同时,请阅读有效的反向查找,了解如何进一步减少数据库命中数。在评论中,也提到了几个现成的解决方案。

#1


15  

There is plenty of room for improvement. By using through on ManyToManyField you can explicitly define the join table, which we can conveniently consider as a single visit to a city during a particular trip. During that visit we had activities, so activity should have a foreignkey to a visit.

还有很大的改进空间。通过在ManyToManyField上使用through,您可以明确定义连接表,我们可以方便地将其视为在特定旅行期间对城市的单次访问。在那次访问期间,我们有活动,所以活动应该有一个外国的访问。

For each foreignkey in a table, Django will add API convenience manager for sets of objects on the opposite side of the relationship. Destination will have a visit_set, but so will Trip. Similarly, because of visit foreignkey in Activity each visit will have an activity_set.

对于表中的每个外键,Django将为关系的另一侧的对象集添加API便利管理器。目的地将有一个visit_set,但Trip也是如此。同样,由于访问Activity中的foreignkey,每次访问都会有一个activity_set。

First start with the models:

首先从模型开始:

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)

Then lets change list_trip a bit, added print_trip for clarity of what is going on in template:

然后让我们稍微更改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

And finally the improved template:

最后是改进的模板:

{% 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 %}

There is still some more room for improvement performance wise. Notice I used select_related. That will prefetch all destinations at the time visits are fetched, so that visit.destination.city_name will not incur another db call. However this doesn't work for reverse ManyToMany relationships (in our case all members of activity_set). Django 1.4 will come out with new method called prefetch_related which will resolve that as well.

还有一些改进的改进空间。注意我使用了select_related。这将在获取访问时预取所有目标,因此visit.destination.city_name不会引发另一个db调用。但是,这对于反向ManyToMany关系(在我们的例子中是activity_set的所有成员)都不起作用。 Django 1.4将推出名为prefetch_related的新方法,它也可以解决这个问题。

In the mean time, read up on Efficient reverse lookups for an idea how to even further reduce the number of DB hits. In the comments few readily available solutions are mentioned as well.

与此同时,请阅读有效的反向查找,了解如何进一步减少数据库命中数。在评论中,也提到了几个现成的解决方案。