Пример приложения, описанный в данной статье, лучший для старта изучения фреймворка Django. Этот пример — это небольшое приложение для голосования (двухстраничный сайт). Первая страница сайта — публичная, с опросами для голосования. Вторая страница — приватная, только для администратора, для добавления, изменения и удаления опросов.
Установка Django на Linux
Устанавливаем фреймворк Django на Linux-сервере (ОС Debian):
apt install python3-django
Пример на Django
Создание проекта Django
В текущем каталоге создаем каталог proj1 для нашего проекта:
django-admin startproject proj1
cd proj1/
Структура созданного каталога proj1 следующая:

Запуск веб-сервера разработки
Запускаем веб-сервер разработки Django:
python3 manage.py runserver
По умолчанию веб-сервер разработки Django запускается на локальном IP адресе 127.0.0.1 и на порту 8000:

Примечание: версии Django и Python у меня тут довольно древние, для реальной разработки на Django рекомендуется обновиться до последних версий.
Запуск веб-сервера разработки с заданными IP и портом
Поскольку мне нужен доступ к веб-серверу разработки Django с другого компьютера сети, то я перезапущу веб-сервер разработки Django на IP адресе 192.168.1.38 и на порту 8000:
python3 manage.py runserver 192.168.1.38:8000

И пропишу в iptables разрешение на доступ по порту 8000:
iptables -A INPUT -p tcp -m tcp --dport 8000 -j ACCEPT
iptables-save > /etc/iptables.up.rules
Проверка соединения
Для проверки соединения с веб-сервером разработки открываем браузер и в адресной строке вводим:
http://192.168.1.38:8000/
Примечание: если веб-сервер разработки Django был запущен, как описано в разделе Запуск веб-сервера разработки, то IP адрес 192.168.1.38 следует заменить на 127.0.0.1, как в данном разделе статьи, так и во всех разделах ниже.
Если возникает ошибка DisallowedHost at / Invalid HTTP_HOST header. You may need to add to ALLOWED_HOSTS

то редактируем следующую строку в файле proj1/proj1/settings.py:
ALLOWED_HOSTS = ['192.168.1.38']
и перезапускаем веб-сервера разработки Django.
В случае успеха браузер отобразит сообщение It worked!

Ура! Мы готовы к разработке на фреймворке Django. Веб-сервер разработки Django не требует перезагрузки при внесении изменений в код существующих в проекте файлов Python. Но при добавлении новых файлов Python в проект, перезагрузка веб-сервера разработки все-таки понадобится.
Создание приложения
Создадим наше приложение опрос с голосованием app1 командой:
python3 manage.py startapp app1
Структура каталога proj1 стала следующей:

Проверяем работу представления
Представление — это то, как все будет отображаться. Настроим представление, а для этого добавим в файл proj1/app1/views.py следующие строки:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world!")
А теперь зададим URL, при вводе которого в адресной строке браузера будет отображаться наше представление «Hello, world!». Для этого файл proj1/proj1/urls.py приводим к виду:
from django.contrib import admin
from django.urls import path
from app1 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('app1/', views.index, name='index'),
]
Теперь, при вводе в адресной строке браузера:
http://192.168.1.38:8000/app1/
должно отобразиться наше представление «Hello, world!». Проверяем:

Настройка базы данных
По умолчанию в файле конфигурации proj1/proj1/settings.py настроено использование SQLite базы данных. SQLite база данных — это просто файл proj1/db.sqlite3. И этот файл создается автоматически.
Примечание: в реальных проектах на Django рекомендуется использовать более масштабируемую базу данных, например, PostgreSQL.
Для просмотра существующих таблиц в SQLite базе данных proj1/db.sqlite3 запускаем клиента командной строки SQLite базы данных:
sqlite3

