Код на салфетке
2.22K subscribers
746 photos
14 videos
2 files
788 links
Канал для тех, кому интересно программирование на Python и не только.

Сайт: https://pressanybutton.ru/
Чат: https://t.iss.one/+Li2vbxfWo0Q4ZDk6
Заметки автора: @writeanynotes

Реклама и взаимопиар: @Murzyev1995
Сотрудничество и др.: @proDreams
Download Telegram
Приветствую.

С ботом мы пока закончили, его необходимо разместить на сервере. Но тут есть два момента: Запускать на сервере данного бота бесполезно. Но инструкция по запуску, универсальна. Следует ли сейчас делать пост по запуску бота на сервере?
Anonymous Poll
40%
Делать пост
20%
Подождать сайт
40%
Сделать посты и по боту и сайту и общий
Django 16. Модель категорий

С ботом пока всё, приступим к разработке самого сайта.

В следующих постах сосредоточимся на следующем функционале:
- Разделы категорий
- Посты
- Поиск по сайту

Стоит отметить, что у меня достаточно поверхностные знания и навыки в вёрстке, по этому примеры шаблонов будут максимально простыми.

И начнём с категорий.
При создании модели категорий, мы будем использовать не встроенный в Django класс модели, а библиотеку django-mptt.
MPTT - это метод хранения и обработки иерархических данных в базе данных. Библиотека django-mptt позволит создавать и работать с иерархиями. Проще говоря, мы сможем сделать древовидное представление категорий.

Установим библиотеку pip install django-mptt и добавим её в requirements.txt.
И пропишем в файле settings.py, добавив mptt в список INSTALLED APPS.
Откроем файл models.py и создадим класс CategoryModel наследующий MPTTModel.
Необходимо создать поля:
- Заголовок - строковое поле с названием категории
- Слаг/Slug - поле, формирующее из строки заголовка - slug-строку. Slug формируется только из ASCII символов, цифр и дефисов.
- Родитель - внешний ключ на родительскую категорию. Поскольку используется MPTTModel, то вместо стандартного ForeignKey используется TreeForeignKey.
- Описание - строковое поле, содержащее описание категории
Так же класс Meta, содержащий название модели и определяющий уникальную пару полей.
Класс MPTTMeta с сортировкой по вложенностям.
Метод get_absolute_url, возвращающий URL-адрес для объекта модели.
И последним будет dunder-метод __str__, возвращающим заголовок.
from django.urls import reverse  
from mptt.managers import TreeManager
from mptt.models import MPTTModel, TreeForeignKey


class CategoryModel(MPTTModel):
title = models.CharField(max_length=100,
verbose_name="Заголовок")
slug = models.SlugField(verbose_name="Альт. заголовок")
parent = TreeForeignKey('self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='children',
db_index=True,
verbose_name='Родительская категория')
description = models.CharField(max_length=350,
verbose_name="Описание",
blank=True)

objects = TreeManager()

class MPTTMeta:
order_insertion_by = ['title']

class Meta:
unique_together = [['parent', 'slug']]
verbose_name = 'Категория поста'
verbose_name_plural = 'Категории постов'

def get_absolute_url(self):
return reverse('blog:category_page', args=[int(self.pk), str(self.slug)])

def __str__(self):
return self.title


Про поле parent. В поле создаётся объект класса TreeForeignKey из django-mptt, позволяющий делать древовидные категории. Аргумент self указывает, что внешний ключ ссылается на ту же модель, в которой он определен, то есть на CategoryModel.
Аргумент on_delete=models.CASCADE задает поведение при удалении связанного объекта. В данном случае, если родительская категория удалена, то все дочерние категории также будут удалены.
Аргументы null=True и blank=True позволяют полю parent быть необязательным и не требуют обязательного ввода значения при создании объекта.
Аргумент related_name='children' определяет имя обратной связи между родительской и дочерней категорией. То есть, у каждого объекта модели CategoryModel будет доступ к своим дочерним категориям через атрибут children.
Аргумент db_index=True создает индекс в базе данных, что позволяет ускорить поиск и сортировку по этому полю.

В поле objects будем использовать не обычного Django-менеджера, а специального для работы с MPTTModel.

