问题
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