Подключаемся к нашей SQLite базе данных proj1/db.sqlite3, просматриваем список существующих таблиц и выходим из клиента командной строки базы данных:
.open db.sqlite3
.tables
.exit
В базе пока единственная таблица django_migrations.
По умолчанию в файле конфигурации proj1/proj1/settings.py подключено к нашему проекту некоторое количество базовых приложений, поставляемых с Django, таких, как аутентификация, сессии, сообщения и т.д. Создадим для этих приложений необходимые таблицы в базе данных:
python3 manage.py migrate
Если теперь просмотрим список существующих таблиц в базе данных proj1/db.sqlite3, то список уже будет состоять не из одной таблицы:

Примечание: список подключаемых к проекту базовых приложений настраивается в файле конфигурации proj1/proj1/settings.py в параметре INSTALLED_APPS.
Создание моделей
Для нашего приложения для голосования нам нужны следующие данные: текст вопроса (question_text), его дата публикации (pub_date), текст ответа (choice_text) и количество голосов (votes). Теперь отредактируем файл proj1/app1/models.py, чтобы он выглядел так:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
Примечание: каждый ответ (Choice) связан с одним вопросом (Question). Это отношение один-к-одному. Такая связь данных определяется в Django с использованием ForeignKey.
В файле proj1/app1/models.py мы описали наши модели (Question и Choice) — по сути структуру нашей базы данных.
Подключим наше приложение app1 к проекту proj1. Для этого в файле конфигурации proj1/proj1/settings.py в параметре INSTALLED_APPS добавим строчку:
'app1.apps.App1Config',

Проверяем, есть ли какие ошибки/проблемы в нашем проекте:
python3 manage.py check
Проблем нет? Запускаем автоматическое создание таблиц созданных моделей в базе данных:
python3 manage.py makemigrations app1
python3 manage.py migrate
Создание администратора
Создадим учетную запись администратора, который будет иметь доступ к приватной странице нашего приложения Django для создания, изменения и удаления опросов:
python3 manage.py createsuperuser

Администраторская часть приложения
Пример веб-приложения Django, который мы создаем, состоит из двух страниц: публичной с опросами и приватной (администраторской) для редактирования опросов. Django автоматически создает администраторскую часть приложения (сайта). Давайте взглянем на нее, находится она по адресу:
http://192.168.1.38:8000/admin/

Примечание: для отображения на русском языке администраторской части приложения Django необходимо в файле конфигурации proj1/proj1/settings.py параметру LANGUAGE_CODE присвоить значение ‘ru-RU’.
Вводим имя администратора и его пароль (см. раздел Создание администратора) и увидим главную страницу администраторской части приложения:

Пока в администраторской части есть возможность добавить/редактировать пользователей и групп пользователей. Данную функциональность предоставляет базовое приложение аутентификации Django (django.contrib.auth).
Чтобы появилась возможность добавления/редактирования опросов и ответов, отредактируем файл proj1/app1/admin.py:
from django.contrib import admin
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
Теперь администратор может добавлять, редактировать, удалять опросы (Questions) и ответы к ним (Choices):

Вот так выглядит окно добавления опроса:

Окно редактирования/удаления опроса очень похоже не предыдущее:

Окно добавления ответа к опросу:

А вот так выглядит окно со списком всех опросов:

Давайте немного настроим это окно (окно со списком всех опросов):
- добавим дату публикации опроса;
- добавим боковую панель «Фильтр» для фильтрации списка опросов по дате публикации;
- добавим поиск по тексту опроса.
Для этого отредактируем файл proj1/app1/admin.py. Удаляем строку:
admin.site.register(Question)
На ее место добавляем:
class QuestionAdmin(admin.ModelAdmin):
list_display = ('question_text', 'pub_date')
list_filter = ['pub_date']
search_fields = ['question_text']
admin.site.register(Question, QuestionAdmin)

