Как отобразить картинку в Django DetailView на основе отношения внешнего ключа?

В настоящее время я создаю сайт на Django, на котором будет размещаться информация о музыке и альбомах/песнях. Мои модели настроены так, что каждая песня имеет внешний ключ к связанному с ней альбому, или к объекту базы данных под названием "Singles", если они не являются частью альбома.

Эти отношения между песней и альбомом отлично работали при создании сайта, пока я не добрался до страницы 'Play Song', над которой я сейчас работаю. Каждый сингл имеет связанную с ним иллюстрацию в модели 'Song', в то время как каждая песня в альбоме не имеет "картинки" в базе данных, поскольку "картинка" является частью модели Album, а не модели Song в этих случаях. Я пытаюсь передать данные из модели Song и модели Album в DetailView так, чтобы если воспроизводимая песня из альбома, а не одиночная, он брал "картинку" из модели Album, к которой у него есть внешний ключ, а не из своего собственного внутреннего объекта "картинка". Мой код приведен ниже, он отлично отображает "картинку" для синглов, но не может отобразить объект "картинка" из альбома и вместо этого показывает мое условие {% else %} - картинка не найдена. HTML файл содержит логику, которую я использую для поиска связанной картинки:

{% elif song in album.song_set.all %}
<img src="{{ album.picture.url }}">

Любая помощь будет принята с благодарностью.

models.py

class Album(models.Model):
    title = models.CharField(
        max_length=200,
        validators=[MinLengthValidator(2, "Must be at least two characters.")]
    )
    release_date = models.DateField()
    picture = models.ImageField(upload_to='albums/', blank=True)
    content_type = models.CharField(max_length=256, blank=True, help_text='The MIMEType of the file')
    description = models.TextField(blank=True)

    def __str__(self):
        return self.title

    def __unicode__(self):
        return self.title


class Song(models.Model):
    title = models.CharField(
        max_length=200,
        validators=[MinLengthValidator(2, "Must be at least two characters.")]
    )
    release_date = models.DateField(blank=True)
    length = models.CharField(
        max_length=200)
    featured_artists = models.CharField(
        max_length=200,
        blank=True)
    picture = models.ImageField(upload_to='singles/', blank=True)
    content_type = models.CharField(max_length=256, blank=True, help_text='The MIMEType of the file')
    description = models.TextField(blank=True)
    alb = models.ForeignKey(Album, on_delete=models.CASCADE)
    audio_file = models.FileField(upload_to='music_audio/', blank=True)

    def __str__(self):
        return self.title

views.py

class PlaySongView(DetailView):
    model = Song
    template_name = 'music/play_song.html'

    def get_context_data(self, **kwargs):
        context = super(PlaySongView, self).get_context_data(**kwargs)
        context['album'] = Album.objects.exclude(title="Single")
        return context

urls.py

urlpatterns = [
    path('', views.MusicView.as_view(), name='music'),
    path('<int:pk>/', views.AlbumDetailView.as_view(), name='album_detail'),
    path('<int:pk>/play', views.PlaySongView.as_view(), name='play')
]

play_song.html

{% load static tailwind_tags %}
<!DOCTYPE html>
<head>
<title>LewnyToons - {{ song.title }}</title>
{% tailwind_css %}
</head>
    <body class="antialiased text-slate-400 bg-slate-900"> 
        <div class="w-full sm:w-9/12 lg:w-8/12 px-4 sm:pr-2 lg:pr-4 mx-auto"> 
            <div class="flex flex-col font-bold text-2xl text-center items-center text-white mx-auto pt-2">
                <h1 class="mb-4">Listen to {{ song.title }} now!</h1>
                {% if song.picture %}
                <img src="{{ song.picture.url }}" class="min-h-64 min-w-64 md:h-96 md:w-96"alt="">
                {% elif song in album.song_set.all %}
                <img src="{{ album.picture.url }}" class="min-h-64 min-w-64 md:h-96 md:w-96"alt="">
                {% else %}
                <img src="{% static 'something_wrong.jpg' %}" class="min-h-64 min-w-64 md:h-96 md:w-96"alt="">
                {% endif %}
                </div>
            {% if song.audio_file %}
            <span class="flex justify-center py-10">
                <div>
                <audio controls><source src="{{ song.audio_file.url }}" type="audio/mpeg"></audio>
                </div>
            </span>
            {% else %}
            <p>The file could not be found.</p>
            <a href="{% url 'the_music:music' %}" class="text-white bold_underline">Return to music.</p>
            {% endif %}
        </div>
    </body>
</html>

Проблема заключается в контексте альбома, передаваемом шаблону в PlaySongView.get_context_data().

Вы передаете QuerySet альбомов вместо объекта альбома, с которым связана песня. Поэтому, когда вы пытаетесь выполнить album.song_set.all, вы выполняете это действие на QuerySet вместо объекта Album. Условие не выполняется, поэтому происходит возврат к блоку else.

Вместо:

context['album'] = Album.objects.exclude(title="Single") 

Лучшим способом подойти к этому в get_context_data() было бы:

song = self.get_object()
context['album'] = song.alb

self.get_object() будет искать pk в ваших URL kwargs (в данном случае '<int:pk>/play') и искать песню на основе ее первичного ключа.

Затем в вашем шаблоне, это так же просто, как сделать:

{{ album }}

Здесь нет необходимости делать обратное отношение в шаблоне, потому что у вас есть прямой доступ к альбому из вашего Song DetailView с помощью self.get_object(). Кроме того, таким образом вы не делаете лишних запросов, получая все ваши альбомы и передавая их в качестве контекста в шаблон. Все, что вам нужно, это один альбом.

Общее эмпирическое правило: если вы можете выполнить то, что вам нужно, с помощью запросов в представлении, это обычно более оптимальное решение.

Вернуться на верх