В классе Meta, помимо привычных полей, появилось новое unique_together. Данное поле определяет какая пара полей модели должны быть уникальными.
Класс MPTTMeta, делает то-же, что и Meta, только для объектов модели MPTTModeL. Поле order_insertion_by определяет порядок вставки новых элементов в дерево. В нашем случае, элементы будут вставляться в порядке возрастания заголовка.

Появился новый метод get_absolute_url. Данный метод возвращает URL-адрес до страницы объекта.

В следующем посте, займёмся шаблонами, зарегистрируем и протестируем модель позже.

Файлы к посту, можно получить в боте по коду: 157101
1👍1
Django 17. Разделение шаблонов

Шаблонизатор Jinja, используемый в Django, позволяет гибко управлять макетами, разбивая их на блоки, а так же, добавляя различные фильтры и конструкции, например, цикл for или условие if.

Макеты принято разбивать не на шаблоны страниц, а на отдельные модули.
Например, у нас есть главная страница, на которой есть: шапка, футер, блок со списком категорий, блок с последними постами, а так же блок популярных постов. Примерная структура:
Файл основного шаблона, содержащий подключения статических файлов, мета-теги, шапка и футер - base.html.
Файл шаблона главной страницы, в котором будет код, форматирующий блоки, так как нам надо. - index.html.
Файл блока списка категорий - index_page/category_list.html
Файл блока последних постов - index_page/latest_posts.html
Файл блока популярных постов - index_page/popular_posts.html

Такая структура позволит избавится от огромных html файлов, выделив модули в отдельные файлы.
Для начала изменим структуру нашего шаблона, который мы делали в одном из первых постов.
В директории templates/blog, создадим файл index.html. Внутри пропишем наследование от базового шаблона, а так же тэги начала и конец блока с контентом:
{% extends "blog/base.html" %}
{% block title %}Главная{% endblock %}

{% block content %}

{% endblock %}

Откроем файл base.html и из блока body вырежем блок section и поместим его в файле index.html между тэгами block content и endblock.

Сделав несколько переносов строки после section и до тега endblock добавляем подключение файла со списком категорий:
    {% block categories_list %}
{% include 'blog/index_page/category_list.html' %}
{% endblock %}

А на место где был блок section, в файле base.html вставим тэги:
{% block content %}  
{% endblock %}

Теперь при обращении к файлу index.html, будет подгружаться всё содержимое файла base.html.

В директории templates/blog, создадим новую директорию index_page. Должна получится следующая иерархия templates/blog/index_page. В ней создадим файл category_list.html.
В файле так же создадим начало и конец блока, однако назовём его не content, а categories_list и добавим код вывода категорий:
{% block categories_list %}
{% load mptt_tags %}
<ul class="root">
{% recursetree categories_list %}
<li>
<a href="{{ node.get_absolute_url }}">{{ node.title }}</a>
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
{% endblock %}

Теперь необходимо создать файл для страницы категории. В директории templates/blog создадим файл category_page.html.
В файле пропишем наследование от base.html, пропишем теги блока контента и выведем название и описание категории:
{% extends "blog/base.html" %}
{% block title %}{{ category.title }}{% endblock %}

{% block content %}
<h1>{{ category.title }}</h1>
<p>{{ category.description }}</p>
{% endblock %}

В следующем посте займёмся представлениями. Доработаем представление для главной страницы и напишем новое для страницы категории.
Зарегистрируем и протестируем.

Файлы к посту, можно получить в боте по коду: 179632
👍1
Django 18. Представление для главной и категорий

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

Откроем файл views.py.
В данный момент наше представление index просто вывод заглушки. Оно ничего не передаёт в шаблон. Давайте это изменим.
В функции index, сделаем несколько отступов перед возвратом рендера. Там напишем переменную categories_list, в которой будем обращаться к нашей модели CategoryModel и получать все объекты через менеджера:
categories_list = models.CategoryModel.objects.all()


Чуть ниже создадим переменную context, это словарь, в котором ключом является имя переменной, к которой мы сможем обращаться внутри шаблона, а значением будут передаваемые данные.
    context = {
'categories_list': categories_list,
}
Поскольку у нас теперь не base.html выполняет функцию главной страницы, а index.html, ниже в строке с возвратом рендера изменим название файла и пропишем третьим параметром передачу в него контекста.
    return render(request,
'blog/index.html',
context)


