首页 > 解决方案 > 如何从 Django 中的相关表中获取单个记录?

问题描述

我有这个模型(简化版):

class Photo(models.Model):
    image = models.ImageField(null=True, blank=True, upload_to='photos')
    photographer = models.CharField(max_length=255)

class Genre(models.Model):
    title = models.CharField(max_length=255)

class Decade(models.Model):
    title = models.CharField(max_length=255)

class Album(models.Model):
    title = models.CharField(max_length=255)
    genre = models.ForeignKey(Genre, on_delete=models.CASCADE)
    decade = models.ForeignKey(Decade, on_delete=models.CASCADE)
    photo = models.ForeignKey(Photo, on_delete=models.CASCADE, null=True, blank=True)

如您所见,有些相册有照片。我想做的是显示所有类型的列表,其中将显示特定十年中该类型的专辑数量,以及该类型的照片。照片应该是该类型相册中的照片。我无法获取第一张唱片的照片,因为它可能没有照片,所以它应该获取有照片的专辑的照片。

例子:

流派:

1 | Rock
2 | Punk
3 | Jazz

专辑:

1 | Rock All   | genre = rock | decade = sixties
2 | Rock It Up | genre = rock | decade = sixties
3 | Jazz Basiq | genre = jazz | decade = nineties
4 | Jazz Uno   | genre = jazz | decade = sixties
5 | Punkio     | genre = punk | decade = sixties

相片:

1 | rockitup.jpg   | Belongs to Album 2
2 | uno.jpg        | Belongs to Album 4
3 | punkio.jpg     | Belongs to Album 5
4 | punkio2.jpg    | Belongs to Album 5
5 | punkio3.jpg    | Belongs to Album 5
6 | basiq.jpg      | Belongs to Album 3

所以,我想最终在我的模板中显示的是:

In the sixties this was happening:

{% for genre in genres %}
  <li>Genre: {{ genre.title }} has {{ genre.total }} albums and here is an image: 
    <img src="{{ genre.image.url }}"></li>
{% endfor %}

在我看来,这就是我所做的:

genres = Genre.objects.annotate(total=Count('albums', filter=Q(decade__name='sixties')))

这很好——我成功地检索了我想要展示的十年的专辑总数。现在的问题是我如何也显示正确的图像?在 SQL 中,我会考虑一个子查询:

... (SELECT photo.image FROM album a JOIN photo ON album.photo = photo.id WHERE a.genre = genre.id AND photo.id IS NOT NULL) AS image ... 

类似的东西。但我不确定在 Django 中执行此操作的正确方法是什么。我是否以某种方式用一条记录对其进行注释?我是否使用自定义模板过滤器(如果是,应该如何工作)?

标签: pythonsqldjango

解决方案


我认为您可以使用向后相关的对象这样做:

{% for genre in genres %}
  <li>Genre: {{ genre.title }} has {{ genre.total }} albums and here are an image: 
    <img src="{{ genre.album_set.all.0.photo.url }}"></li>
{% endfor %}

它将获得第一张专辑的流派形象。您可以通过以下方式获取所有类型的图像:

{% for genre in genres %}
  <li>Genre: {{ genre.title }} has {{ genre.total }} albums and here is all images: 
    {% for album in genre.album_set.all %}
        <img src="{{ album.photo.url }}"></li>
    {% endfor %}
{% endfor %}

或者

在 Genre Model 中创建一个属性方法并将其显示在模板中。喜欢:

#model
class Genre(models.Model):
    title = models.CharField(max_length=255)

    def get_image(self):
      album = self.album_set.all().first()
      if album:
         return album.photo

# template
{% for genre in genres %}
  <li>Genre: {{ genre.title }} has {{ genre.total }} albums and here is an image: 
    {{ genre.get_image.photo.url }}
{% endfor %}

更新:使用子查询

from django.db.models import OuterRef, Subquery


albums_subquery = Album.objects.filter(genre=OuterRef('pk'))
Genre.objects.annotate(image=Subquery(albums_subquery.values('photo')[:1]))

# and then you can keep the template implementation as it is as described in question.

子查询文档


推荐阅读