фильтрация во вложенных циклах (django шаблонов) - PullRequest
0 голосов
/ 20 апреля 2020

У меня есть две модели, книги и жанры.

class Book(models.Model):
     title = models.CharField(max_length=300)
     author = models.CharField(max_length=100)
     year = models.IntegerField()
     genre = models.ForeignKey('Genre', on_delete=models.CASCADE)

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

На моей веб-странице я хочу отобразить каждый жанр, а внутри каждого жанра - только книги, чей год - 1950 или более поздний. Код в моем шаблоне выглядит следующим образом:

{% for genre in genres %}
     <h1> {{genre.title}} </h1>
     {% for book in genre.book_set.all %}
          {{book.title}}
     {% endfor %}
{% endfor %}

Проблема в том, что он показывает ВСЕ книги в этом жанре, а не только книги, опубликованные после 1950 года. Можно ли отфильтровать результат из genre.book_set.all каким-то образом получить только те, которые соответствуют определенному атрибуту?

Edit 1

Мои взгляды выглядят так:

def books_home(request):
    books = Book.objects.filter(year__gt='1950')
    genres = Genre.objects.all()
    return render(request, "index.html", {"books": books, "genres": genres})

Т.е. я пытаюсь отфильтровать книги на этом этапе, но это не тянет к шаблону.

Ответы [ 2 ]

1 голос
/ 20 апреля 2020

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

class Book(models.Model):
     title = models.CharField(max_length=300)
     author = models.CharField(max_length=100)
     year = models.IntegerField()
     genre = models.ForeignKey('Genre', on_delete=models.CASCADE)

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

С вашими моделями все в порядке, однако их можно улучшить следующими способами :

в изменении класса Book:

class Book(models.Model):    
    ...
    genre = models.ForeignKey('Genre', on_delete=models.CASCADE, related_name="books")
    ...

Добавив это связанное имя, вы сможете выполнить обратный поиск, используя Genre, например:

myGenre.books.all()

Это дало бы вам все книги в этом жанре.

В любом случае, я отвлекся.

В вашем View.py, который возвращает шаблон, а не тот лог c в вашем шаблоне согласно ответу, который вы пометили как правильный, просто отфильтруйте установленный там запрос для точной информации, которую вы хотите отобразить.

Я полагаю, что в настоящее время у вас есть что-то похожее на это в ваш взгляд:

render("template.html", {genres=Genre.objects.all()})

вместо Genre.objects.all(), используйте Book и наоборот Book:

Book.objects.order_by('-genre').filter(year__gte=1950)

Таким образом, вы получите только те книги, которые вам действительно нужны. gte «больше или равно»; если вы хотите просто после 1950 года, измените его на year__gt.

Кроме того, если вам нужны только название и жанр книги, которые вы можете сделать:

Book.objects.order_by('-genre').filter(year__gte=1950).values_list("title", pk)

Вы используете только заголовок, но вы можете указать любое поле модели.

Решение, приведенное выше, не разбивает его на Genre с, поэтому вам нужно сделать немного больше. Вы можете сделать это с помощью annotate вместо списка значений , читая здесь , или вы можете сначала просто выбрать жанры и сделать следующее:

(Примечание: использование annotate уменьшит БД обращается к 1, но в зависимости от количества жанров это может не быть проблемой:

books = {}
for genre in Genre.objects.all().order_by('-title'):
     books[genre.title] = Book.objects.order_by('-genre').filter(year__gte=1950, genre=genre).values_list("title")

Использование order_by('-title') просто гарантирует, что мы получим некоторый порядок в жанрах - в данном случае по алфавиту.

Затем вы можете просто вернуть context=books или добавить его к текущему объекту контекста, а затем настроить свой шаблон соответственно следующим образом:

{% for key, values in books.items %}
    <h1> {{key}} </h1>
    {% for book in values %}
        {{book}}
    {% endfor %}
{% endear%}

Делая это таким образом, вы не запрашиваете БД для каждой книги, и вы не фильтруете в шаблоне.

Еще одна вещь до I go, если вы не планируете разрешать пользователям добавлять жанры, то есть они предопределены, вы может покончить с моделью Genre, полностью меняя поле genre на модели Book для простого

genres = (
("scf", "SciFi)",
("hrr", "Horror"),
("rom","Romance"),
("gen", "General"),
)
genre = models.CharField(choices=genres, default="gen", max_length=3)

Я добавил, что только для справки; использование модели лучше, если вам нужно добавить жанры, или если у у тебя их будет много.

1 голос
/ 20 апреля 2020

Я бы рекомендовал вам выполнять фильтрацию в вашем представлении, а не в самом шаблоне. Там вы можете использовать все функции djangos и вам не нужно реализовывать пользовательские теги / фильтры для шаблона. Затем вы можете также выполнить оптимизацию запросов с помощью select_ / prefetch_related позже (если вы получаете больше запросов).

Пример:

class myview(...):
    qs = Genre.objects.filter(book_set__year__gt=1950)
    ...

Также см. Поиск полей в документы для фильтрации с gt.

...