Напишем новую функцию category_page, принимающую в себя request, pk и slug.
В шаблон небходимо передать информацию о текущей категории, для этого, создаём переменную category и как и в предыдущей функции, получаем из модели данные. Только нам необходимы не все объекты, а конкретный.
category = models.CategoryModel.objects.get(pk=pk)


По аналогии создаём контекст и возврат рендера:
    context = {
'category': category,
}

return render(request,
'blog/category_page.html',
context)


Готово. Теперь добавим URL-паттерн для страницы категорий. Откроем файл urls.py в директории приложения и в список urlpatterns сайта, добавим следующую строку:
path('category/<int:pk>-<str:slug>/', views.category_page, name='category_page'),

Первым параметром мы формируем паттерн, состоящий из слова category, pk категории и его slug-поля.

Осталось зарегистрировать модель в панель адмниистратора. Откроем admin.py и зарегистрируем:
from mptt.admin import DraggableMPTTAdmin


@admin.register(models.CategoryModel)
class CategoryAdmin(DraggableMPTTAdmin):
list_display = ('tree_actions', 'indented_title', 'parent',)
list_display_links = ('indented_title',)
prepopulated_fields = {'slug': ('title',)}
search_fields = ['title', ]

Как можно заметить, мы наследуем наш класс не от ModelAdmin, а от DraggableMPTTAdmin, данный класс позволяет организовать удобное отображение дерева категорий.
В кортеже list_display появились два не указанных в модели поля:
- tree_actions - добавляет в интерфейс функции взаимодействия с объектами.
- indented_title - поле заголовка с отступом.

В переменной prepopulated_fields мы прописываем, что поле slug будет заполняться автоматически на основе данных из поля title.

Применим миграции:
python manage.py makemigrations

python manage.py migrate


Запустим Django и перейдём в панель администратора.
Там должна появиться наша модель. Сделайте несколько тестовых категорий разной вложенности, затем посмотрите как оно будет выглядеть на сайте.

Файлы к посту, можно получить в боте по коду: 234064
👍1
Django 19. Визуальный редактор CKEditor5

В прошлых постах сделали модель категорий. По хорошему теперь надо создать модель постов, но что бы не править модель в будущем, подготовим всё заранее.

Что бы посты выглядели прилично, с форматированием и рюшечками, установим визуальный редактор. Ранее, я использовал CKEditor, но в этот раз, решил опробовать CKEditor5.

Установим библиотеку pip install django-ckeditor-5. Вы уже должны были привыкнуть, что далее необходимо сразу добавить установленную библиотеку django-ckeditor-5==0.2.8 в requirements.txt. Также добавить django_ckeditor_5 в INSTALLED_APPS.

Не уходя из файла settings.py, в самый конец добавим конфигурацию:
Код конфигурации ищите в файле settings.py в архиве с материалами к посту.
Переменная CKEDITOR_5_CUSTOM_CSS задаёт пользовательские стили.
Если вы используете тёмную тему панели администратора, то в директории static необходимо создать директорию django_ckeditor_5 и в ней создать файл admin_dark_mode_fix.css со следующим содержимым:
.ck.ck-editor {  
color: black !important;
}


По умолчанию, редактор сохраняет изображения в корне директории media, что безусловно не удобно. Для того, что бы реализовать сохранение изображений в директориях post/%Y/%m/%d, служит параметр CKEDITOR_5_FILE_STORAGE. В нём определяется обработчик для сохранения файлов. Если у вас названия проекта совпадают, с моими, менять ничего не надо, если же не совпадают, то измените на соответствующие.
Создадим обработчик. Для этого перейдём в директорию приложения и создадим файл storage.py.
В файле пропишем следующий код:
import os
from datetime import datetime
from urllib.parse import urljoin

from django.conf import settings
from django.core.files.storage import FileSystemStorage


class CustomStorage(FileSystemStorage):
"""Пользовательское хранилище для изображений."""
date = datetime.now().strftime('%Y/%m/%d')
location = os.path.join(settings.MEDIA_ROOT, f"post/{date}/")
base_url = urljoin(settings.MEDIA_URL, f"post/{date}/")


