Обход нескольких внешних ключей в Django DetailView - PullRequest
0 голосов
/ 30 марта 2020

Я работаю над проектом, чтобы записать все оценки, снятые на кучу событий стрельбы из лука. Они застрелены многими людьми на многих мероприятиях и во многих различных раундах стрельбы из лука. («Раунд» в стрельбе из лука - это особый тип соревнований. Для простоты, скажем, их два: в помещении и на улице.)

Вот базовая c диаграмма ER соответствующей части моего модель базы данных:

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

Вы можете видеть, что здесь работают две связи ManyToMany, решаемые с помощью двух соединительных таблиц (EventRound и Score). Я создаю эти соединительные таблицы вручную, указав таблицу «through» и «through_fields» в models.py.

Я создал PersonDetailView, который позволяет мне получать доступ и проходить через все оценки в Score таблица для конкретного c человека. (Благодаря Jaberwocky и его решению в 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 %}

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

Я не могу понять, как достичь через EventRound таблицы и получите результаты, хранящиеся в Score. Предположительно, мне нужно дополнительно манипулировать context в get_context_data методе PersonDetailView.

Любые идеи о том, как это сделать?

Обновление: Вот часть моего models.py, который включает таблицы, на которые есть ссылки в этом посте.

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 Ответ

1 голос
/ 30 марта 2020

Фильтрация с использованием полей связанной модели

Поскольку вы просто показываете баллы, лучше всего начать работу с Score модели

# 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)

Применение к вашему сценарию использования

Счёт по человеку:

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

Счёт по событию:

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

Счёт по раунду:

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