Traverse multiple foreign keys in Django DetailView

ぃ、小莉子 提交于 2020-07-10 08:43:05

问题


I'm working on a project to record all scores shot at a bunch of archery events. They are shot by many people at many events and many different archery rounds. (A "round" in archery is a particular type of competition. For the sake of simplicity let's say there are two: indoor and outdoor.)

Here's a basic ER diagram of the relevant part of my database model:

┌───────────┐         ┌───────────┐       ┌────────────────┐        ┌───────────┐
│           │        ╱│           │╲      │                │╲       │           │
│  Person   │──────┼──│   Score   │──┼────│   EventRound   │──┼─────│   Event   │
│           │        ╲│           │╱      │                │╱       │           │
└───────────┘         └───────────┘       └────────────────┘        └───────────┘
                                                  ╲│╱                            
                                                   ┼                             
                                                   │                             
                                             ┌───────────┐                       
                                             │           │                       
                                             │   Round   │                       
                                             │           │                       
                                             └───────────┘                       

You can see that there are two ManyToMany relationships at work here resolved with two junction tables (EventRound and Score). I'm creating these junction tables manually by specifying the "through" table and "through_fields" in models.py.

I've created a PersonDetailView that allows me to access and iterate through all of the scores in the Score table for a specific person. (Thanks to Jaberwocky and his solution at Detailview Object Relations)

# views.py
class PersonDetailView(DetailView):
    model = Person
    queryset = Person.objects.all()
    template_name = 'person_detail.html'

    def get_context_data(self, **kwargs):
        context = super(PersonDetailView, self).get_context_data(**kwargs)
        context['scores'] = Score.objects.filter(person=self.get_object()).order_by('-event_round__date')
        return context

# person_detail.html
{% block content %}
<h1>Results for {{ person }}</h1>

<table>
<tr><th>Division</th><th>Score</th><th>Date</th><th>Event</th><th>Round</th></tr>
{% for score in scores %}
    <tr>
        <td>{{ score.division }}</td>
        <td>{{ score.pretty_score }}</td>
        <td>{{ score.event_round.date|date:"M d, Y" }}</td>
        <td>{{ score.event_round }}</td>
        <td>{{ score.event_round.round }}</td>
    </tr>
{% endfor %}
</table>

{% endblock content %}

The trouble comes when I try the same strategy with the Events and Rounds. I'd like to show all the scores associated with a particular event or round and include details about the person who shot the score.

I can't figure out how to reach through the EventRound table and get to the scores stored in Score. Presumably, I need to further manipulate the context in the get_context_data method of the PersonDetailView.

Any ideas about how to do this?

Update: Here's a portion of my models.py that includes the tables referenced in this post.

from django.db import models
from datetime import date
from django.urls import reverse
from django.utils import timezone