Настройки почти окончены, осталось прописать URL-паттерн. Перейдём в urls.py в директории проекта и добавим в конец списка urlpatterns следующую строку:
path("ckeditor5/", include('django_ckeditor_5.urls'), name="ck_editor_5_upload_file"),


Тем, кто читает данный пост, только, что бы узнать как установить редактор:
Для того, что бы форматированный в редакторе текст отображался на страницах сайта корректно, в шаблоне в тег вывода переменной с текстом, например, {{ post.body }} необходимо добавить фильтр safe.
Должно быть вот так: {{ post.body | safe }}.

В следующем посте начнём писать модель поста, будет много текста.

Файлы к посту, можно получить в боте по коду: 883614
👍1
Приветствую.

Сайт для канала ещё в процессе разработки, но уже сейчас можно посмотреть, что получается.

Сайт: https://pressanybutton.ru/

Я уже говорил, что верстальщик из меня посредственный, поэтому, я буду рад вашим комментариям с предложениями, критикой или просто обсуждению сайта.
🔥3👍2🤩1
Django 20. Модель поста

Сегодня напишем модель поста. Текста будет много и кода тоже.

Откроем файл models.py и создадим новый класс PostModel унаследованный от models.Model.

Создадим вложенный класс Status унаследованный от models.TextChoices, в котором создадим два поля для статуса поста: Черновик и Опубликован.

Далее начнём описывать поля модели. У нас их будет 13 штук:
- title - поле с заголовком поста. Тип CharField.
- slug - поле преобразованное из заголовка в безопасный набор символов. Тип SlugField.
- image - поле для изображения поста. Тип ImageField.
- author - поле со ссылкой на пользователя, написавшего пост. Тип ForeignKey.
- category - поле со ссылкой на категорию. Тип TreeForeignKey.
- short_body - поле с коротким описанием/содержанием поста. Тип CKEditor5Field.
- full_body - поле с основным текстом поста. Тип CKEditor5Field.
- publish - поле с датой публикации поста. Тип DateTimeField.
- created - поле с датой создания поста. Тип DateTimeField.
- updated - поле с датой обновления поста. Тип DateTimeField.
- status - поле со статусом поста. Выбор статуса происходит из класса Status. Тип CharField.
- views - поле со счётчиком просмотров. Тип IntegerField. Стоит отметить, что это не уникальные просмотры, а буквально каждое обновление страницы с постом, возможно в будущем, поле доработаем.
- objects - поле с определением менеджера для модели. Тип Manager.

Далее вложенный класс Meta со следующими полями:
- ordering - определяем сортировку по дате публикации в от самого нового.
- indexes - определяем индексирование базы данных по полю публикации.
- verbose_name и verbose_name_plural - определяем имя и множественное имя модели.

Метод get_absolute_url, возвращает URL-адрес для объекта модели.

Методы get_next_post и get_previous_post возвращают следующий и предыдущий пост в данной категории основываясь на дате публикации.

Dunder-метод __str__ возвращает заголовок поста.

Код:
from django_ckeditor_5.fields import CKEditor5Field
from django.contrib.auth.models import User
from django.utils import timezone

class PostModel(models.Model):
"""Модель поста"""

class Status(models.TextChoices):
"""Класс выбора статуса поста"""
DRAFT = 'ЧЕ', 'Черновик'
PUBLISHED = 'ОП', 'Опубликовано'

title = models.CharField(max_length=200,
verbose_name="Заголовок")
slug = models.SlugField(verbose_name="Альт. заголовок")
image = models.ImageField(upload_to='post/%Y/%m/%d',
default='default/not_found.png',
verbose_name='Изображение поста')
author = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='post',
verbose_name="Автор")
category = TreeForeignKey('CategoryModel',
on_delete=models.PROTECT,
related_name='post',
verbose_name='Категория')
short_body = CKEditor5Field(max_length=350,
verbose_name="Краткое описание",
blank=True)
full_body = CKEditor5Field(verbose_name='Содержимое поста')
publish = models.DateTimeField(default=timezone.now,
verbose_name="Опубликовано")
created = models.DateTimeField(auto_now_add=True,
verbose_name="Создано")
updated = models.DateTimeField(auto_now=True,
verbose_name="Обновлено")
status = models.CharField(max_length=2,
choices=Status.choices,
default=Status.DRAFT,
verbose_name="Статус")
views = models.IntegerField(default=0,
verbose_name="Количество просмотров")

