В Django используется модульная система приложения, когда одно приложение состоит из нескольких приложений, отвечающих каждое за свой функционал. Как Вы успели заметить, на момент написания статьи, на сайте присутствует раздел "База Знаний", в котором присутствует несколько разделов, по которым уже разделены статьи.
При работе с предыдущим сайтом на Wordpress, бестолково тратилось время на добавление новых статей в список уроков по Qt. На данном же сайте страница является автоматически генерируемой и при сохранении новой статьи со статусом опубликовано, данная статья автоматически попадает в список соответствующего раздела.
Предлагаю разобраться, как это реализуется в минимальном варианте на примере EVILEG COM.
Структура проекта
Конкретно в данном сайте используется два приложения в проекте:
- home - приложение, которое отвечает за основную страницу index, страницы ошибок и за базовые шаблоны
- knowledge - приложение, которое отвечает именно за статьи и разделы статей
Если посмотреть детально, то структура проекта будет следующая:
mysite/ manage.py mysite/ __init__.py settings.py urls.py wsgi.py home/ __init__.py admin.py apps.py models.py tests.py urls.py views.py templates/ home/ base.html knowledge/ __init__.py admin.py apps.py models.py tests.py urls.py views.py templates/ knowledge/ article.html index.html section.html
Пара слов о создании проекта и приложений в проекте. Использовалась среда разработки Pycharm, которая подготавливает проект с включёнными основными модулями, в том числе и админкой, поэтому буду рассказывать о том, что не было включено по умолчанию.
Для создания приложений необходимо воспользоваться следующими командами:
python manage.py startapp home python manage.py startapp knowledge
Настройка settings.py
Для того, чтобы были доступны модули приложений, необходимо подключить их в конфигурационном файле проекта.
INSTALLED_APPS = [ 'home.apps.HomeConfig', 'accounts.apps.AccountsConfig', ... ]
Модели
В проекте используется два вида моделей:
- Section - разделы, которые объединяют все статьи по одному общему признаку, принадлежности к определённой тематике
- Article - сами статьи
Модели описываются в файле models.py
Для разработки этих моделей данных было применено наследование от встроенного в Django класса Model. А также использовалась модель пользователя User, поскольку в статьях указывается автор из числа зарегистрированных на сайте пользователей.
from django.db import models from django.contrib.auth.models import User
Section
Модель раздела состоит из:
- Наименование - title раздела
- URL - используется для формирование часть адреса на сайте, то есть хранятся не абсолютные пути, а часть пути к разделу
- Описание - которое выводится в начале страницы перед списком статей
Посмотрим на реализацию модели в коде:
class Section(models.Model): class Meta: db_table = "section" section_title = models.CharField(max_length=200) section_url = models.CharField(max_length=50) section_description = TextField() def __str__(self): return self.section_title
Класс Meta позволяет переопределить некоторые параметры модели, которые имеют непосредственное отношение к таблицам в базе данных. В данном случае определяется, как будет называться таблица.
В модели имеется три поля, два из них имеют тип CharField, который подразумевает, небольшие по размеру записи с ограничением числа вводимых символов.
Тип TextField, подразумевает, что будет вводиться достаточно большой произвольный массив символов, максимальный размер которого заранее неизвестен.
Переопределение метода str , а это именно переопределение, а не новый метод, позволяет возвратить название статьи, которое будет отображаться в админке. Дело в том, что если не переопределить этот метод, то все записи в адмике будут выглядеть, как Section Object, и разобраться где и что находится будет весьма проблематично при большом количестве разделов.
Article
Модель состоит из следующих полей:
- Наименование статьи
- Раздел - это внешний ключ на таблицу Разделов, определяет принадлежности статьи к определённому разделу
- Автор - это внешний ключ на таблицу пользователей, из которых будет выбирать автор статьи
- Дата - дата и время публикации
- Контент - текстовое поле аналогично описанию, как в модели разделов
-
Статус - предполагаю несколько статусов для статей, на данный момент используется лишь два:
- Черновик - значение 0
- Опубликовано - значение 1
class Article(models.Model): class Meta: db_table = "article" article_title = models.CharField(max_length=200) article_section = models.ForeignKey(Section) article_author = models.ForeignKey(User) article_date = models.DateTimeField('Дата публикации') article_content = TextField() article_status = models.IntegerField() def __str__(self): return self.article_title
Регистрация моделей в админке
Для того, чтобы была возможность редактировать статьи и разделы из админки, необходимо зарегистрировать их в модуле админки. Делается это в файле admin.py соответствующего приложения.
from django.contrib import admin from .models import Section, Article admin.site.register(Section) admin.site.register(Article)
Миграция
Написали модели, уверены что модели имеют все нужные поля? Тогда нужно сделать миграции баз данных. Для этого выполняем следующие команды.
python manage.py makemigrations python manage.py migrate
Примечание
Primary Key в общем случае подставляется автоматически и является автоинкрементируемым, поэтому мы его не указываем.
Шаблоны
Шаблоны в Django просто замечательная вещь, особенно если учитывать то, что они могут наследоваться и определённые блоки будут переопределяться.
В проекте создан один базовый шаблон base.html в приложении home , от которого наследуются все остальные страницы. Условная структура шаблона следующая:
{% block head %} {% endblock %} {% block content %} {% block page %} {% endblock page %} {% block sidebar %} {% endblock %} {% endblock content %} {% block footer %} {% endblock %}
В данном случае есть пять блоков, которые можно будет переопределить при наследовании шаблонов в других страницах.
index.html
Это шаблон основной страницы приложения, где выводится список всех разделов. С помощью оператора extends , мы указываем, что наследуемся от базового шаблона приложения home. А продублировав блок page, из базового шаблона и прописав в него содержимое мы переопределяем данный блок базового шаблона внося нужную нам информацию для конкретной страницы.
Как Вы могли заметить, в языке шаблонов Django используются конструкции if, for, url. Но откуда взялись переменные section_list и section? - section_list передаётся в качестве контекстного значения в шаблон при подготовке данных в представлении ( view ). Проверив, что список разделов существует, мы проходим по все элементам списка подставляя необходимые значения в шаблон. Поскольку section соответствует модели Section, то соответствующие названия полей модели подхватываются автоматически.
Что касается url , то данный оператор указывает, что необходимо взять шаблон адреса из приложения knowledge с именем section, а также мы знаем, что у этого шаблона адреса имеется одна переменная, которой мы присваиваем url объекта section. Данные шаблоны описываются в файле urls.py. Мы их рассмотрим чуть позже.
{% extends 'home/base.html' %} {% block page %} <article> <h1>Разделы</h1> {% if section_list %} <ul> {% for section in section_list %} <li> <a href="{% url 'knowledge:section' section.section_url %}">{{ section.section_title }}</a> </li> {% endfor %} </ul> {% endif %} </article> {% endblock %}
section.html
В шаблоне разделов происходит формирование списка статей, которые соответствуют данному раздлу. В качестве контекста в шаблон передаётся переменная section, которая содержит информацию об объекте раздела. Мы подставляем название раздела и его описание. Заметьте, что описание подставляется со специальным аргументом safe, который указывает на сохранение форматирования. Это необходимо сделать, чтобы сохранить html разметку, иначе пользователь увидит вместо красиво оформленного описания код html вёрстки.
Также здесь приведён вариант с забором информации о всех статьях, которые относятся к данном разделу, а сделано это с помощью вызова section.article_set.all, то есть мы забираем все статьи, у которых есть внешний ключ на данный раздел. Ну и сортируем статьи с помощью dictsort с указанием параметра, по которому происходит сортировка.
А блоке if проверяется статус статьи, то есть те статьи, которые не опубликованы, отображаться не будут.
Что касается url, то опять используется шаблон адреса, но уже с двумя аргументами.
{% extends 'home/base.html' %} {% block page %} <article> <h1>{{ section.section_title }}</h1> <p>{{ section.section_description|safe }}</p> <h2>Статьи</h2> <ul> {% for article in section.article_set.all|dictsort:'article_title' %} {% if article.article_status %} <li> <a href="{% url 'knowledge:article' section.section_url article.id %}"> {{ article.article_title }} </a> </li> {% endif %} {% endfor %} </ul> </article> {% endblock %}
article.html
Как видите, шаблон для статьи достаточно скуден, чтобы что-то добавлять про него.
{% extends 'home/base.html' %} {% block page %} <article> <h1>{{ article.article_title }}</h1> <p>{{ article.article_content|safe }}</p> </article> {% endblock %}
Примечание
Если Вы посмотрите на структуру проекта, до увидите, что приложения содержат в папках templates ещё папки, которые имеют такое же наименование что и приложения, а уже в них находятся шаблоны страниц. Делается это для того, чтобы точно определять, какой шаблон будет использоваться. Django ищет шаблоны по всем папкам и выбирает первый шаблон, который соответствует наименованию. И если названия пересекаются, например index.html, то может быть выбран неправильный шаблон. Поэтому используется пространство имён, что точно определить нужный шаблон.
Представление
В данном случае работа с представления достаточно ограниченная, поэтому приведу сразу полный листинг файла views.py.
Мы наследуемся от класса View и переопределяем метод get, который отвечает за выполнение GET запроса, то есть запроса страницы пользователем. Также импортируем методы render_to_response, который будет обрабатывать шаблон в нужном нам контексте, и get_object_or_404 , который автоматически вернёт ошибку 404, если объект не будет найден, по соответствующей переменной.
В EKnowledgeIndex забираются абсолютно все разделы с сортировкой по наименованию и помещаются в контекст под именем section_list, Вы помните, что данная переменная использовалась в шаблоне index.html.
В ESectionView забирается уже конкретный Раздел, по аргументу, который передаётся в запрашиваемом url. kwargs как раз отвечает за переменные, которые вычленяются из url по шаблону, заданном в файле urls.py.
В EArticleView всё гораздо интереснее. Дело в том, что в шаблоне url используется две переменных, но для получения статьи достаточно сделать запрос по id, то есть Primary Key , который передаётся в url.
from django.views import View from django.shortcuts import render_to_response, get_object_or_404 from .models import * class EKnowledgeIndex(View): template_name = 'knowledge/index.html' def get(self, request, *args, **kwargs): context = {} context['section_list'] = Section.objects.all().order_by('section_title') return render_to_response(template_name=self.template_name, context=context) class ESectionView(View): template_name = 'knowledge/section.html' def get(self, request, *args, **kwargs): context = {} section = get_object_or_404(Section, section_url=self.kwargs['section']) context['section'] = section return render_to_response(template_name=self.template_name, context=context) class EArticleView(View): template_name = 'knowledge/article.html' def get(self, request, *args, **kwargs): context = {} article = get_object_or_404(Article, id=self.kwargs['article_id']) context['article'] = article return render_to_response(template_name=self.template_name, context=context)
URL шаблоны
Хорошо, всё что нужно написано, но осталось совсем немного, чтобы всё заработало, а именно настроить шаблоны url, по которым будут обрабатываться запросы.
Особенностью Django является то, что данные шаблоны представляют собой проверки по регулярным выражениям, а не выводятся из контролеров моделей. Лично для меня данный подход оказался понятным и прозрачным, и он мне нравится. Так давайте посмотрим, как оживить наши странички.
mysite/mysite/urls.py
Для начала необходимо задать шаблоны в файле mysite/mysite/urls.py , который будет определять, в какое приложение направить запрос. Здесь Вы видите, что есть направление в приложение home, там отображается главная страница сайта, но я не буду углубляться в описание данной части, поскольку о ней мы не говорим в данной статье. Также имеется направление в админку сайта и в приложение knowledge, которое отвечает как раз за разделы и статьи.
Через include, подключаются файлы urls.py в приложениях home и knowledge.
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^', include('home.urls')), url(r'^knowledge/', include('knowledge.urls')), url(r'^admin/', admin.site.urls), ]
mysite/knowledge/urls.py
app_name отвечает за пространство имён в шаблонах. Помните? Чуть выше в шаблоне была запись 'knowledge:section'.
Ну а регулярочки определяют, что и куда будет направляться. Что касается записей
и
Ну а name указывает наименование URL шаблона, то есть вторая часть в 'knowledge:section'.
Ну и последнее, что хотелось бы отметить, так это символ ^ , который присутствует в шаблоне. С помощью него отбрасывается уже распознанная часть шаблона. То есть в этих url также присутствует в самом начале knowledge/ , которая была распознана ранее в mysite/mysite/urls.py.
from django.conf.urls import url from . import views app_name = 'knowledge' urlpatterns = [ url(r'^$', views.EKnowledgeIndex.as_view(), name='index'), url(r'^(?P<section>[\w]+)/$', views.ESectionView.as_view(), name='section'), url(r'^(?P<section>[\w]+)/(?P<article_id>[0-9]+)/$', views.EArticleView.as_view(), name='article') ]
Для Django рекомендую VDS-сервера хостера Timeweb .
Спасибо за ваши статьи. Очень понятно и качественно пишите. Есть пара моментов в данной статье, которые мне кажутся спорными. Например, именование полей внутри моделей(article_title, article_date вместо просто title и date). Думаю добавлять в качестве префикса имя модели несколько избыточно - поля без модели не используются и без всяких префиксов всегда понятно откуда они. Второй момент это использование в шаблоне для ссылки на конкретный объект тег url. Гораздо удобнее реализовать для модели метод get_absolute_url и использовать его. Это даст возможность полностью менять схему урлов без поиска и переписывания всех шаблонов. К тому же этот метод используется стандартной админкой для формирования ссылок "посмотреть на сайте". Ну и ваши представления легче было наследовать от соответсвующих generic views - DetailView, ListView.
Хотя может это всё допущения чтобы не усложнять материал.
Спасибо за отзыв.
Хотелось спросить, можно ли создать многоуровневые url из свойств модели? Я вот с часик лежал, искал как это сделать не нашел, вопрос на хабре оставил, никто не ответил. Оригинальная документация говорит только об одном слаге, а все примеры заканчиваются на том, что есть домен и к нему slug. Я просто хотел прикрепить запись к определенной категории, и еще одной субкатегории, т.е типо домен/категория/подкатегории/запись
я сделал подобное, но блин, доступ к записи осуществляется даже с др.категории и вообще с любимым текстом, т.е ссылка каноническая, могу перейти и по test/test/запись и по nowork/test/запись, передавал в модель свойства вот так: kwargs=("url":self.url, "cat":self.cat.url, "sub":self.sub.url)
Если я правильно понимаю ваш вопрос, то нечто подобное у меня реализовано для страниц правил пользования сайтом, вот например страница тем форума
Но там я формирую полный путь, укладываю его в slug, а вот url шаблон у меня сделан так
Но у меня реализация там сделана за счёт того, что я задаю частичный url, который собирает все parent страницы формирует уже итоговый url и кладёт его куда надо.
Ещё делал раньше как вы делаете, но я так полагаю, что у вас код смотреть нужно, наверняка у вас неправильно написан код где-то:
К слову говоря, если делали по этой статье, то там тоже в EArticleView нужно ещё добавить проверку на slug раздела, и если не совпадаетЮ, то выкинуть ошибку 404. Так что да, косяк в статье.
Я делаю вот так у себя в модели для получения ссылки:
Там где доработать это я закомментил, ибо получалось доступность по всем категориям, потом выяснил, что так вообще по всей ссылки.
models.py
Вьюшка:
URLs
Запись доступна по articles/blog/название статьи, но таким же образом она доступна к неподвязанной категории, т.е articles/files/название статьи
Также можно любой текст можно подставить, articles/131411414/название статьи и будет тоже доступно.
Ну я так и думал. Здесь нужно или обычный View использовать, или формировать slug так, чтобы он содержал slug категории, и как у меня использовать path, но тут тогда будет проблем в том, что тогда url категории скорее всего перестанет работать.
Так что или берите обычный View, добавляйте туда метод get, и проверяйте как slug статьи, так и slug категории, которая будет привязана к статье, либо переопределяйте метод get_object у DetailView, но там также нужно будет ковырять как slug статьи, так и slug категории
Хорошо, посмотрю что получится с простым View
Отлично. Спасибо. Вопрос, как мне теперь передать на индексную страницу(ту которая по умолчанию) содержание только статей, без секций? И вообще передать на дефолтную страницу? Что то не получается. PS: в джанго - недавно, неделю.
Я дура. Разобралась. Но, остаюсь вашим читателем)