Django and models with multiple foreign keys

I am new to Django and so far I have been impressed with its capabilities. I play with more complex models, and I have a problem to use them correctly. Using Django 1.3, I am trying to write a summary page that presents the three models below with the following structure. In other words, a travel list with destinations and activities.

  • Trip 1
    • Destination 1
    • Destination 2
    • Action 1
  • Trip 2
    • Destination 1
    • Action 2

Models

  • Trip ↔ TripDestination ↔ Destination (a trip may have multiple destinations)
  • Activity → Trip, activity → Destination (the action is defined for a trip to a specific place / 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 that would create a page with the structure presented above. The main problem that I am facing now is to show the actions for a specific trip and destination. As you can see in the code below, I am creating a dictionary, and I doubt it is correct. 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 short, can someone please help me get a summary of all trips and events? What is the best way to do this? Is the model correct?

Thanks!

+4
source share
1 answer

There are many opportunities for improvement. Using ManyToManyField, you can explicitly define a connection table, which is convenient to consider as one visit to the city during a particular trip. During this visit, we had events, so activities should have a visit in the opposite direction.

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

Start with the models first:

 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 modify list_trip bit, add print_trip to clarify what happens in the template:

 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, an 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 are few more options for improving performance. Notice I used select_related. This will prefetch all destinations during visits, so visit.destination.city_name will not make another db call. However, this does not work for ManyToMany inverse relationships (in our case, all activity_set members). Django 1.4 will come out with a new method called prefetch_related, which will also solve this.

At the same time, read Effective Reverse Lookup to understand how to further reduce the number of database calls. The comments also mention several available solutions.

+13
source

Source: https://habr.com/ru/post/1398433/


All Articles