objects = models.Manager()

class Meta:
ordering = ['-publish']
indexes = [
models.Index(fields=['-publish']),
]
verbose_name = 'Пост'
verbose_name_plural = 'Посты'

def get_absolute_url(self):
"""Метод получения URL-адреса объекта"""
return reverse('blog:post_page', args=[int(self.pk), str(self.slug)])

def get_next_post(self):
"""Метод получения следующего поста"""
try:
return self.get_next_by_publish(category=self.category)
except PostModel.DoesNotExist:
return None

def get_previous_post(self):
"""Метод получения предыдущего поста"""
try:
return self.get_previous_by_publish(category=self.category)
except PostModel.DoesNotExist:
return None

def __str__(self):
return self.title


При желании, поле description в модели категории, так же можно изменить на CKEditor5Field, добавив в него графический редактор
Применим миграции:
python manage.py makemigrations

python manage.py migrate

По аналогии с моделью категории нам далее необходимо: Зарегистрировать модель в панели администратора, написать представление для страницы поста, добавить URL-паттерн и сделать шаблон страницы с постом. Этим займёмся в следующих постах.

Файлы к посту, можно получить в боте по коду: 398438
Django 21. Регистрация модели поста

Модель создали, теперь необходимо выполнить ещё ряд действий.

Начнём с регистрации модели в панели администратора. Перейдём в файл admin.py и создадим класс PostAdmin унаследованный от admin.ModelAdmin. Так же не забудьте про декоратор над классом или строку с регистрацией класса в самом низу файла, в зависимости от того, каким способом вы регистрируете модель.

Далее создаём 7 полей:
- list_display - Список имен полей модели, которые должны быть отображены в списке объектов.
- list_filter - Список имен полей, по которым Django создаст фильтры в правой части страницы изменения списка.
- search_fields - Активирует поиск в правом верхнем углу страницы со списком объектов.
- prepopulated_fields - Словарь, определяющий поля, которые должны быть автоматически заполнены из значений других полей.
- date_hierarchy - Поля DateField или DateTimeField в модели, которые должны быть использованы навигации по датам.
- ordering - Указывает порядок расположения записей на странице.
- exclude - Список полей, которые будут исключены из формы создания/редактирования объекта.

Поскольку по умолчанию, при создании нового объекта, в нашем случае нового поста, поле Автор будет представлять из себя выпадающий список со всеми зарегистрированными пользователями. Нам это ни к чему. Мы скрываем поле author со страницы создания/редактирования поста добавив поле в список exclude.
Для того, чтобы пользователь подставлялся в поле автоматически, мы переопределяем метод сохранения объекта.
Создадим метод save_model, принимающий ряд аргументов.
В теле метода, мы в поле author у будущего поста подставляем текущего пользователя. Сохраняем объект и вызовем родительский метод для сохранения модели.

Для того, что бы в столбцы объектов добавить отображение превью поста, создадим метод image_preview передав в него self и obj.
В теле метода проверяем, есть ли изображение у объекта поста или нет. Если его нет, то вернём строку No image found. Если изображение есть(а у нас оно всегда будет, но дополнительная защита всё же не повредит), то мы с помощью функции format_html, возвращаем тег img с путём до изображения.
Обратите внимание! Pycharm будет ругаться и просить сделать метод статическим, т.к. он не использует self. Игнорируем эту ошибку, поскольку нам необходимо, что бы метод знал, в каком классе он вызван, иначе он будет работать не корректно или вовсе не будет работать, если его сделать статическим.

И ниже, после метода добавим переопределение названия нового поля image_preview.

Код:
from django.utils.html import format_html


@admin.register(models.PostModel)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'image_preview', 'author', 'category', 'publish', 'created',
'updated', 'views', 'status', ]
list_filter = ['status', 'publish', 'author', ]
search_fields = ['title', 'body', ]
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'publish'
ordering = ['status', 'publish', ]
exclude = ["author", 'views', ]

def save_model(self, request, obj, form, change):
obj.author = request.user
obj.save()
super().save_model(request, obj, form, change)

def image_preview(self, obj):
if obj.image:
return format_html(f'<img src="{obj.image.url}" width="100"/>')
else:
return '(No image found)'

image_preview.short_description = 'Превью'

