Здравствуйте, сегодня я хотел бы вам рассказать о том, как сделать модель, которая хранит в себе обычные страницы, а не отдельные записи в базе данных (для ListView, TemplateView и тд). Речь пойдёт о том, как расширить и дополнить существующие в Django flatpages. Но хотелось бы рассказать о проблеме, с которой я столкнулся и почему решил поделиться данным функционалом. Часто возникает ситуация, когда в админке для администратора сайта нужно реализовать функционал самой обычной страницы (одна запись в БД – это одна страница, где прописывается url, контент и доп. инфа для конкретной страницы). Тем самым можно создавать прямо из админки новые страницы с любым url и контентом.
Приведу пример: была поставлена задача реализовать такую страницу, где было бы обычное текстовое поле, к которому прикручен ckeditor и администратор мог бы менять и писать нужный текст о компании, а также переписывать его. Первая мысль, которая тебя посещает — это обычная запись в модели и в контроллере (вьюхе) сделать класс на основе TemplateView, и страница создана без проблем. Но далее начинаешь понимать, что это очень плохой стиль и если этот администратор начнёт добавлять новые записи в бд, то вёрстка поедет. Сразу же приходит в голову способ переопределить, например, метод get_context_data или get_queryset (в ListView). И там сделать нужную выборку (например, брать только самую первую запись из БД) где собственно администратор и будет править нужную страницу, но всё равно у него остаётся возможность добавлять новые записи в бд, которые просто будут игнорироваться. Придумав ещё пару способов, я отбросил эту затею. Посчитав это плохим тоном, я вспомнил о существование flatpages в Django, но освежив в памяти их функционал, стало ясно, что добавить что-то в их контекст данных невозможно, но можно, например, указать нужный шаблон, который обрабатывается ‘шаблонизатором’, но вот текст, который вы задаёте в поле текста, не обрабатывается ‘шаблонизатором’. Это стоит учитывать, но для меня это не являлось большой проблемой. Но вот что являлось, так это то что это изначально flatpages не расширяемый модуль и прикрутить столь важный ckeditor невозможно. Пришлось думать, как это можно реализовать. Но именно этот функционал мне и нужен был. После вводной части теперь давайте перейдём к более подробному изучению столь хорошему модулю flatpages, но к сожалению, неполноценному, давайте это исправим.
Что такое статичные страницы в джанге – это страницы содержимое которых не генерируется на основе хранящихся в модели данных, а задаётся в виде обычного html кода в соответствующих, заранее заданных, полях.
Основные недостатки:
- Данные не генерируются на основе данных из модели, а следовательно во views.py (далее буду называть контроллером по MVC, а не представление по MVT) мы не можем как то их обработать, дополнить и поместить в контекст данных что либо ещё. А также не можем изначально расширить или изменить модель (models.py).
- Содержимое таких страниц должно представлять собой чисты html код — это главная причина почему из коробки flatpages неполноценны. Так же стоит понимать, что данный код не обрабатывается ‘шаблонизатором’
Основные плюсы:
- Cодержимое flatpages включает в себя интернет-адрес страницы, её заголовок, содержимое и самое главное путь к файлу шаблона. Последний пункт, является ключевым, не смотря на то, что мы не можем передавать данные, но можем сделать нужную страницу используя: базовый шаблон, в котором у нас есть меню настроенное с помощью тегов и переменных джанги, задать места где нужно выводить данные из flatpages, подключать ‘включённые шаблоны’ и тд.
- Одна запись в модели соответствует одной странице на сайте, что является большим плюсом, система полностью настроена нужным образом, что позволяет не писать нам собственный велосипед.
Настройка проекта:
- В settings.py добавляем.
INSTALLED_APPS = [ … 'django.contrib.sites', '''Служит для обеспечения работы нескольких web-сайтов на одной копии Django. В данном проекте, это нужно для корректной работы flatpages.''' 'django.contrib.flatpages', 'flatpage_main' #Потребуется в дальнейшем, имя можете задать любое. … ]
- Добавляем в проект SITE_ID = 1 и в MIDDLEWARE 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
- Делаем синхронизацию с базой данных для добавление нужных таблиц в БД (до 5 шага 'flatpage_main' не писать в INSTALLED_APPS) .
- Заходим на административный сайт проверяем, что всё работает. У вас должна появиться графа ‘простые страницы’.
- Создаём новое приложение — python manage.py startapp 'flatpage_main'.
- Настраиваем ckeditor, на просторах интернета этот материал уже есть, думаю вы сможете его поставить и настроить, а использовать научитесь на этом примере. Если будет нужно, постараюсь написать статейку на хабр, но материал по этому вопросу есть в интернете.
Реализация поставленной задачи:
- Зайдём в модели и добавим следующий код —
from django.db import models from django.contrib.flatpages.models import FlatPage from ckeditor_uploader.fields import RichTextUploadingField class NewFlatpage(models.Model): flatpage = models.OneToOneField(FlatPage) description = RichTextUploadingField(verbose_name = 'Основной текстовый контент страницы',default='') text_block = RichTextUploadingField(verbose_name='Дополнительный блок текста',default='') def __str__(self): return self.flatpage.title class Meta: verbose_name = "Содержание страницы" verbose_name_plural = "Содержание страницы"
Разберёмся что тут написано:
… import FlatPage Добавляем модель, на которую будем ссылаться.
… import RichTextUploadingField спец поле, которое нужно для работы ckeditorВ нашем новом классе ссылаемся через OneToOneField на модель FlatPage, тем самым создаём нужную связь, что позволяет нам увеличить базовую функциональность flatpages, расширяя её.
А далее добавляем любые нужные нам поля, которые хотим, чтобы были в админке и в будущем на самой странице, тем самым можем добавить поле для любого нужного нам контента. - Следующая ступень – это admin.py.
from django.contrib import admin from django.contrib.flatpages.admin import FlatPageAdmin from .models import * class NewFlatpageInline(admin.StackedInline): model = NewFlatpage verbose_name = "Содержание" class FlatPageNewAdmin(FlatPageAdmin): inlines = [NewFlatpageInline] fieldsets = ( (None, {'fields': ('url', 'title', 'sites')}), (('Advanced options'), { 'fields': ('template_name',), }), ) list_display = ('url', 'title') list_filter = ('sites', 'registration_required') search_fields = ('url', 'title') admin.site.unregister(FlatPage) admin.site.register(FlatPage, FlatPageNewAdmin)
Приступим к разбору:
Первый класс NewFlatpageInline и атрибут во втором классе inlines = [NewFlatpageInline], создаёт связь между этими классами, давая возможность на одной странице выводить поля из двух взаимосвязанных таблиц (‘позволяет редактировать связанные объекты на одной странице с родительским объектом’).
fieldsets позволяет задать поля и настройки которые будут выведены в интерфейсе администратора у родительского объекта(flatpage), лишние настройки были убраны.
Последние две строчки — это снятие и регистрация моделей в админке.
- Третий шаг это — urls.py.
url(r'^main/$', views.flatpage, {'url': '/main/'}, name = 'main'),
Добавляем эту строчку если чётко знаем что данная страница точно будет, и мы хотим обрабатывать её в базовом шаблоне, например, в меню, и ссылаться на неё по имени в переменной шаблона, потому что сам шаблон рендерится ‘шаблонизатором’.
Для других страниц вы можете задать —url(r'^/', include('django.contrib.flatpages.urls')),
Тем самым любой другой url адрес будет привязан к url адресу, который вы зададите при создание новой страницы.
- Четвёртый шаг это – template:
{% url 'main' as main %} {% if request.path == main %} <li class="navigation__elem navigation__elem--main navigation__elem--current"> <a href="{% url 'main'%}" class="navigation__href navigation__href--current"><i class="demo-icon icon-main__icon"></i>Главная</a></li> {% else %} <li class="navigation__elem nav-active"><a href="{% url 'main'%}" class="navigation__href"><i class="demo-icon icon-main__icon"></i>Главная</a></li> {% endif %}
Выше написал пример работы меню с flatpages и остальными страницами основанных на контроллерах и шаблонах, думаю код тут предельно ясный и понятный, комментарии излишни. Как видим получить имя url адреса из urls.py, а потом обработать его не составляет никакого труда, тем самым закрывая последнюю сложность в реализации.
Последний штрих, в шаблоне, который мы указали при создании страницы, при создании страницы в админке будет поле в которому указывается шаблон для этой страницы (напр: main.html). В этом шаблоне добавляем нужные нам поля, используя переменные
{{flatpage.meta.description|safe}}
и
{{flatpage.meta.text_block|safe}}
, тем самым позволяя нам вывести нужные данные на итоговую страницу. В этом шаблоне работает всё тоже самое что и в других, например, наследование от base.html.
- Деплойт – при деплойте вы столкнётесь с парой ошибок связанных с работой ‘django.contrib.sites', вы должны войти в шелл (python manage.py shell) и написать следующие команды:
>>> from django.contrib.sites.models import Site >>> site = Site.objects.create(domain='http://ваш_домен.ru/', name=''http://ваш_домен.ru/) >>> site.save()
Это должно помочь решить проблему с эксепшн.
На этом всё! Мы получили крутой и удобный способ добавлять, редактировать и удалять страницы на своём сайте. Научились реализовывать меню, которое позволит переключаться между активными и не активными пунктами и не важно flatpages это или страницы с контроллерами. Так же поработали с ckeditor и разобрались как его настраивать. Разобрали все попутные моменты и сложности. А самое главное соединили это всё в удобный и полезный инструмент, который позволит грамотно администрировать сайт в дальнейшем, а также развивать его, что конечно же удобно для конечного пользователя. Надеюсь данная статья была полезна и многим она поможет в работе! Кому-то сэкономит время. А кто-то напишет в комментариях ниже дополнительные советы и дополнит её!
Автор: Дмитрий Анисов