class Person(models.Model):
    """
    Contains information about competitors who have scores in the database.
    """
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=75)
    birthdate = models.DateField(blank=True, null=True)
    slug = models.SlugField(null=False, unique=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name_plural = "People"
        ordering = ['last_name', 'first_name']

    def __str__(self):
        return "%s %s" % (self.first_name, self.last_name)

    def get_absolute_url(self):
        return reverse('person_detail', kwargs={'slug', self.slug})


class Event(models.Model):
    name = models.CharField(max_length=100)
    start_date = models.DateField(null=True)
    end_date = models.DateField(null=True)
    location = models.ForeignKey("Location", on_delete=models.CASCADE)
    slug = models.SlugField(null=False, unique=True)
    scoring_method = models.ForeignKey("ScoringMethod", on_delete=models.CASCADE)
    event_type = models.ForeignKey("EventType", blank=True, on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return f"{self.name}"

    def get_absolute_url(self):
        return reverse('event_detail', kwargs={'slug', self.slug})

    @property
    def date_range(self):
        if self.start_date is None:
            return "Unknown"
        elif self.start_date == self.end_date:
            return f"{self.start_date.strftime('%b %d, %Y')}"
        else:
            return f"{self.start_date.strftime('%b %d, %Y')} – {self.end_date.strftime('%b %d, %Y')}"


IN_OR_OUT_CHOICES = [
    ("Indoor", "Indoor"),
    ("Outdoor", "Outdoor"),
]


class Round(models.Model):
    name = models.CharField(max_length=75)
    description = models.TextField()
    slug = models.SlugField(null=False, unique=True)
    organization = models.ForeignKey("Organization", on_delete=models.CASCADE)
    is_retired = models.BooleanField("Retired", default=False)
    in_or_out = models.TextField(
        "Indoor/Outdoor",
        max_length=30,
        choices=IN_OR_OUT_CHOICES,
    )
    events = models.ManyToManyField(
        Event,
        through="EventRound",
        through_fields=('round', 'event'),
        related_name="rounds",
    )
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['organization', 'name']

    def __str__(self):
        return "%s" % (self.name)

    def get_absolute_url(self):
        return reverse('round_detail', kwargs={'slug', self.slug})


class EventRound(models.Model):
    date = models.DateField(null=True)
    event = models.ForeignKey("Event",
                              on_delete=models.CASCADE,
                              related_name="event_rounds",
                              )
    round = models.ForeignKey(
        "Round",
        on_delete=models.CASCADE,
        related_name="event_rounds",
    )
    participants = models.ManyToManyField(
        Person,
        through="Score",
        through_fields=('event_round', 'person'),
        related_name="event_rounds",
    )
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name_plural = "Event-Rounds"
        ordering = ['-event']

    def __str__(self):
        return "%s" % (self.event)


DISTANCE_UNIT_CHOICES = [
    ("yd", "Yards"),
    ("m", "Meters"),
]


class Score(models.Model):
    person = models.ForeignKey(
        "Person",
        on_delete=models.CASCADE,
        related_name="scores",
    )
    event_round = models.ForeignKey(
        "EventRound",
        on_delete=models.CASCADE,
        related_name="scores",
    )
    score = models.PositiveSmallIntegerField()
    x_count = models.PositiveSmallIntegerField(blank=True, null=True)
    age_division = models.ForeignKey("AgeDivision", on_delete=models.CASCADE)
    equipment_class = models.ForeignKey("EquipmentClass", on_delete=models.CASCADE)
    gender = models.ForeignKey("Gender", on_delete=models.CASCADE)
    distance = models.CharField(max_length=10, blank=True)
    distance_unit = models.CharField(max_length=10, choices=DISTANCE_UNIT_CHOICES)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return "%s - %s" % (self.person, self.event_round)


回答1:


Filtering using fields of a related model

Since you are simply showing scores, it is best to start working from the Score model

# Get scores by Event for all rounds and dates
Score.objects.filter(event_name__event=event)

# Get scores by Event-Round for all dates
Score.objects.filter(event_name__event=event, event_name__round=round)

# Get scores from one Event-Round in a specific date
Score.objects.filter(event_name__event=event, event_name__round=round, event_name__date=date)

Applying to your use case

Scores by Person:

# view.py
class PersonDetailView(DetailView):
    model = Person      
    queryset = Person.objects.all()
    template_name = 'person_detail.html' 
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        person = self.get_object()
        context['person'] = person
        context['scores'] = Score.objects.filter(person=person)
        return context

# person_detail.html
{% block content %}
    <h1>Results for {{ person }}</h1>
    <table>
        <tr><th>Division</th><th>Score</th><th>Date</th><th>Event</th><th>Round</th></tr>
        {% for score in scores %}
            <tr>
                <td>{{ score.division }}</td>
                <td>{{ score.pretty_score }}</td>
                <td>{{ score.event_round.date|date:"M d, Y" }}</td>
                <td>{{ score.event_round.event }}</td>
                <td>{{ score.event_round.round }}</td>
            </tr>
        {% endfor %}
    </table>
{% endblock content %}

Scores by Event:

# view.py
class EventDetailView(DetailView):
    model = Event       
    queryset = Event.objects.all()
    template_name = 'event_detail.html' 
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        event = self.get_object()
        context['event'] = event
        context['scores'] = Score.objects.filter(event_round__event=event)
        return context

# event_detail.html
{% block content %}
    <h1>Results for {{ event }}</h1>
    <table>
        <tr><th>Division</th><th>Score</th><th>Date</th><th>Round</th><th>Person</th></tr>
        {% for score in scores %}
            <tr>
                <td>{{ score.division }}</td>
                <td>{{ score.pretty_score }}</td>
                <td>{{ score.event_round.date|date:"M d, Y" }}</td>
                <td>{{ score.event_round.round }}</td>
                <td>{{ score.person}}</td>
            </tr>
        {% endfor %}
    </table>
{% endblock content %}

Scores by Round:

# view.py
class RoundDetailView(DetailView):
    model = Round   
    queryset = Round.objects.all()
    template_name = 'round_detail.html' 
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        round = self.get_object()
        context['round'] = round
        context['scores'] = Score.objects.filter(event_round__round=round)
        return context
        
# round_detail.html
{% block content %}
    <h1>Results for {{ round }}</h1>
    <table>
        <tr><th>Division</th><th>Score</th><th>Date</th><th>event</th><th>Person</th></tr>
        {% for score in scores %}
            <tr>
                <td>{{ score.division }}</td>
                <td>{{ score.pretty_score }}</td>
                <td>{{ score.event_round.date|date:"M d, Y" }}</td>
                <td>{{ score.event_round.event}}</td>
                <td>{{ score.person}}</td>
            </tr>
        {% endfor %}
    </table>
{% endblock content %}


来源:https://stackoverflow.com/questions/60920941/traverse-multiple-foreign-keys-in-django-detailview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!