Перейдём в файл urls.py в директории приложения и в паттерны сайта, добавим для страницы поста, следующую строку:
path('post/<int:pk>-<str:slug>/', views.post_page, name='post_page'),
Не обращаем внимания на ошибку, у нас же всё ещё нет представления, этим займёмся в следующем посте.

Файлы к посту, можно получить в боте по коду: 810542
👍1
Приветствую.

Небольшое дополнение к предыдущему посту(пост уже обновлён).

Как заметил Кирилл в чате, просто сохранять объект в переопределённом методе, без вызова родительского класса - не самая хорошая практика.

Я обновил пост, но, если вы уже повторили его, тогда в метод save_model добавьте строку super().save_model(request, obj, form, change).

Если у вас есть комментарии, вопросы или просто хотите пообщаться - заходите к нам в чат: https://t.iss.one/+cm-ITbA-JTczMTRi
👍1
Django 22. Представление для страницы поста

Сегодня мы напишем представление для страницы поста, а также добавим отображение постов на странице категории.

Начнём с представления поста. Перейдём в файл views.py и напишем функцию post_page, передавая те же аргументы, что и для представления категорий, а именно request, pk и slug.

В теле функции создадим переменную post и так же как мы делали для категории, получим из модели конкретный пост.
Создаём переменную context с переменной post и делаем возврат рендера.

Код:
def post_page(request, pk, slug):
post = models.PostModel.objects.get(pk=pk)
post.views += 1
post.save()

context = {
'post': post,
}

return render(request,
'blog/post_page.html',
context)
Теперь изменим функцию category_page.
Под переменной category, создаём ещё одну с именем posts.
И тут есть два варианта вывода постов в категории.

1. Если нам нужно выводить посты строго в той категории, для которой они прописаны, то прописываем получение постов относящихся только к этой категории: posts = models.PostModel.objects.filter(category=category).

2. Если нам надо, что бы посты отображались как в предназначающейся категории, так и в категории выше, как пример у меня категория Гайды и в ней категория Django. То создаём две переменные. В одной мы получаем список категорий, а во второй получаем посты относящиеся к категориям из списка:
    descendant_categories = category.get_descendants(include_self=True)
posts = models.PostModel.objects.filter(category__in=descendant_categories)

Код:
  # Первый вариант
posts = models.PostModel.objects.filter(category=category)

# Второй вариант
descendant_categories = category.get_descendants(include_self=True)
posts = models.PostModel.objects.filter(category__in=descendant_categories)

context = {
'category': category,
'posts': posts,
}
Для себя, выбрал второй вариант.

Представления написаны, перейдём к шаблонам.
Сперва изменим шаблон страницы категории. Откроем файл category_page.html в директории с шаблонами.
Под секцией с выводом информации о категории, напишем новую секцию, в которой будем через цикл for выводить элементы.

Код:
    <section class="posts">
<div class="container">
<hr>
{% for post in posts %}
<h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
<p>{{ post.short_body | safe }}</p>
<hr>
{% endfor %}
</div>
</section>

Наконец, создадим файл post_page.html.
Подключим базовый шаблон, пропишем блок с контентом и внутри вывод информации о посте.

Код:
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}

{% block content %}
<section class="post">
<div class="container">
<h1>{{ post.title }}</h1>
<p>{{ post.full_body | safe }}</p>
</div>
</section>
{% endblock %}

Осталось всё проверить. Запустите Django, перейдите в панель администратора и добавьте несколько тестовых постов.
Затем перейдите на сайт и проверьте как всё работает.

Файлы к посту, можно получить в боте по коду: 220049
👍2
Django 23. Добавляем sitemap и счётчики

Как и для любого сайта, аналитика посещений очень важна. Как и индексирование сайта в поисковых системах.

В этом посте опишу, как удобно встроить счётчики Яндекс.Метрики и Google Analytics в шаблон сайта, как создать файлы sitemap.xml и robots.txt.

Начнём с аналитики и счётчиков.
Дабы не засорять код базового шаблона длинными скриптами со счётчиками Яндекса и Google, установим пакет pip install django-analytical. Пропишем его в requirements.txt и добавим analytical в INSTALLED_APPS.

