У 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
Розділ
Модель розділу складається з:
- Найменування - 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, і розібратися де і що буде дуже проблематично при великій кількості розділів.
Стаття
Модель складається з наступних полів:
- Найменування статті
- Розділ - це зовнішній ключ на таблицю Розділів, що визначає приналежності статті до певного розділу
- Автор – це зовнішній ключ на таблицю користувачів, з яких вибиратиме автор статті
- Дата - дата та час публікації
- Контент - текстове поле аналогічне опису, як у моделі розділів
- Статус – припускаю кілька статусів для статей, на даний момент використовується лише два:
- Чернетка - значення 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: в джанго - недавно, неделю.
Я дура. Разобралась. Но, остаюсь вашим читателем)