Фантастика! Django создал админку со всеми формами автоматически. Мы только прописали модели Question и Choice в файле proj1/app1/models.py, а Django знает, каким типам полей модели (CharField, DataTimeField) какие виджеты HTML соответствуют. А если внешний вид по умолчанию админки не устраивает, то его настройка занимает буквально несколько строк!
Публичная часть приложения
Переходим к разработке публичной части нашего примера веб-приложения на фреймворке Django. Нам понадобится четыре представления (см. пример простого представления в разделе Проверяем работу представления):
- главная страница со списком нескольких последних опросов (URL вида http://192.168.1.38:8000/app1/),
- страница опроса с формой для голосования (URL вида http://192.168.1.38:8000/app1/<question_id>/),
- страница опроса с результатом голосования (URL вида http://192.168.1.38:8000/app1/<question_id>/results/),
- страница опроса с обработкой голосования (URL вида http://192.168.1.38:8000/app1/<question_id>/vote/),
где <question_id> — это уникальный номер (идентификатор), присвоенный опросу при добавлении в базу данных.
Мы будем использовать систему шаблонов Django, чтобы отделить дизайн станицы от кода Python. Т.е. для каждого представления мы создадим свой HTML-шаблон, а представление будет использовать соответствующий шаблон.
Создаем каталог templates в каталоге proj1/app1/:
mkdir templates
По умолчанию Django ищет шаблоны в подкаталоге templates каталога приложения (app1).
Представление под номером 1
В каталоге templates создаем шаблон app1_index.html (для представления под номером 1) и добавляем в него следующий код:
{% if question_list %}
<ul>
{% for question in question_list %}
<li><a href="/app1/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>Опросов нет.</p>
{% endif %}
Шаблон создает список из текстов опросов, а ссылки указывают на страницу опроса с формой голосования.
Примечание: пример шаблона выше использует неполный HTML. В реальных разработках рекомендуется делать шаблоны полными HTML документами.
Чтобы проверить представление под номером 1, т.е. главную страницу со списком нескольких последних опросов, приведем файл proj1/app1/views.py к виду:
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = 'app1_index.html'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:3]
Здесь мы используем базовое представление Django ListView для отображения списка. С помощью атрибута template_name указываем базовому представлению, какой шаблон использовать. А для выборки нескольких последних опросов, мы делаем сортировку опросов по дате публикации и берем 3 самых последних.
Отредактируем файл proj1/proj1/urls.py. Удаляем старую строку:
path('app1/', views.index, name='index'),
Добавляем новую строку:
path('app1/', views.IndexView.as_view(), name='index'),
Проверяем работу главной страницы нашего примера приложения на Django:

Все отлично работает!
Представление под номером 2
Представление под номером 2 — это страница опроса с формой для голосования (URL вида http://192.168.1.38:8000/app1/<question_id>/).
В каталоге proj1/app1/templates/ создаем шаблон app1_detail.html и добавляем в шаблон код:
<form action="/app1/{{ question.id }}/vote/" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Голосовать">
</form>
Шаблон выводит текст опроса, варианты ответов и кнопку Голосовать. При нажатии на кнопку Голосовать форма form отправляет методом post данные choice=<идентификатор выбранного ответа>. Идентификатор ответа уникальный и присваивается ответу при добавлении его в базу данных. Тег шаблона {% csrf_token %} используется встроенной системой защиты Django против подделок межсайтовых запросов.
Чтобы проверить представление под номером 2, добавим в файл proj1/app1/views.py:
class DetailView(generic.DetailView):
model = Question
template_name = 'app1_detail.html'
Здесь мы использует базовое представление Django DetailView для отображения страницы подробностей. С помощью атрибута template_name указываем базовому представлению, какой шаблон использовать. С помощью атрибута model — какую модель использовать.
Базовое представление DetailView ожидает, что значение первичного ключа, получаемое из URL, будет называться pk. В соответствии с этой информацией добавим в файл proj1/proj1/urls.py следующий код:
path('app1/<int:pk>/', views.DetailView.as_view(), name='detail'),
Проверяем страницу опроса с формой для голосования:

Пример представления Django работает отлично!
Представление под номером 3
Представление под номером 3 — это страница опроса с результатом голосования (URL вида http://192.168.1.38:8000/app1/<question_id>/results/).
В каталоге proj1/app1/templates создаем шаблон app1_results.html и добавляем в шаблон код:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'detail' question.id %}">Голосовать еще</a>
В файл представлений proj1/app1/views.py добавляем:
class ResultsView(generic.DetailView):
model = Question
template_name = 'app1_results.html'
Откроем файл proj1/proj1/urls.py и добавим строку:
path('app1/<int:pk>/results/', views.ResultsView.as_view(), name='results'),
Проверяем страницу опроса с результатом голосования:

Представление под номером 4
Представление под номером 4 — это страница опроса с обработкой голосования (URL вида http://192.168.1.38:8000/app1/<question_id>/vote/). В этом представлении обрабатываются данные, оправленные формой form (см. Представление под номером 2).
Редактируем файл представлений proj1/app1/views.py. Сверху добавляем код:
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
Снизу в файле proj1/app1/views.py добавляем:
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'app1_detail.html', {
'question': question,
'error_message': "Вы не выбрали ответ.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('results', args=(question.id,)))
Функция get_object_or_404() получает объект или вызывает исключение Http404, если объекта не существует.
request.POST[‘choice’] возвращает идентификатор выбранного ответа или вызывает исключение KeyError. Исключение KeyError вызывается при условии, если ответ не был выбран, а кнопка Голосовать была нажата. В случае исключения KeyError повторно отображается страница опроса с формой для голосования и с сообщением «Вы не выбрали ответ.».
У функции render() первый аргумент — это объект запроса, второй аргумент — это шаблон, третий необязательный аргумент — это словарь. Функция загружает шаблон, заполняет контекстом и возвращает объект HttpResponse c результатом визуализации шаблона.
selected_choice — это счетчик выбора. Значение счетчика выбора получается из базы данных, потом значение увеличивается и обратно сохраняется в базу данных.
Примечание: при данной реализации функции vote() и при одновременном голосовании нескольких человек может возникнуть так называемое «состояние гонки». В реальных проектах следует избегать условий гонки.
У функции reverse() первый аргумент — имя представления Django, которому мы хотим передать выполнение, а второй аргумент — чтобы уточнить, для какого именно опроса. Функция reverse() возвращает строку вида:
'/app1/8/results/'
Функция HttpResponseRedirect принимает один аргумент — это URL адрес, на который будет перенаправлен пользователь. Т.е. после того, как пользователь проголосовал в опросе, функция vote() перенаправит пользователя на страницу с результатом голосования по опросу.
Откроем файл proj1/proj1/urls.py и добавим строку:
path('app1/<int:question_id>/vote/', views.vote, name='vote'),
Чтобы проверить работу представления под номером 4, нужно перейти на страницу опроса с формой для голосования и проголосовать.
Выводы
Пример веб-голосовалки на фреймворке Django, который мы разобрали в этой статье, подходит для первого ознакомления с фреймворком Django. Мы научились устанавливать сам фреймворк, запускать веб-сервер для проверки работоспособности веб-приложения, разобрались в основных файлах и каталогах проекта Django и собрали свое первое приложение Django.
Как это может помочь бизнесу?
Лучший способ помочь бизнесу быть более успешным — это использовать практики лидеров рынка. Чтобы использовать эти практики, нужно быть с ними знакомыми. Эта статья знакомит с фреймворком Django, который используется в проектах: YouTube, поиске Google, Instagram, Yahoo Maps и многих других. Фреймворк Django бесплатный и уменьшает стоимость разработки за счет уже готового набора компонентов, которые необходимы практически в любом проекте: аутентификация пользователей (как локальная, так и с аккаунтом социальных сетей), регистрация, управление учетными записями, загрузка и скачивание файлов, работа с облачными хранилищами данных, настраиваемая «админка» со встроенным набором виджетов и т.д. Так что, при возникновении идеи создания веб-приложения для своего бизнеса, вспомните про фреймворк Django!
Все вопросы, по традиции, предлагаю оставлять под статьей в комментариях. Спасибо!
Комментарии