Теория
Настало время добавить к постам tags(теги), которые бы сгруппировали наши посты по определенным тематикам. Плюс ко всему сделаем, чтобы все связанные посты могли выходить списком при нажатии на тег и чтобы все теги тоже выходили списком при нажатии на ссылку.
Т.е., для этого нам понадобиться добавить еще одну таблицу в blog/models.py и выполнить еще раз процесс миграции, чтобы изменения локальных моделей обновились в базе данных.
Практика
Файл models.py
Откроем blog/models.py и добавим новую таблицу Tag . Выделенные строки соответствуют местам изменения кода
from django.db import models from django.shortcuts import reverse # Create your models here. class Post(models.Model): title = models.CharField(max_length=150, db_index = True) slug = models.SlugField(max_length=150, unique = True) body = models.TextField(blank=True, db_index = True) date_pub = models.DateTimeField(auto_now_add = True) tags = models.ManyToManyField('Tag', blank=True, related_name = 'posts') def get_absolute_url(self): return reverse('post_detail_url', kwargs={'slug': self.slug}) def __str__(self): return '{}'.format(self.title) class Tag(models.Model): title = models.CharField(max_length=50) slug = models.SlugField(max_length=50, unique = True) def get_absolute_url(self): return reverse('tag_detail_url', kwargs = {'slug': self.slug}) def __str__(self): return '{}'.format(self.title)
После обновления файла моделей нужно их отразить в реальной БД, поэтому выполняем команды миграции в консоли
python3 manage.py makemigrations python3 manage.py migrate
После выполнения миграции можно поэксперементировать в консоли. Для этого заходим в shell python, через команду
python3 manage.py shell
И потренеруемся с добавлением и связкой тегов к постам
>>> from blog.models import * >>> t = Tag.objects.create(title='Django', slug='django') >>> t <Tag: Django> >>> Post.objects.values() <QuerySet [{'id': 1, 'title': 'New post title 1', 'slug': 'new-post-1', 'body': 'Body text 1 ...', 'date_pub': datetime.datetime(2018, 11, 11, 18, 56, 18, 127958, tzinfo=<UTC>)}, {'id': 2, 'title': 'New post title 2', 'slug': 'new-post-2', 'body': 'Body text 2 ...', 'date_pub': datetime.datetime(2018, 11, 11, 19, 8, 32, 411534, tzinfo=<UTC>)}, {'id': 3, 'title': 'New post title 3', 'slug': 'new-post-3', 'body': 'Body text 3 ...', 'date_pub': datetime.datetime(2018, 11, 11, 19, 24, 47, 409418, tzinfo=<UTC>)}, {'id': 4, 'title': 'New post title 4', 'slug': 'new-post-4', 'body': 'Body text 4 ...', 'date_pub': datetime.datetime(2018, 11, 11, 19, 25, 15, 533403, tzinfo=<UTC>)}, {'id': 5, 'title': 'New post title 5', 'slug': 'new-post-5', 'body': 'Body text 5 ...', 'date_pub': datetime.datetime(2018, 11, 11, 19, 25, 30, 460405, tzinfo=<UTC>)}, {'id': 6, 'title': 'New post title 6', 'slug': 'new-post-6', 'body': 'Body text 6 ...', 'date_pub': datetime.datetime(2018, 11, 11, 19, 25, 47, 309093, tzinfo=<UTC>)}, {'id': 7, 'title': 'New post title 7', 'slug': 'new-post-7', 'body': 'Body text 7 ...', 'date_pub': datetime.datetime(2018, 11, 11, 19, 26, 1, 493016, tzinfo=<UTC>)}]> >>> p = Post.objects.get(slug='new-post-1') >>> p.title 'New post title 1' >>> p.title = 'Post width tag' >>> p.save() >>> p.tags.add(t) >>> p.tags.all() <QuerySet [<Tag: Django>]>
Опишем некоторые команды
- from blog.models import * — импортируем все модели;
- t = Tag.objects.create(title=’Django’, slug=’django’) — создаем новый эксземпляр(для таблицы это будет строка) сущности
- Post.objects.values() — выводим все значения в модели Post;
- p = Post.objects.get(slug=’new-post-1′) — ищем посты по соответствию;
- p.title — выводим заголовок поста;
- p.title = ‘Post width tag’ — задаем новый заголовок для поста;
- p.save() — сохраняем экземпляр с новым изменениями и перезаписываем в БД, как строку;
- p.tags.add(t) — добавляем новый экземпляр тега к посту;
- p.tags.all() — выводим все теги экземпляра поста.
Файл urls.py
Открываем файл blog/urls.py и задаем 2 дополнительных маршрутизации для вывода постов по тегу и списка всех тегов и плюс к этому добавим к ним обработчики для видов
from django.urls import path from .views import * urlpatterns = [ path('', posts_list, name='posts_list_url'), path('post/<str:slug>', post_detail, name='post_detail_url'), path('tags/', tags_list, name='tags_list_url'), path('tag/<str:slug>', tag_detail, name='tag_detail_url'), ]
В данном случае, мы добавили 2 шаблона URL
- tags — выведет все существующие теги в базе;
- tag/<str:slug> — выведет все посты, прикрепленные к этому тегу.
Файл views.py
Открываем файл blog/views.py и пишем обработчики для шаблонов ссылок маршрутизации, которые выведут в шаблоны вида список тегов и список связанных постов
from django.shortcuts import render from django.http import HttpResponse from .models import Post, Tag # Create your views here. def posts_list(request): posts = Post.objects.all() return render(request, 'blog/index.html', context = {'posts' : posts}) #return HttpResponse("<h1>Hello! Hello!</h1>") def post_detail(request, slug): post = Post.objects.get(slug__iexact = slug) return render(request, 'blog/post_detail.html', context = {'post' : post}) def tags_list(request): tags = Tag.objects.all() return render(request, 'blog/tags_list.html', context={'tags': tags}) def tag_detail(request, slug): tag = Tag.objects.get(slug__iexact = slug) return render(request, 'blog/tag_detail.html', context={'tag': tag})
Как мы видим, нам потребуется создать еще 2 шаблона вида
- tags_list.html — шаблон страницы списка всех тегов;
- tag_detail.html — шаблон страницы всех постов с тем или иным конкретным тегом.
Принцип DRY в шаблонах Django
Как было в предыдущей части упомянуто, нам нужно создать еще 2 шаблона вида: tags_list.html и tag_detail.html.
Тут важно учитывать, что вывод постов по ссылке mysite.com/blog и по ссылке mysite.com/blog/tag/tagname идентичны и нам нужно для этого создать карточку для поста, чтобы не копипастить один и тот же код и для этого в шаблонах Django есть возможность подключать куски кода html друг в друга и это соответствует принципу DRY.
Don’t repeat yourself, DRY (рус. не повторяйся) — это принцип разработки программного обеспечения, нацеленный на снижение повторения информации различного рода, особенно в системах со множеством слоёв абстрагирования. Принцип DRY формулируется как: «Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы».
Т.е., помимо шаблонов tags_list.html и tag_detail.html мы еще создадим папку blog/templates/blog/includes, в которой будет карточка поста post_card.html, который будет подключен к файлу blog/templates/blog/index.html и к файлу blog/templates/blog/tag_detail.html
Файл blog/templates/blog/index.html
{% extends 'blog/base_blog.html'%} {% block title %} Site Blog {% endblock %} {% block content %} <div> <div class="grid-x"> {% for post in posts %} {% include 'blog/includes/post_card.html'%} {% endfor %} </div> </div> {% endblock %}
Файл blog/templates/blog/tags_list.html
{% extends 'blog/base_blog.html'%} {% block title %} Site Blog - Tags list {% endblock %} {% block content %} <div> <div class="grid-x"> {% for tag in tags %} <div class="cell small-12 medium-3 large-3"> <div class="card"> <div class="card-section"> <h4> <a href="{{ tag.get_absolute_url }}" class="button"> {{ tag.title }} </a> </h4> </div> </div> </div> {% endfor %} </div> </div> {% endblock %}
Файл blog/templates/blog/tag_detail.html
{% extends 'blog/base_blog.html'%} {% block title %} Site Blog - Tag detail {% endblock %} {% block content %} <div> <h3>Posts with tag <b>{{tag.title|title}}</b>:</h3> <br/> <div class="grid-x"> {% for post in tag.posts.all %} {% include 'blog/includes/post_card.html'%} {% endfor %} </div> </div> {% endblock %}
Файл blog/templates/blog/includes/post_card.html
<div class="cell small-12 medium-3 large-3"> <div class="card"> <img src="https://foundation.zurb.com/sites/docs/assets/img/generic/rectangle-1.jpg"> <div class="card-section"> <h4>{{ post.title }}</h4> <p>{{ post.body | truncatewords:15 }}</p> <!-- <a href="{% url 'post_detail_url' slug=post.slug %}" class="button">Learn More</a> --> <a href="{{ post.get_absolute_url }}" class="button">Learn More</a> <h6>{{ post.date_pub }}</h6> </div> <div> Tags: {% for tag in post.tags.all %} <a href="{{ tag.get_absolute_url }}"> {{ tag.title }} </a> {% empty %} none {% endfor %} </div> </div> </div>
В карте поста важно отметить обновления, связанные с выводом тегов к посту в футере карточки.
В итоге карточки постов должны получиться, как на скриншотах внизу
Список тегов должно получиться, как внизу
Список постов по определенному тегу