В данном уроке мы займемся созданием блога на Django, которое позволит пользователям создавать, редактировать и удалять записи. На домашней странице сайта дается список всех записей блога, для каждой отдельной записи также будет предусмотрена детализированная страница. Помимо всего прочего, изучим как применяются CSS стили и рассмотрим принципы работы Django со статическими файлами, такие как css, js, jpg, png.
Содержание статьи
- Начальная настройка блога
- Создание модели Post для работы с записями блога
- Панель администратора для блога
- Настройка URL маршрутов для блога
- Представление BlogListView для отображения записей блога
- Создание шаблона для блога
- Статические файлы в Django
- Создаем отдельную страницу DetailView для статьи
- Пишем TestCase для блога на Django
- Загружаем файлы блога на Github
Начальная настройка блога на Django
Настройка нового Django проекта состоит из следующих этапов:
- создание новой директории для кода на рабочем столе под названием
blog
; - установка Django в новом виртуальном окружении;
- создание нового проекта под названием
blog_project
; - создание нового приложения
blog
; - осуществление миграции для установки базы данных;
- обновление файла настроек
settings.py
.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Не забудьте поставить точку .
в конце команды для создания нового проекта blog_project
.
1 2 3 4 5 6 7 8 9 |
$ cd ~/Desktop $ mkdir blog $ cd blog $ pipenv install django==3.1 $ pipenv shell (blog) $ django-admin startproject blog_project . (blog) $ python manage.py startapp blog (blog) $ python manage.py migrate (blog) $ python manage.py runserver |
Сообщим Django о новом приложении. Для этого откроем в текстовом редакторе файл settings.py
и добавим в конце переменной INSTALLED_APPS
наше приложение:
1 2 3 4 5 6 7 8 9 10 |
# blog_project/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog.apps.BlogConfig', # Добавляем наше приложение ] |
При переходе по адресу http://127.0.0.1:8000/ в браузере откроется следующая страница.
Приветственная страница Django
Итак, установка завершена! Далее займемся созданием модели базы данных для записей блога.
Создание модели Post для работы с записями блога
Каковы характеристики типичного блога? Не будем усложнять задачу, остановимся на том, что у каждой записи должен быть:
- Заголовок;
- Автор;
- Содержимое.
Все это можно превратить в модель базы данных, наполните файл blog/models.py
следующим содержимым:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# blog/models.py from django.db import models class Post(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) body = models.TextField() def __str__(self): return self.title |
В верхней части импортируется класс models
, а затем модель Post
наследует класс models.Model
. Используя функциональные особенности наследника, мы автоматически получаем доступ ко всем данным внутри родительского класса django.db.models.Models. Теперь можно добавлять дополнительные поля и все необходимые нам методы.
Для заголовка title
ставим ограничение на 200 символов, а для содержимого статьи body
используем тип TextField
, что автоматические расширяется, подстраиваясь под длину текста пользователя. В Django доступно множество типов полей, с полным списком которых можете ознакомиться здесь.
Для поля автора author
используем ForeignKey, что допускает взаимодействия по схеме многие-к-одному. Это значит, что любой пользователь может быть автором множества записей, но не иначе. Отталкиваемся от встроенной в Django модели User
, предусмотренной для аутентификации. Для взаимоотношений многие-к-одному вроде ForeignKey требуется уточнить опцию on_delete чтобы знать как себя вести при удалении записи из одной таблицы которая связана с данными из других таблиц.
Теперь, когда новая модель базы данных создана, нужно создать новую запись миграции и перенести изменение в нашу базу данных. Остановите веб-сервер через комбинацию CTRL+C
. Данный двухэтапный процесс можно выполнить используя данные команды:
1 2 |
(blog) $ python manage.py makemigrations blog (blog) $ python manage.py migrate blog |
База данных настроена! Что дальше?
Панель администратора для блога на Django
Сейчас нам нужно получить доступ к данным. Прежде всего нам нужно создать аккаунт администратора, введя указанные ниже команды и следуя дальнейшим указаниям по настройке электронной почты и пароля. Обратите внимание, что при вводе пароля он не будет отображаться на экране, это делается в целях безопасности.
1 2 3 4 5 6 |
(blog) $ python manage.py createsuperuser Username (leave blank to use 'wsv'): wsv Email: Password: Password (again): Superuser created successfully. |
Теперь вновь запустим веб-сервер при помощи команды python manage.py runserver
и откроем админку, перейдя по адресу http://127.0.0.1:8000/admin/. При входе укажите данные вашего аккаунта суперпользователя.
И где же новая модель Post
для работы с записями блога?
Домашняя страница панели администратора
Мы забыли обновить файл blog/admin.py
. Давайте сделаем это сейчас.
1 2 3 4 5 |
# blog/admin.py from django.contrib import admin from .models import Post admin.site.register(Post) |
Обновления в админке можно увидеть после перезагрузки страницы.
Домашняя страница панели администратора
Добавим две записи, чтобы появились образцы для дальнейшей работы с блогом. Для создания нового поста нажмите кнопку + Add
рядом с Posts
. Не забудьте указать автора каждой записи, так как по умолчанию все поля в модели являются обязательными. Если попытаться опубликовать что-то без указания автора, выйдет ошибка. При желании поменять данное условие, можно добавить специальные опции для полей рассматриваемой модели, сделав поле необязательным или же заполнить его значением по умолчанию.
Создание первой записи в блоге
Создание второй записи в блоге
Список записей блога в админке
С созданием модели базы данных все работы завершены. Теперь нужно создать все необходимые представления (Views), настройка URL маршрутов и создание файлов шаблона для отображения информации нашего блога на Django.
Настройка URL маршрутов для блога на Django
Для отображения записей блога на главной странице, настроим конфигурацию файла blog_project/urls.py
, а также файла blog/urls.py
.
В командной строке выключаем запущенный веб-сервер через комбинацию CTRL+C
и создаем новый файл urls.py
внутри приложения blog
:
1 |
(blog) $ touch blog/urls.py |
Теперь заполняем его содержимое используя ниже предоставленный код.
1 2 3 4 5 6 7 8 |
# blog/urls.py from django.urls import path from .views import BlogListView urlpatterns = [ path('', BlogListView.as_view(), name='home'), ] |
В верхней части импортируем будущие представления. Пустые одинарные кавычки ''
говорят Django принимать все значения и создать именованный URL home
, к которому можно будет позже отсылаться из представлений (views). Хотя сейчас создавать именованный URL не обязательно, будет лучше, если вы выработаете у себя данную привычку. С ростом количества URL маршрутов это поможет сохранять проект более организованным и упорядоченным.
Мы также должны обновить файл blog_project/urls.py
, чтобы в дальнейшем все запросы направлялись напрямую к приложению blog
.
1 2 3 4 5 6 7 8 |
# blog_project/urls.py from django.contrib import admin from django.urls import path, include # новое изменение urlpatterns = [ path('admin/', admin.site.urls), path('', include('blog.urls')), # новое изменение ] |
Мы добавили функцию include()
на второй строке и URL паттерн, используя пустую строку регулярного выражения ''
, указывая, что URL запросы должны быть перенаправлены к обработчику URL маршрутов от приложения blog
для последующих инструкций.
Представления — BlogListView для отображения записей блога
Сейчас мы будем использовать классовые представления, однако при создании приложения для блога также можно применить функционально-ориентированный подход. При желании более подробно изучить второй вариант можете ознакомиться с уроками от Django Girls Tutorial. Отличная вещь!
В файле views.py
добавьте указанный ниже код, который нужен для отображения содержимого из модели Post
при использовании ListView
от Django.
1 2 3 4 5 6 7 8 9 |
# blog/views.py from django.views.generic import ListView from .models import Post class BlogListView(ListView): model = Post template_name = 'home.html' |
Первые две строчки импортируют ListView и модель нашей базы данных Post
. Мы наследуем класс ListView
и добавляем отсылку к модели и указываем на HTML файл шаблона. Это позволяет сэкономить на коде, избавляя от необходимости написания дополнительного кода с нуля.
Создание шаблона для блога на Django
Настройка URL маршрутов и представления готовы, остались только разобраться с шаблонами. Здесь можно унаследовать свойства других шаблонов, чтобы сохранить текущий код чистым. Начнем с файла base.html
и файла home.html
, который наследует структуру от него. Затем создадим шаблоны для создания и редактирования записей блога, который также могут наследовать основную структуру от base.html
.
Создадим новую директорию templates
и два HTML файла шаблонов внутри нее.
1 2 3 |
(blog) $ mkdir templates (blog) $ touch templates/base.html (blog) $ touch templates/home.html |
Теперь обновляем файл settings.py
, для того чтобы Django понимал в какие еще директории искать наши файлы шаблона.
1 2 3 4 5 6 7 8 |
# blog_project/settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], # новое ... }, ] |
Затем обновляем шаблон base.html
следующим образом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- templates/base.html --> <html> <head> <title>Django blog</title> </head> <body> <header> <h1><a href="{% url 'home' %}">Django blog</a></h1> </header> <div> {% block content %} {% endblock content %} </div> </body> </html> |
Обратите внимание, что место между {% block content %}
и {% endblock content %}
будет заполнено содержимым из других файлов, например home.html
.
1 2 3 4 5 6 7 8 9 10 11 |
<!-- templates/home.html --> {% extends 'base.html' %} {% block content %} {% for post in object_list %} <div class="post-entry"> <h2><a href="">{{ post.title }}</a></h2> <p>{{ post.body }}</p> </div> {% endfor %} {% endblock content %} |
В верхней части файла можно заметить, что данный шаблон расширяет базовый base.html
и затем заполняет блок content
данными. Используя специальный язык шаблонов мы применяем цикл for для отображения всех записей. Обратите внимание, что переменная object_list
досталось нам от класса ListView
и содержит все объекты нашего представления.
Если опять запустить веб-сервер через python manage.py runserver
и затем перезагрузить страницу http://127.0.0.1:8000/, можно убедиться, что все работает.
Домашняя страница блога с двумя записями
Выглядит, конечно, не очень. Давайте исправим ситуацию при помощи добавления CSS стилей.
Подключение статических файлов в Django
Сейчас нам потребуется использовать CSS стили, что по сути являются статическими файлами, так как в отличие от динамического содержимого базы данных, они не меняются. К счастью, в Django можно напрямую добавить статические файлы вроде CSS, JavaScript или изображения.
При создании сайтов для улучшения производительности файлы обычно хранят на CDN сервере. CDN сервер предоставляет возможность пользователю получить статические файлы быстрее в зависимости от его страны. Однако, для нашего проекта статические файлы мы будет хранить на том же сервере что и сам Django.
Закрываем локальный веб-сервер через CTRL+C
. Создаем новую директорию под названием static
.
1 |
(blog) $ mkdir static |
Как и в случае с директорией templates
, сейчас нам понадобится обновить файл settings.py
для уведомления Django о месте поиска статических файлов. В settings.py
потребуется изменить переменную STATICFILES_DIRS
. Внесем изменения в нижнюю часть файла под переменной STATIC_URL
.
1 2 |
# blog_project/settings.py STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] |
Теперь внутри static
создаем директорию css
и добавляем новый файл base.css
в нее.
1 2 |
(blog) $ mkdir static/css (blog) $ touch static/css/base.css |
Что добавить в файл? Может, поменяем цвет заголовка на красный?
1 2 3 4 |
/* static/css/base.css */ header h1 a { color: red; } |
В конце нужно включить статические файлы в наши шаблоны, добавив {% load static %}
в верхнюю часть base.html
. Так как остальные шаблоны унаследуют каркас от base.html
, то редактировать надо будет только этот файл. Добавляем новую строку в шаблоне, она напрямую отсылает к новому файлу base.css
.
1 2 3 4 5 6 7 8 |
<!-- templates/base.html --> {% load static %} <html> <head> <title>Django blog</title> <link href="{% static 'css/base.css' %}" rel="stylesheet"> </head> ... |
Ну вот, мучениям пришел конец. Теперь можно добавить статические файлы в директорию static
, после чего они автоматически появятся во всех шаблонах.
Заново запускаем веб-сервер через команду python manage.py runserver
и смотрим на изменения в обновленной домашней странице http://127.0.0.1:8000/.
Домашняя страница блога с красным заголовком
Давайте изменим еще что-то. Как насчет нового шрифта? Давайте просто вставим следующий код бесплатного шрифта Source Sans Pro от Google между тегами <head></head>
.
1 2 3 4 5 6 7 8 9 |
<!-- templates/base.html --> {% load static %} <html> <head> <title>Django blog</title> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400" rel="stylesheet"> <link href="{% static 'css/base.css' %}" rel="stylesheet"> </head> ... |
Затем мы должны обновить файл css
, скопировав и вставив следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
/* static/css/base.css */ body { font-family: 'Source Sans Pro', sans-serif; font-size: 18px; } header { border-bottom: 1px solid #999; margin-bottom: 2rem; display: flex; } header h1 a { color: red; text-decoration: none; } .nav-left { margin-right: auto; } .nav-right { display: flex; padding-top: 2rem; } .post-entry { margin-bottom: 2rem; } .post-entry h2 { margin: 0.5rem 0; } .post-entry h2 a, .post-entry h2 a:visited { color: blue; text-decoration: none; } .post-entry p { margin: 0; font-weight: 400; } .post-entry h2 a:hover { color: red; } |
Перезагрузив домашнюю страницу http://127.0.0.1:8000/, вы увидите следующее.
Домашняя страница блога с CSS
Создаем отдельную страницу DetailView для статьи
Теперь можно добавить функционал для индивидуальных страниц блога. Как это сделать? Понадобится новое представление (view), настройка url маршрута и HTML шаблон. Надеюсь, вы уже уловили суть и правила разработки c Django.
Начнем с представления. Для простоты можем использовать общий класс DetailView. В верхней части файла импортируем DetailView
и затем создаем новое представление под названием BlogDetailView
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# blog/views.py from django.views.generic import ListView, DetailView # новое from .models import Post class BlogListView(ListView): model = Post template_name = 'home.html' class BlogDetailView(DetailView): # новое model = Post template_name = 'post_detail.html' |
В новом представлении мы указываем на используемую модель Post
, а также на файл HTML шаблона, с которым нужно ее ассоциировать — post_detail.html
. По умолчанию DetailView
предоставляет объект содержимого модели, которого мы можем использовать в шаблоне для получения данных о статье, название переменной зависит от названия модели, в нашем случае это post
. Кроме того, класс DetailView
запрашивает первичный ключ (ID), либо slug
в качестве идентификатора требуемой нами записи из БД. Скоро поговорим и об этом.
Закрываем локальный веб-сервер через CTRL+C
и создаем новый HTML шаблон для просмотра записей на отдельной странице:
1 |
(blog) $ touch templates/post_detail.html |
Далее набираем следующий код:
1 2 3 4 5 6 7 8 9 10 |
<!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> {% endblock content %} |
В верхней части уточняется, что данный шаблон наследует структуру от base.html
. Затем показывается заголовок и содержимое статьи из объекта post
, данную переменную предоставляет общий класс представления DetailView
в зависимости от названия нашей модели.
В начале изучения Django мне казалось, что именование объектов содержимого в представлениях сильно запутывает. Из-за того, что объект содержимого из DetailView
является моделью с названием post
или object
, можно было бы обновить шаблон используя переменную object
вместо post
, при этом он бы работал как и раньше.
1 2 3 4 5 6 7 8 9 |
<!-- templates/post_detail.html --> {% extends 'base.html' %} {% block content %} <div class="post-entry"> <h2>{{ object.title }}</h2> <p>{{ object.body }}</p> </div> {% endblock content %} |
Если использование post
или object
кажется вам запутанным, можно напрямую именовать объект содержимого в представлении, задействовав context_object_name.
«Магическое» именование объекта содержимого становится своего рода платой за простоту использования общих представлений (generic views). Они являются отличным вариантом, если известен их функционал, поэтому лучше заранее ознакомиться с особенностями настройки в официальной документации.
Что дальше? Добавим новый URL маршрут для работы с отдельными статьями в зависимости от их ID.
1 2 3 4 5 6 7 8 9 |
# blog/urls.py from django.urls import path from .views import BlogListView, BlogDetailView # новое изменение urlpatterns = [ path('post/<int:pk>/', BlogDetailView.as_view(), name='post_detail'), # новое изменение path('', BlogListView.as_view(), name='home'), ] |
Все записи блога будут начинаться с post/
. Рассмотрим первичный ключ для записи, который будет представлен целым числом <int:pk>
. Что такое первичный ключ, спросите?
Django автоматически добавляет автоинкрементный первичный ключ к модели базы данных. В то время, как мы просто обозначили поля title
, author
и body
в модели Post
, Django автоматически добавил еще одно поле под названием id
, которое и является первичным ключом. Получить к нему доступ можно через id
или pk
.
У первой записи «Hello, Worl» pk
будет 1. У второй записи ID = 2. И так далее. Следовательно, если перейти на индивидуальную страницу первого поста, структура его url будет post/1
.
Зачастую новички испытывают сложности, пытаясь разобраться с принципом работы первичных ключей. Будет не лишним подробнее остановиться на предыдущих двух параграфах, ну, а с практикой данный аспект разработки станет привычным.
Теперь, если запустить веб-сервер через команду python manage.py runserver
и перейти по адресу http://127.0.0.1:8000/post/1/, можно увидеть отдельную индивидуальную страницу для первой записи блога.
Детали первой записи блога
Ура! Следовательно, перейдя по адресу http://127.0.0.1:8000/post/2/, вы увидите индивидуальную страницу второй записи. Для простоты обновим ссылку на домашней страницы, чтобы получить доступ ко всем записям оттуда. На данный момент ссылка <a href="">
в файле home.html
пуста. Обновим ее как <a href="{% url 'post_detail' post.pk %}">
.
1 2 3 4 5 6 7 8 9 10 11 |
<!-- templates/home.html --> {% extends 'base.html' %} {% block content %} {% for post in object_list %} <div class="post-entry"> <h2><a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a></h2> <p>{{ post.body }}</p> </div> {% endfor %} {% endblock content %} |
Начнем с указания шаблону о необходимости отсылки к настройкам из URLConf, для чего используем код {% url ... %}
. Какой URL? Тот самый под названием post_detail
, что является именем, которое мы недавно дали BlogDetailView
в переменной URLConf. Если посмотреть на post_detail
в URLConf, то увидим, что он ждет передачи аргумента pk
, который представляет первичный ключ записи блога. К счастью, Django уже создал и включил поле pk
для объекта post
. Мы передаем его в URLConf при добавлении в шаблон как post.pk
.
Перезагрузите главную страницу http://127.0.0.1:8000/ и кликните по каждому заголовку записей блога, чтобы убедиться в том, что все работает должным образом.
Пишем TestCase для блога на Django
Теперь нужно проверить нашу модель и представления (views). Необходимо убедиться, что модель Post
работает как надо. Также протестируем общие классы представлений ListView
и DetailView
.
Образец тестов в blog/tests.py
будет выглядеть следующим образом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# blog/tests.py from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse from .models import Post class BlogTests(TestCase): def setUp(self): self.user = get_user_model().objects.create_user( username='testuser', email='test@email.com', password='secret' ) self.post = Post.objects.create( title='A good title', body='Nice body content', author=self.user, ) def test_string_representation(self): post = Post(title='A sample title') self.assertEqual(str(post), post.title) def test_post_content(self): self.assertEqual(f'{self.post.title}', 'A good title') self.assertEqual(f'{self.post.author}', 'testuser') self.assertEqual(f'{self.post.body}', 'Nice body content') def test_post_list_view(self): response = self.client.get(reverse('home')) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Nice body content') self.assertTemplateUsed(response, 'home.html') def test_post_detail_view(self): response = self.client.get('/post/1/') no_response = self.client.get('/post/100000/') self.assertEqual(response.status_code, 200) self.assertEqual(no_response.status_code, 404) self.assertContains(response, 'A good title') self.assertTemplateUsed(response, 'post_detail.html') |
В тестах появилось много нового, поэтому разберем все аспекты в подробностях. В верхней части импортируется get_user_model для отсылки на активных User
и TestCase
, которых мы видели ранее.
В метод setUp
добавляется образец записи блога для тестирования и дальнейшего подтверждения, что строки и содержимое работают верно. Затем используется test_post_list_view
, который подтверждает, что домашняя страница возвращает HTTP код состояния 200, содержит правильный текст в теге body
и использует правильный шаблон home.html
. В конечном итоге test_post_detail_view
проверяет, работает ли индивидуальная страница записи правильно, а поврежденная страница возвращает ошибку 404. В тестах полезно проводить проверку как на наличие определенных данных, так и на отсутствие разнообразных ошибок.
Можете выполнить тесты прямо сейчас. Все должно сработать.
1 |
(blog) $ python manage.py test |
Загружаем файлы блога на Github
Пришло время для коммита изменений на github. Начинаем с инициализации нашей директории.
1 |
(blog) $ git init |
Затем осматриваем все изменения и добавления в коде, через команду status
. Добавляем все новые файлы. После этого делаем первый коммит.
1 2 3 |
(blog) $ git status (blog) $ git add -A (blog) $ git commit -m 'initial commit' |
Заключение
Теперь мы знаем как создать блога на Django с нуля. При помощи использования панели администратора Django нам под силу создать, отредактировать и удалить запись. Мы также впервые использовали DetailView
для создания индивидуального представления для каждой записи блога.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»