В этом же файле пропишите ваши номера счётчиков:
# Статистика  
YANDEX_METRICA_COUNTER_ID = ''
GOOGLE_ANALYTICS_GTAG_PROPERTY_ID = ''


Все доступные виды счётчиков, можно найти в официальной документации: https://django-analytical.readthedocs.io/en/latest/
Затем перейдём в файл base.html. Нам необходимо вставить теги аналитики.
В самом верху файла, после тэга подключения статики, добавьте тег {% load analytical %}.
Затем, в блоке <head> добавьте два тега. Один в самое начало блока, второй в самый конец:
{% analytical_head_top %}
{% analytical_head_bottom %}

Точно также и со следующими двумя тегами, но уже в блок <body>:
{% analytical_body_top %}
{% analytical_body_bottom %}

На этом всё. В скором времени, на странице счётчика начнёт появляться статистика.


Теперь займёмся настройкой файла sitemap.xml.

Перейдём в файл settings.py и в INSTALLED_APPS добавим следующие модули:
'django.contrib.sites',
'django.contrib.sitemaps',
И добавим переменную SITE_ID = 1 в начало файла, после переменной BASE_DIR.

Модуль django.contrib.sites добавляет поддержку нескольких сайтов, это нам не нужно, однако это необходимо модулю django.contrib.sitemaps.
Выполним миграции python manage.py migrate.

Запустите Django и перейдите в панель администратора. Там будет новый пункт меню Сайты.
Откройте его. В списке увидите сайт example.com. Нажмите на него, что бы перейти к редактированию.
На открывшейся странице введите адрес вашего сайта, если он всё ещё на локальной машине, то введите 127.0.0.1

В директории приложения создадим файл sitemap.py.
Cоздадим класс PostSitemap унаследованный от Sitemap.
В теле класса пропишем пять методов(для класса категории, их будет четыре):
- items - метод, возвращающий все объекты модели. В нашем случае, все опубликованные посты
- lastmod - метод, возвращающий дату обновления поста. Данный метод не нужен для класса с категориями, поскольку у нас нет поля изменения категории
- priority - метод, возвращающий приоритет для объектов модели. Приоритет определяется от 0.0(наименьший), до 1.0(наивысший).
- changefreq - метод, возвращающий периодичность обновления постов. Доступные варианты: "always", "hourly", "daily", "weekly", "monthly", "yearly", "never".
- location - метод, возвращающий URL-адрес объекта. Обратите внимание на то, что он заполняется также, как и метод get_absolute_url.

Код:
from django.contrib.sitemaps import Sitemap
from django.urls import reverse

from . import models


class PostSitemap(Sitemap):
def items(self):
return models.PostModel.objects.filter(status=models.PostModel.Status.PUBLISHED)

def lastmod(self, obj):
return obj.updated

def priority(self, obj):
return 0.8

def changefreq(self, obj):
return "weekly"

def location(self, obj):
return reverse('blog:post_page', args=[obj.pk, obj.slug])

По аналогии создайте классы для других моделей, которые необходимо внести в файл sitemap.xml, например, модель категории.

Перейдём в файл urls.py в директории проекта.
Создадим словарь, в котором перечислим наши классы из файла sitemap.py и в конец urlpatterns добавим строку для доступа к файлу:
from django.contrib.sitemaps.views import sitemap

from blog.sitemap import PostSitemap, CategorySitemap

sitemaps = {
'PostSitemap': PostSitemap,
'CategorySitemap': CategorySitemap,
}


urlpatterns = [
# ...
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
]

Для создания файла robots.txt, достаточно сделать всего два действия.
Первое, что необходимо сделать - это создать сам файл. Перейдём в директорию static и в корне создадим файл robots.txt.
В нём пропишите необходимые разрешения или запреты для поисковых роботов, например, самый простой вариант:
User-agent: *
Disallow: /admin/

Второе, что необходимо сделать - это прописать URL-паттерн для доступа к файлу.
Откроем файл urls.py в директории проекта и в конец urlpatterns добавим следующую строку:
from django.urls import path, include, re_path
from django.views.static import serve

re_path(r'^robots.txt$', serve, {'document_root': settings.STATIC_ROOT, 'path': "robots.txt"}),

На этом всё. Осталось добавить файлы в отслеживание поисковых систем.

Файлы к посту, можно получить в боте по коду: 276141
👍1