Django. Вступление
Введение
Django - один из самых популярных backend web-фреймворков на сегодняшний день. Backend web-фреймворком предназначены для работы на серверной стороне. Кратко говоря, фреймворки это библиотеки преднастроенных параметров. В каждом проекте есть какой-то скелет, на который каждый раз тратить время нецелесообразно. Именно для экономии этого времени и существуют фреймворки. Они создают за вас этот скелет, а их разработчики и комьюнити предоставляют для вас документацию и обучающие материалы, помогающие вам "масштабировать" этот скелет так, как нужно вам.
Django умеет многое, постараемся со временем тут разобраться во всем. Django написан на Python, поэтому я подразумеваю, что к началу этого блока вы знакомы с базовым синтаксисом и ООП этого языка. Не станем долго затягивать и прямо сейчас создадим наш первый сайт на Django.
Установка Django. Виртуальное окружение. Первый сайт.
В этом обучающем материале будут примеры кода и результата его исполнения. Для написания кода будет использоваться интегрированная среда разработки PyCharm. Используйте тот редактор, к которому привыкли, это не принципиально. Операционная система Linux, команды, исполняемые в командной строке, тоже будут для Linux. Рекомендую, если вы решили заниматься программированием, начать использовать Linux как можно раньше, установка его рядом с Windows или как единственную систему на вашем устройстве займет пару часов, не затягивайте с этим.
Venv
Устанавливается Django, как и другие библиотеки Python, с помощью системы управления пакетами и соответственно команды pip install или apt install в случае Linux. Но перед созданием первого проекта создадим виртуальное окружение и установим Django в него. Venv - один из наиболее популярных инструментов для создания виртуального окружения. Для чего это окружение вообще нужно? Библиотеки python постоянно обновляются и у них выходят новые версии. Чаще всего изменения и исправления в них незначительны. Но если в одном из ваших старых проектов использовалась более старая версия какой-то библиотеки, а впоследствии вы воспользовались этой библиотекой для нового вашего проекта и обновили ее, то это может привести к проблемам с работоспособностью ваших прошлых проектов. Поэтому лучше не игнорировать виртуальное окружение. Еще одна причина - развертывание проекта на другом устройстве или хостинге. Когда вы отправляете проект на github, то помимо прочего проект обычно содержит файл requerments.txt, внутри которого записаны все библиотеки, используемые для проекта. Об этом подробнее говорится в блоке про деплой, сейчас можете сильно в это не погружаться, но примите за правило: новый проект - новое виртуальное окружение под этот проект. Лучше привыкать к грамотному программированию сразу.
tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$ python3 -m venv venv
tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$ source venv/bin/activate
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$
Как видно создается виртуальное окружение всего одной командой, и всего одной командой запускается. Рассмотрим немного подробнее. Зачастую библиотека venv устанавливается сразу с установкой python, однако, если вы используете дистрибутив Linux - Ubuntu, она там может быть не установлена.
Устанавливается она следующей командой:
sudo apt install python3.* venv
, где вместо * пишется ваша версия python. Например, 3.8. Узнать версию python можно командой:
python3 --version
.
После успешной установки переходим в папку, где хотим создать виртуальное окружение и пишем команду как на примере:
python3 -m venv venv
, где первое упоминание venv - название исполняемого модуля, а второе - его название. Часто виртуальное окружение так и называют - venv. В итоге в выбранной папке создается каталог venv/, где хранится копия интерпретатора python. В этот каталог будут устанавливаться необходимые нам библиотеки для работы над проектом.
Осталось запустить окружение. Команда:
source venv/bin/activate
, если после этого около названия вашего устройства появились круглые скобки с названием вашего окружения, (venv), в случае примера, то вы успешно запустили виртуальное окружение. Команда deactivate
для выхода из виртуального окружение.
Установка django
Устанавливается django командой:
pip install django
, загрузка может не начинаться несколько секунд, стоит подождать. Не забывайте, пишем мы эту команду в виртуальном окружении, иначе зачем мы его вообще создавали, кстати команда pip install будет работать только внутри виртуального окружения. Если все-таки вы хотите установить django для всего вашего python, а не только для venv, то воспользуйтесь командой:
sudo apt install python3-django
. Проверить успешность установки в любом из двух случаев можно командой:
django-admin --version
, если в ответ на нее вы получили три цифры, разделенные точкой, то установка прошла успешно.
Итак, у нас есть виртуальное окружение с установленным в него django, можно наконец создать свой первый проект.
Создание нового проекта
Находясь в виртуальном окружении введите команду:
django-admin startproject (название проекта)
, например firstsite. Все, первый Django проект создан. На одном уровне с папкой venv должна появится папка с названием вашего проекта. Вот содержание этой папки.
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$ ls
firstsite venv
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$ cd firstsite/
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite$ ls
firstsite manage.py
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite$ cd firstsite/
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/firstsite$ ls
asgi.py __init__.py settings.py urls.py wsgi.py
На одном уровне, как и говорилось папки venv и firstsite, внутри папки firstsite еще одна папка с названием проекта и файл manage.py и внутри уже второй папки с названием проекта файлы: __init__.py, asgi.py, settings.py, urls.py, wsgi.py. Это тот самый скелет, стандартная структура любого, только что созданного, проекта. Подробнее о каждом из них чуть далее, а сейчас напишем еще одну команду, находясь в первой папки с названием проекта:
python manage.py runserver
, скорее всего вы увидите сообщение от терминала, говорящее о необходимости совершить миграции, сейчас не обращаем на это внимания. А чуть ниже вы увидите адрес, скорее всего он будет такой: http://127.0.0.1:8000/, аналогом является http://localhost:8000/, где 8000 это восьмидесятый порт, к которому разработчики django добавили еще два нуля, просто для того, чтобы отличать на каком фреймворке создан сайт. Кликните по нему или скопируйте и вставьте в командную строку браузера. Вы увидите следующее.

Так выглядит стартовая страница Django и если после всех этих действий у вас получилось то же самое, то вы все сделали правильно. Для того чтобы деактивировать сервер нажмите ctrl + c
в терминале.
Структура проекта
Для запуска сервера мы воспользовались файлом manage.py, этакого "менеджера" проекта, с помощью которого выполняются административные задачи. К его помощи мы будем обращаться постоянно. Все доступные команды можно увидеть с помощью команды:
python manage.py help
Во второй папке с названием проекта содержаться:
__init__.py - пустой файл, говорящий python'у воспринимать данную директорию в качестве пакета.
settings.py - файл с настройками проекта.
urls.py - файл, в котором будут прописываться url адреса.
wsgi.py - с помощью этого файла приложение может работать с веб-сервером по протоколу wsgi.
asgi.py - с помощью этого файла приложение может работать с веб-сервером по протоколу asgi.
А также после первого запуска добавился файл db.sqlite3 - база данных проекта. Базы данных отдельная, большая тема и подробнее о ней в будущем мы конечно поговорим. SQlite - не единственная СУБД, сказать честнее чаще всего в реальных проектах используется не она, а например MySQL или PostgreSQL, но пока остановимся на SQlite.
settings.py
settings.py является не менее важным файлом, чем manage.py, и хотелось бы поговорить о нем чуть подробнее. Откройте его и пробежимся по нему сверху вниз.
BASE_DIR - переменная, в которой указан путь к папке проекта, изначально вычислен автоматически.
SECRET_KEY - сгенерированный случайный набор символов, секретный ключ проекта, лучше его никому не знать, а если кто-то его узнал, то достаточно заменить в нем некоторые символы.
DEBUG - режим отладки, в значении TRUE - включен, в значении FALSE - отключен. При включенном режиме мы получаем более полную информацию об ошибках, обычно при разработке режим отладки всегда включен и выключается, когда проект выгружается в сеть.
ALLOWED_HOSTS - по умолчанию пустой список, в него добавляются имена доменов или хостов, которые может обслуживать этот проект django.
INSTALLED_APPS - список зарегистрированных приложений, подробнее о них позже.
MIDDLEWARE - список зарегистрированных посредников, подробнее о них позже.
ROOT_URLCONF - путь к файлу urls.py, уровня проекта.
TEMPLATES - настройки для работы с шаблонами, по умолчанию используется шаблонизатор - djangotemplates, естественно существуют и другие. Django использует HTML шаблоны для визуальной составляющей сайта.
WSGI_APPLICATION - настройка, связанная с разворачиванием сайта.
DATABASES - настройки, где описаны базы данных и их адрес.
AUTH_PASSWORD_VALIDATORS - настройки, для ограничения паролей к аккаунтам, которые имеют доступ к сайту. Например, длина пароля.
LANGUAGE_CODE - язык сайта, по умолчанию английский, для изменения например на русский, удалите 'en-us' напишите - 'ru'.
TIME_ZONE - часовой пояс.
USE_I18N - если True, то автоматически все системные сообщения будут выводится на языке указанном в LANGUAGE_CODE, если False - то нет.
USE_TZ - если True, значение даты и времени будет отображаться соответствуя указанной в TIME_ZONE временной зоне.
STATIC_URL - настройка для работы со статическими файлами.
DEFAULT_AUTO_FIELD - необходим для моделей, у которых нет поля с primary_key = True. И для управления этими полями.
Прямо сейчас поработаем с настройками и добавим первое приложение нашего сайта.
Первое приложение
По сути, сайт на django - это набор приложений отвечающих за разный его функционал. Можно создать сайт состоящий из одного приложения, но зачастую их несколько. Давайте начнем создавать наш первый сайт. Пусть это будет сайт, который умеет генерировать случайный логин, логины часто нужны для самых разных целей, но иногда не хочется их придумывать, пусть этот сайт решает эту проблему. Генерация логина единственный функционал нашего первого сайта, поэтому для него хватит одного приложения. Создадим его командой, предварительно остановив сервер, если он запущен:
python manage.py startapp genlogins
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite$ python manage.py startapp genlogins
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite$ ls
db.sqlite3 firstsite genlogins manage.py
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite$ cd genlogins
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins$ ls
admin.py apps.py __init__.py migrations models.py tests.py views.py
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins$
Создалась папка с нашим приложением, будем говорить о ее содержимом постепенно, но сначала добавим приложение в наш проект. Откройте файл settings.py и в переменную INSTALLED_APPS добавьте в конец наше созданное приложение. Достаточно в кавычках написать название приложение, оно такое же, как и у его папки, genlogins в нашем случае. Примерно так.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'genlogins',
]
Первое приложение создано и добавлено в проект. Но если мы теперь заново запустим наш сервер, то никаких изменений мы не увидим. Во-первых, приложение есть в проекте, но ему не присвоен никакой адрес, а во-вторых, файл views.py, как раз отвечающий за отображение - содержит только импорт функции render из django.shortcuts, то есть по сути пустой. Давайте это исправим.
Введение в urls
Обычно каждому разделу сайта присвоен свой url адрес, название которого примерно описывает, что происходит по этому адресу. Возьмем наш сайт, адрес 127.0.0.1:8000 - это адрес нашей стартовой страницы, когда мы приобретем доменное имя и присвоим его сайту, этот адрес заменится на доменное имя. Как к примеру домен twithc.tv - это стартовая страница стриминговой платформы twithc. Но каждому стримеру этой платформы присвоен свой url адрес, который указывается через / после доменного имени - twithc.tv. Думаю, если вы пользуетесь интернетом, то вы прекрасно это знаете, но я не смог проигнорировать это введение. Для страниц (приложений) нашего сайта мы тоже хотим присваивать свои url адреса. По умолчанию в новом проекте, в главном файле urls.py содержится один адрес, admin/, слово главном я использовал, потому что в будущем мы создадим файл urls.py для каждого нашего приложения. Этот адрес ведет нас на страницу авторизации в панель администратора нашего сайта. Введем его после 127.0.0.1:8000/.
Я прописал в адресной строке только admin, /login/?next=/admin/ добавилось автоматически, сейчас не обращайте на это внимания. Перед нами открылось окно авторизации в панель администратора, ее мы коснемся позже. Сейчас мы перешли по этому адресу только для того, чтобы лучше понять идею url адресов.
Стартовая страница
Конечно, взлетающая ракета на нашей стартовой странице выглядит красиво, но мы все-таки хотим создать свою стартовую страницу. Рассмотрим пример.
PROJECT | urls.py | views.py
| |
forsite | from django.contrib import admin | from django.shortcuts import render
firstsite | from django.urls import path | from django.http import HttpResponse
firstsite | from genlogins import views |
__init__.py | |
asgi.py | urlpatterns = [ | # Create your views here.
settings.py | path('admin/', admin.site.urls), | def start(request):
urls.py | path('', views.start), | return HttpResponse("START")
wsgi.py | ] |
genlogins |
migtations |
__init__.py |
admin.py |
apps.py |
models.py |
tests.py |
views.py |
db.sqlite3 |
manage.py |
venv |
Начнем с главного файла urls.py. Функция path из django.urls помогает создавать нам новые url адреса, она принимает два значения, название и то, какое представление должно отработать при переходе по ней. Название адреса всегда можно изменить, например, заменив 'admin/' на 'adminpanel/', ничего не изменится, кроме того, что авторизация в панель администратора будет открываться по адресу adminpanel. Со вторым значением интересней, в случае admin мы используем представление site.urls из модуля django.contrib. В случае стартовой страницы мы теперь хотим использовать наше приложение genlogins и для этого в его файле views.py создадим первое и самое примитивное представление.
В файле views.py напишем самую простую функцию python, назовем ее start и принимать она будет один параметр request. Для того чтобы отобразить текст на странице, воспользуемся классом HttpResponse из django.http. Возвратим в нашей функции start HttpResponse с каким-нибудь значением, словом START в данном случае. Не забудьте импортировать файл views.py в файл urls.py (строка 18 файла urls.py). Теперь запустим сервер заново.
И увидим, что вместо взлетающей ракеты перед нами пустая страница со значением, возвращаемым классом HttpResponse. Обратите внимание адрес все тот же - 127.0.0.1:8000/.
Подытожим. Сначала пользователь переходит по какому-то пути, для этого пути ищется соответствие в файле urls.py, второй параметр функции path отправляет запрос в файл, в котором находится указанное представление, представление отрабатывает и его результат выводится пользователю по указанному им адресу. И для тренировки добавим путь, допустим, 'end/', ведь файл views.py может содержать столько представлений, сколько url адресов используется на нашем сайте.
PROJECT | urls.py | views.py
| |
forsite | from django.contrib import admin | from django.shortcuts import render
firstsite | from django.urls import path | from django.http import HttpResponse
firstsite | from genlogins import views |
__init__.py | |
asgi.py | urlpatterns = [ | # Create your views here.
settings.py | path('admin/', admin.site.urls), | def start(request):
urls.py | path('', views.start), | return HttpResponse("START")
wsgi.py | path('end', view.end), |
genlogins | ] |
migtations | | def end(request):
__init__.py | | return HttpResponse("END")
admin.py |
apps.py |
models.py |
tests.py |
views.py |
db.sqlite3 |
manage.py |
venv |
И посмотрим на результат.
По адресу 127.0.0.1:8000/end отобразилось слово END. Думаю теперь идея этого механизма полностью понятна. Но согласитесь, страница, на которой просто отображается текст это достаточно скучно. И как известно, за визуализацию, в том числе за визуализацию сайтов, отвечает такой раздел программирования, как frontend.HTML, CSS и JavaScript - три столпа, на которых стоит frontend. И визуализация django сайтов не исключение.
Шаблоны
Я здесь не буду объяснять как работают HTML, CSS и JavaScript, все-таки frontend - это отдельная и большая тема, поэтому предполагается, что это все вам уже известно. Сначала нужно понять, где писать наши шаблоны.
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins$ mkdir templates
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins$ cd templates/
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins/templates$ mkdir genlogins
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins/templates$ cd ../
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins$ ls
admin.py apps.py __init__.py migrations models.py templates tests.py views.py
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins$ cd templates/
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/firstsite/genlogins/templates$ ls
genlogins
Внутри нашего приложения genlogins создадим папку templates, и внутри папки templates создадим еще папку с названием нашего приложения, то есть genlogins. Такая структура папок создается в каждом приложении. Сделаем простой набросок нашего сайта
PROJECT | index.html
|
forsite | <h1>Genlogin</h1>
firstsite | <p>Здесь можно сгенерировать случайный текст</p>
firstsite |
__init__.py |
asgi.py |
settings.py | views.py
urls.py |_____________________________________________________
wsgi.py | from django.shortcuts import render
genlogins | from django.http import HttpResponse
migtations |
templates |
genlogins | # Create your views here.
index.html |
__init__.py | def start(request):
admin.py | return render(request, 'genlogins/index.html')
apps.py |
models.py |
tests.py | def start(request):
views.py | return HttpResponse("END")
db.sqlite3 |
manage.py |
venv |
Вот так теперь выглядит структура папок нашего проекта. В файле index.html добавим тег h1 для названия, и тег p для краткого описания. Теперь отобразим этот шаблон на стартовой странице, для этого в файле views.py возвратим этот шаблон, только уже не через класс HttpResponse, а через функцию render из django.shortcuts. Первым параметром будет являться аргумент request нашей функции, который является адресом нашего запроса, а вторым - путь до нашего шаблона, который можно написать просто как название приложения / название файла, поэтому и создается именно такая структура папок для шаблонов. Взглянем на нашу стартовую страницу теперь.
Параметры request и template_name функции render являются обязательными. Но для этой функции существуют еще четыре необязательным параметра, один из которых context - словарь значений для добавления их в шаблон.
PROJECT | index.html | forsite | <h1>Genlogin</h1> firstsite | <p>Здесь можно сгенерировать случайный текст</p> firstsite | __init__.py | <p>Или логин</p> asgi.py | {{ login }} settings.py | urls.py | views.py wsgi.py |_____________________________________________________ genlogins | from django.shortcuts import render migtations | from django.http import HttpResponse templates | genlogins | index.html | # Create your views here. __init__.py | admin.py | def start(request): apps.py | return render(request, 'genlogins/index.html' models.py | {'login': 'random_login'} tests.py | views.py | def end(request): db.sqlite3 | return HttpResponse("END") manage.py | venv |
Добавляется в виде обычного python словаря, в фигурных скобках парой 'ключ':'значение'. Для добавления значения из этого словаря в шаблон нужно в двойных фигурных скобках написать ключ необходимого значения.
Вот так это будет выглядеть на странице. То что мы сделали можно считать введением в шаблонизаторы.
Тег form
В каком виде мы хотим выводить наш сгенерированный логин? Сразу вспоминается сайт random.org, где задается диапазон значений и в окне вывода на этой же странице отображается случайное число из этого диапазона. Пусть в нашем логине тоже можно будет выбрать количество символов, но логины, например в 10 символов, вряд ли кому-то пригодятся, так же как и в 1 символ, поэтому пусть наш логин будет ограничен диапазоном от 2 до 5 символов. Это наверное мало, но так код для примера получится компактнее, а зная как сделать выбор от 2 до 5, можно сказать, что вы знаете, как сделать выбор от n до n. В html есть тег form, который поможет нам реализовать такой функционал. Но пусть логин будет отображаться не на той же странице, а на новой. Да удобней для пользователя было бы отображать логин на этой же странице, но для обучения и понимания лучше сделать это сделать на другой.
index.html | views.py
|
<h1>Genlogin</h1> | ...
<p>Не можете придумать логин? Сгенерируйте его здесь.</p> | def start(request):
<p> Для этого просто выберите из скольки символов будет состоять ваш логин. | return render(request, 'genlogins/index.html')
<br>Можно выбрать конкретное значение</p> |
<form action="{% url 'login' %}"> |
<select name="hard_length"> | def login(request):
<option value="2">2</option> | return render(request, 'genlogins/login.html')
<option value="3">3</option> |
<option value="4">4</option> |
<option value="5">5</option> |
</select> |___________________________________________________
<input type="submit" value="Сгенерировать логин заданной длины"> | urls.py
</form> |
<p>Либо выбрать диапазон значений</p> | from django.contrib import admin
<form action="{% url 'login' %}"> | from django.urls import path
<select name="from"> | from genlogins import views
<option value="2">От 2</option> |
<option value="3">От 3</option> |
<option value="4">От 4</option> | urlpatterns = [
</select> | path('admin/', admin.site.urls),
<select name="to"> | path('', views.start),
<option value="3">До 3</option> | path('your_login', views.login, name='login'),
<option value="4">До 4</option> | ]
<option value="5">До 5</option> |
</select> |
<input type="submit" |
value="Сгенерировать логин в заданном диапазоне значений"> |
</form> |
_____________________________________________________________________________|
login.html |
|
<h1>Ваш логин: </h1> |
Вот так мы это реализуем. Во-первых, добавим в начало нашего шаблона более понятное описание для чего нужен этот сайт, а во-вторых добавим две формы. Первая форма дает нам возможность сгенерировать логин жестко заданной длины, а вторая в диапазоне выбранных значений. Выпадающий список создается внутри тега select, где внутри тегов option создаются значения этого выпадающего списка. Обратите внимание воспринимается значение заданное в параметре value, а то, что написано внутри тега это информация для пользователя, так во второй форме внутри тега перед цифрами добавлены слова от и до, но в параметрах value находятся просто значения. Для совершения действия перехода на новую страницу используется тег input с параметром type в значении submit. Для того чтобы перейти на какую-то страницу, ее сначала нужно создать. Создадим файл login.html рядом с основным нашим файлом index.html. И напишем там всего один тег h1, с текстом 'Ваш логин:'. Заменим ранее созданный url end на your_login, а также заменим функцию end на функцию login и будем ссылаться на нее. В функции path появился еще и третий необязательный параметр name в значении login. Он нужен для того, чтобы ссылаться не к имени url, которое видит пользователь в адресе сайта, а к имени заданному в параметре name. Дело в том, что у реального проекта может появиться необходимость изменить видимое значение url, и чтобы не менять также ссылку на этот url во всех местах проекта, легче ссылаться к его имени, потребность в изменении которого, может появиться с гораздо меньшей вероятностью. Ссылаемся мы к этому имени в параметре action тега form. Запись {% %} - эквивалент записи {{ }}, используемой выше, а полная запись этого параметра ="{% url 'login' %}" - эквивалент записи ="your_login", то есть явной ссылки на url страницы, которую нужно открыть. После всех этих манипуляций наша домашняя страница стала выглядеть так:
Теперь можно выбрать значения из выпадающих списков и нажать на кнопку рядом.
При нажатии на любую из обеих кнопок мы будем попадать на страницу, на которой написано 'Ваш логин: '. Обратите внимание на url этой страницы, во-первых, она находится по адресу your_login, а во-вторых, через знак вопроса заданы значения. Конкретно в этом случае, я нажал на кнопку 'Сгенерировать логин в заданном диапазоне значений', при заданном диапазоне от 3 до 5, и это отобразилось в url адресе открывшейся страницы.
Реализовываем необходимый функционал
Вот так, совсем не сложно, мы сделали набросок нашей страницы, с описанием функционала и с рабочими формами. Но основной функционал все еще не реализован, мы не получаем наш сгенерированный логин. Сейчас это исправим.
from django.shortcuts import render
import random
# Create your views here.
def start(request):
return render(request, 'genlogins/index.html')
def login(request):
alphabet = 'абвгдежзийклмнопрстуфхцчшщэюя'
length = int(request.GET.get('hard_length'))
your_login = ''
for x in range(length):
your_login += random.choice(alphabet)
return render(request, 'genlogins/login.html',
{'login': your_login})
def rand_login(request):
alphabet = 'абвгдежзийклмнопрстуфхцчшщэюя'
your_rand_login = ''
from_char = int(request.GET.get('from_char'))
to_char = int(request.GET.get('to_char'))
random_length = random.randint(from_char, to_char)
for x in range(random_length):
your_rand_login += random.choice(alphabet)
return render(request, 'genlogins/random_login.html',
{'rand_login': your_rand_login})
<h1>Genlogin</h1>
<p>Не можете придумать логин? Сгенерируйте его здесь.</p>
<p> Для этого просто выберите из скольки символов будет состоять ваш логин.
<br>Можно выбрать конкретное значение</p>
<form action="{% url 'login' %}">
<select name="hard_length">
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<input type="submit" value="Сгенерировать логин заданной длины">
</form>
<p>Либо выбрать диапазон значений</p>
<form action="{% url 'rand_login' %}">
<select name="from_char">
<option value="2">От 2</option>
<option value="3">От 3</option>
<option value="4">От 4</option>
</select>
<select name="to_char">
<option value="3">До 3</option>
<option value="4">До 4</option>
<option value="5">До 5</option>
</select>
<input type="submit" value="Сгенерировать логин в заданном диапазоне значений">
</form>
<h1>Ваш логин: </h1>
<h2>{{ login }}</h2>
<h1>Ваш логин: </h1>
<h2>{{ rand_login }}</h2>
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.start),
path('your_login', views.login, name='login'),
path('your_rand_login', views.rand_login, name='rand_login'),
]
При реализации этого функционала я решил, что при нажатии на кнопку для жестко заданной длины и для случайной длины будут открываться разные страницы, соответственно называющиеся your_login и your_rand_login. Это можно было бы реализовать и на одной странице, но мне показалось что так нагляднее. Поэтому в коде появились пара незначительных изменений. Добавился новый url your_rand_login, ссылающийся на функцию rand_login. Соответственно в файле index.html поменялся параметр action с ="{% url 'login' %}" на ="{% url 'rand_login' %}" для второй формы. Помимо этого в файле index.html теги option я написал в строку только для того, чтобы на одном примере компактнее уместить все изменения, вам этого делать не следует. А также заменил параметры name тега select у второй формы с from и to на from_char и to_char соответственно, это сделано из-за того, что при программировании в фале views.py from воспринималось как команда импорта python, а не как переменная. Я думаю, то что код поддался небольшому рефакторингу это наоборот хорошо, поскольку это имитирует полностью естественный и натуральный процесс программирования. Теперь перейдем к файлу views.py.
В возвращаемой функции render снова появился третий параметр, и ключ этого параметра я добавил внутрь тегов h2 файлов login.html и random_login.html. Эти ключи будут ссылаться ну случайно сгенерированные наборы символов, переменная your_login для функции login и переменная your_rand_login для функции rand_login, которые изначально будут являться пустой строкой. В обеих функциях создадим переменную alphabet, куда поместим все буквы русского алфавита, кроме 'ёьыъ', исключение этих букв, как вы понимаете, необязательно. Теперь нам нужно получать данные из наших форм. Делается это благодаря методу GET.get. Обратите внимание на строки 13, 28 и 29 файла views.py. Ссылаться на формы мы можем по значению параметра name тега select. Так в случае функции login нам достаточно взять одно значение, а в случае функции rand_login нам нужно взять два значения. Параметр value тега option изначально является строковым типом, то есть str, нам же нужен целочисленный тип, то есть int. Делается это оборачиванием нашего запроса в функцию int. Далее импортируем модуль random, и благодаря ему, выбираем случайный символ из переменной alphabet и прибавляем его к переменной, которую хотим вывести, с помощью random.choice. Повторяется это действие в цикле for столько раз, сколько задано в переменной length для функции login и в переменной random_length для функции rand_login. Random_length выбирает случайное значение, с помощью функции random.randint, которая в качестве параметров принимает переменные from_char и to_char. Думаю теперь вам понятна каждая строка, которую мы написали. Откройте сайт и убедитесь, что все работает.
Вот пример работы нашего сайта, обратите внимание на url адрес, диапазон значений выбран от 2 до 5, а возвращенное слово состоит из четырех символов.
Теперь на сайт работает так как мы задумали, но конечно функционал можно доработать как вам захочется. Например, можете предоставлять пользователю выбор между русским и английским алфавитом, а на страницу с результатом добавить кнопку "вернуться к выбору параметров", ведь сейчас, для того чтобы вернуться на главную страницу, нужно нажимать стрелочку "назад" в браузере.
Добавить эту кнопку можно очень просто, например так:
<h1>Ваш логин: </h1>
<h2>{{ login }}</h2>
<button>
<a href="{% url 'home' %}">Вернуться к выбору параметров</a>
</button>
<h1>Ваш логин: </h1>
<h2>{{ rand_login }}</h2>
<button>
<a href="{% url 'home' %}">Вернуться к выбору параметров</a>
</button>
urlpatterns = [ path('admin/', admin.site.urls), path('', views.start, name='home'), path('your_login', views.login, name='login'), path('your_rand_login', views.rand_login, name='rand_login'), ]
Думаю объяснять тут нечего.
Теперь на странице с результатом появилась кнопка, при нажатии на которую мы переходим обратно на домашнюю страницу.
Внешний вид
Мы создали сайт, который работает и выполняет необходимую нам задачу. Но он выглядит достаточно просто. Как известно, за внешность сайта отвечает css, а помогают ему такие мощные фреймворки как, например, bootstrap. Давайте с помощью css и bootstrap сделаем наш сайт посимпатичнее. Добавим bootstrap на наш сайт. Bootstrap подключается как обычно. Заходим на страницу документации bootstrap, копируем css link и размещаем его вначале наших html файлов.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">
<div class="container text-center">
<h1>Genlogin</h1>
<p>Не можете придумать логин? Сгенерируйте его здесь.</p>
<p> Для этого просто выберите из скольки символов будет состоять ваш логин.
<br>Можно выбрать конкретное значение</p>
<form action="{% url 'login' %}">
<select name="hard_length">
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
<input type="submit" value="Сгенерировать логин заданной длины" class="btn btn-secondary">
</form>
<p>Либо выбрать диапазон значений</p>
<form action="{% url 'rand_login' %}">
<select name="from_char">
<option value="2">От 2</option>
<option value="3">От 3</option>
<option value="4">От 4</option>
</select>
<select name="to_char">
<option value="3">До 3</option>
<option value="4">До 4</option>
<option value="5">До 5</option>
</select>
<input type="submit" value="Сгенерировать логин в заданном диапазоне значений" class="btn btn-secondary">
</form>
</div>
{% load static %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/style.css'%}">
<div class="container text-center">
<h1>Ваш логин: </h1>
<div class="login"><h2>{{ login }}</h2></div>
<button class="btn btn-light">
<a href="{% url 'home' %}">Вернуться к выбору параметров</a>
</button>
<input type='button' onclick='window.location.reload()' value ='Сгенерировать заново' class="btn btn-secondary">
</div>
{% load static %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/style.css'%}">
<div class="container text-center">
<h1>Ваш логин: </h1>
<div class="login"><h2>{{ rand_login }}</h2></div>
<button class="btn btn-light"> <a href="{% url 'home' %}">Вернуться к выбору параметров</a> </button>
<input type='button' onclick='window.location.reload()' value ='Сгенерировать заново' class="btn btn-secondary">
</div>
.login {
color: red;
width: 200px;
height: 200px;
background-color: antiquewhite;
margin: 0 auto;
margin-bottom: 20px;
border-radius: 50%;
text-align: center;
padding-top: 80px;
border: 1px black solid;
}
Сделаем это таким образом, и из документации возьмем готовые стили для наших кнопок. Также поместим весь на html внутрь тега div с классами container и text-center. Еще подключим css файл к нашему проекту. CSS в django подключается не так как в обычном HTML. В django для этой цели тоже используется тег link, но в параметре href пишется не просто путь к файлу, а конструкция следующего вида: {% static 'путь к файлу' %}, а также в начале шаблона подключаем статические файлы специальным тегом: {% load static %}. Для статических файлов создадим папку static и внутри этой папки еще папку css и уже в этой папке создадим файл style.css. В этом css файле застилизуем генерируемый логин.
Теперь зайдем на наш сайт и обновим страницу. Наш сайт стал выглядеть намного интересней.
Еще я добавил кнопку обновления страницы, чтобы не использовать f5. Как это сделано можно посмотреть в файле login.html.
Первый сайт на django готов
Мы сделали наш первый сайт. Как по мне, получилось неплохо. Конечно, это очень простой сайт, для создания которого использовалась малая часть возможностей django. Но надеюсь его создание сформировало у вас общее представление о работе с этим мощным и функциональным фреймворком. Мы не пользовались базами данных, панелью администратора, мы воспользовались библиотекой bootstrap и подключили css файл, но есть еще не мало моментов связанных с шаблонами, которые мы позже обсудим, и вообще нам предстоит разобрать еще очень многое. Надеюсь вы не просто повторяли за мной, а экспериментировали с кодом, для того, чтобы лучше понять как все это устроено.
Можно немного отдохнуть и приступать к созданию второго сайта.
Весь код сайта genlogin вы можете найти на моем github и там же, при необходимости, скачать его.
Базы данных. Панель администратора. Второй сайт.
Во втором сайте, как заявлено в названии, хотелось бы уделить внимание базам данных и панели администратора. Для освоения баз данных можно создать сайт, на котором будут выкладываться какие-нибудь, назовем это, посты. И должны они быть оформлены в одинаковом формате, допустим у них есть название, описание и картинка, самый очевидный набор информации, который сразу приходит в голову. Я не хочу писать сайт-блог, на который можно добавлять новые посты. Или сайт-портфолио, на который можно прикреплять свои новые проекты, может мне, конечно, так кажется, но эти идеи для сайтов используются в любом обучающем материале. Это, действительно, достаточно универсальные, полезные и при этом не сложные идеи для реализации, и в обоих этих случаях мы будем использовать базы данных. Но мне захотелось придумать что-нибудь другое. Я как человек с язвенной болезнью должен следить за своим питанием, хоть в последнее время я за этим и не слежу, но в теории это должно быть так. Давайте напишем сайт, который будет состоять из двух приложений. Первое - раздел с блюдами, которые можно употреблять людям с язвой, второе - некая форма, состоящая из семи дней недели, в которой для каждого дня можно комбинировать блюда из первого раздела, тем самым составляя недельный рацион. Мне кажется, что приложение, в которое можно добавлять блюда и составлять из них рацион на неделю (хотя подозреваю, что таких приложений огромное множество) может быть кому-то полезно. И созданный нами сайт можно легко довести до ума, добавив возможность регистрации пользователей, создание личного кабинета каждого пользователя со своими вариантами блюд и рационом, ну и прочее, ведь все едят и многие следят за тем, что едят. Но поскольку это всего второй сайт ограничимся только знакомством с базами данных.
Панель администратора
Перейдем в PyCharm и создадим второй наш проект. Точно также как делали это в прошлый раз. У меня пока есть только представление как это выглядит в голове, нет никакого даже самого простенького наброска, поэтому начинаем мы с вами действительно с нуля.
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$ django-admin startproject dietforurcle
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite$ cd dietforurcle
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$ python manage.py startapp menu
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$ python manage.py startapp week
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$ ls
dietforurcle manage.py menu week
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'menu',
'week',
]
В консоли создадим новый проект и сразу же создадим два задуманных приложения и не забудем зарегистрировать их в файле settings.py.
После этого запустим сервер и перейдем по адресу http://127.0.0.1:8000/admin в прошлом проекте мы уже это делали.
Для авторизации нам требуется логин и пароль, предварительно их нужно создать. Для этого сначала выключим сервер комбинацией ctr + c.
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$ python manage.py createsuperuser
Username (leave blank to use 'tsarkoilya'): Admin
Email address:
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$
Делается это командой
python manage.py createsuperuser
Почту можно пропустить, а вот к паролю есть требования, их видно на примере, в учебном примере я все-равно использую простой пароль, но на реальных проектах, конечно, лучше прислушаться к советам по паролю от Django. Логин и пароль готовы, теперь можем запустить сервер снова и авторизоваться.
Увидим мы примерно следующее. Группы и пользователи. У нас есть какой-то функционал этих сущностей и в целом мы уже можем с ними взаимодействовать. Перейдем, например, в пользователей.
Перед нами таблица базы данных, в которой хранятся пользователи. Я не буду тут подробно останавливаться на устройстве баз данных в целом, я планирую написать материал по SQL, возможно к моменту, когда вы это читаете он уже написан. SQL на базовом уровне очень прост для освоения, и даже если вы с этим языком вообще не знакомы, то того, что мы увидим при создании этого сайта вам должно хватить для понимания устройства баз данных. Базы данных в программировании это таблицы, так база данных пользователей это таблица со столбцами, которые мы видим на примере (USERNAME, EMAIL ADDRESS, FIRST NAME, LAST NAME, STAFF STATUS), а строками соответственно пользователи с данными параметрами.
Весь backend сайтов основан на базах данных, это удобно и в этом несложно разобраться. Давайте создадим теперь свою таблицу в базе данных для приложения menu.
Модели
Таблицы в базе данных в django называются моделями и создаются модели в файле models.py.
PROJECT | models.py
|
forsite | from django.db import models
firstsite |
dietforurcle |
dietforurcle | class Menu(models.Model):
menu | title = models.CharField(max_length=100)
migrations | description = models.TextField(blank=True)
__init__.py | calories = models.IntegerField()
admin.py | img = models.ImageField(upload_to='menu/images', blank=True)
apps.py |
models.py |
tests.py |
views.py |
week |
db.sqlite3 |
manage.py |
venv |
Для этого перейдем в этот файл и создадим первую модель. Создаются в django модели с помощью классов. Наследуется этот класс от класса models. Model. Теперь, что касается самих столбцов, в данном случае мы создали 5 столбцов с соответствующими названиями и соответствующими типами. Типов столбцов достаточно много, так CharField тип используется для небольших текстов, а TextField для больших. IntegerField для чисел, ImageField для изображений, ну и так далее, со всеми типами можно ознакомиться в документации. У всех типов есть необязательные и обязательные параметры, один из которых max_length, внутри которого как понятно из названия мы прописываем максимально допустимую длину символов для этого столбца. Параметр upload_to у типа ImageField хранит путь до места, в котором будут храниться все добавляемые в это поле изображения.
Поcле создания модели ее нужно сынтегрировать с нашим приложением
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$ python manage.py makemigrations
Migrations for 'menu':
menu/migrations/0001_initial.py
- Create model Menu
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$
PROJECT | 0001_initial.py
|
| # Generated by Django 4.0.3 on 2022-03-08 16:02
|
forsite | from django.db import migrations, models
firstsite |
dietforurcle |
dietforurcle | class Migration(migrations.Migration):
menu |
migrations | initial = True
0001_initial.py |
__init__.py | dependencies = [
__init__.py | ]
admin.py |
apps.py | operations = [
models.py | migrations.CreateModel(
tests.py | name='Menu',
views.py | fields=[
week | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
db.sqlite3 | ('title', models.CharField(max_length=100)),
manage.py | ('description', models.TextField(blank=True)),
venv | ('calories', models.IntegerField()),
| ('img', models.ImageField(blank=True, upload_to='menu/images')),
| ],
| ),
| ]
Делается это командой
python manage.py makemigrations
Консоль говорит нам, что миграция готова. Файл 0001_initial.py, который создается автоматически, содержит таблицу с нашими полями и с дополнительным полем id, это поле в SQL мы должны создавать вручную, а django это делается за нас. Это поле нужно по большей части для взаимодействия таблиц друг с другом.
миграция создана, но она еще не подтверждена.
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, menu, sessions
Running migrations:
Applying menu.0001_initial... OK
(venv) tsarkoilya@tsarkoilya-NBLK-WAX9X:~/kavo/PycharmProjects/forsite/dietforurcle$
Сделаем это командой
python manage.py migrate
Среди списка системных миграций мы видим еще и нашу созданную миграцию menu. Как вы уже наверное поняли новая модель должна отображаться в панели администратора.
PROJECT | admin.py
|
forsite | from django.contrib import admin
firstsite |
dietforurcle | # Register your models here.
dietforurcle | from .models import Menu
menu |
migrations | admin.site.register(Menu)
0001_initial.py |
__init__.py |
__init__.py |
admin.py |
apps.py |
models.py |
tests.py |
views.py |
week |
db.sqlite3 |
manage.py |
venv |
Для того, чтобы зарегистрировать и отобразить таблицу в админке перейдем в файл admin.py, импортируем модель Menu из файла models.py и зарегистрируем ее командой
admin.site.register(имя модели)
Теперь запустим сервер, перейдем в админку и посмотрим, что изменилось.
Добавилась модель с именем Menus. Мы можем нажать на + Add и увидеть, что мы можем отредактировать пункты меню. У нас есть все наши поля, а именно: название, описание, рецепт, калорийность и изображение. Настоящие блюда и их описания мы своруем с какого-нибудь сайта позже, а сейчас придумаем что-нибудь простое.
Первое блюдо или вернее сказать первая строка таблицы под названием Menus создана. Все хорошо, кроме одного момента, если сейчас попытаться открыть изображение по ссылке в поле img мы получим ошибку, говорящую, что страница не найдена.
Первая причина, по которой это произошло, изображение сохранилось по пути menu/images/indeyka.png. Нам нужно изменить это, пусть хранилище всех медиа файлов проекта будет в одной папке, с таим же названием 'media'.
PROJECT | settings.py
|
forsite | ...
firstsite |
dietforurcle | MEDIA_URL = '/media/'
dietforurcle | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
menu |
media |
menu |
images |
indeyka.png |
week |
db.sqlite3 |
manage.py |
venv |
Добавим константу MEDIA_ROOT в файл settings.py, где и напишем путь, по которому мы будем хранить все файлы и еще константу MEDIA_URL. Это то название, которое будет подставляться в адресной строке, когда будет формироваться новый путь для нового изображения. Перезапустим сервер и пересохраним изображение вареной индейки. После этого в папке проекта создалась папка 'media', а уже внутри нее путь menu/images/indeyka.png.
Папку для всех медиафайлов мы создали, а вот динамически формирующиеся адреса для этих файлов еще нет. Перейдем в главный файл urls.py.
urls.py
...
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Импортируем settings и static и сформируем ссылку для каждого загружаемого изображения. Вернемся в админку, пересохраним изображения и попробуем по нему перейти.
Изображение индейки теперь открывается и имеет свой путь, который мы создали. Теперь можно создавать еще несколько блюд, с разными изображениями и каждое изображение сохранится в папке и получит свой уникальный путь. Вот этим базы данных и хороши, мы настроили этот момент всего однажды, потратили на него не так много времени, а пользоваться им теперь можно сколько потребуется. Представляете сколько времени экономится тем, что мы избавили себя от необходимости вручную помещать изображения в нужный каталог, так еще теперь и не возникнет никаких конфликтов связанных с путем и мы автоматически сформируем путь для каждого изображения. А более этого всем содержимым баз данных мы, конечно, можем пользоваться для отображения этого содержимого на экране. Этим теперь и займемся.
Отображаем информацию из баз данных на сайте. Знакомство с Django.templates
Как вы наверное помните к заглавной странице в файле urls.py мы можем обратиться пустыми одинарными кавычками. Давайте воспользуемся этим и отобразим всю информацию о вареной индейке на главной странице.
index.html | views.py
|
<h1>Что съесть</h1> | from django.shortcuts import render
| from .models import Menu
|
{% for meal in menu %} |
| # Create your views here.
<h2><b>{{ meal.title }}</b></h2> | def home(request):
| menu = Menu.objects.all()
<p> | return render(request, 'menu/index.html', {'menu': menu})
{{ meal.description }}<br> |
{{ meal.recipe }} |______________________________________________________________
</p> | urls.py
|
<img src="{{ meal.img.url }}"> | from menu import views
|
<p> Калорийность - {{ meal.calories }}</p> | urlpatterns = [
| path('admin/', admin.site.urls),
{% endfor %} | path('', views.home),
| ]
Рассмотрим пример. Первым делом вспомним как отображать информацию на главной странице. Импортируем файл views.py приложения menu в urls.py. Добавим еще одну функцию path, где путь будет вести на главную страницу, то есть пустые кавычки, а отображать по этому пути мы будем функцию написанную в файле views.py. Далее перейдем в файл views.py и создадим там функцию home, которая в обязательном порядке будет принимать параметр request. Возвращать эта функция будет функцию render, первым параметром в которой будет request, вторым шаблон, а третьим все объекты базы данных. Для того чтобы взять все объекты базы данным импортируем из файла models.py нашу базу данных Menu. Запись вида 'название базы данных.object.all()' возьмет все данные из базы данных. Сохраним всю эту информацию в переменную с одноименным названием, это условие не обязательно, но по мне так удобнее и добавим эту переменную в словарь в качестве значения, в качестве названия ключа я тоже предпочитаю тождественное слово. С этим закончили.
Теперь подробнее остановимся на файле index.html, путь до него templates/menu этот момент мы разбирали в прошлом проекте. Стандартный шаблонизатор в django имеет название django templates, мы можем использовать другие шаблонизаторы, например, jinja2, но пока остановимся на стандартном шаблонизаторе. Их синтаксис все-равно ничем не отличается. Итак, мы хотим взять все данные из базы данных. Для этого будем пробегаться по ней в цикле. Синтаксис цикла в шаблонизаторах выглядит так:
{% for x in (название переменной, в которой сохранены все объекты модели, (menu в нашем случае)) %}
Тело цикла
{% endfor %}
Далее, для обращения к объектам модели мы используем переменную 'x' в нашем случае 'meal' и через точку обращаемся к названию, которое мы придумали в файле models.py. Обращаемся мы ко всем этим объектам в двойных фигурных скобках. И к этим объектам применим стандартный html синтаксис. Таким образом обратимся ко всем объектам нашей модели. Обратите внимание на отображение изображения. Помещается изображение непосредственно в тег img, где в параметре src мы указываем шаблонное обращение к объекту модели типа ImageField, у нас этот объект назван img, помимо этого добавим через точку параметр url, для того чтобы передавался полный путь до изображения. Теперь откроем главную страницу и посмотрим, что получилось.
Увидим следующее. Я скачал другую картинку индейки, она мне больше нравится. Согласитесь, с базами данных работа над сайтом стала куда удобнее.
А что будет, если добавить еще одно блюдо в наше меню?
Добавим в меню вареную курицу.
<h1>что съесть</h1>
{% for meal in menu %}
<h2><b>{{ meal.title }}</b></h2>
<p>
{{ meal.description }}
{{ meal.recipe }}
</p>
<style>
img {
width: 400px;
height: 300px;
border: 1px solid black;
}
</style>
<img src="{{ meal.img.url }}">
<p> Калорийность - {{meal.calories }} </p>
{% endfor %}
Добавим в шаблон форматирование картинок, чтобы они все были равного размера. Пока что сделаем это прям внутри html разметки. И перейдем на главную страницу снова.
Теперь она выглядит следующим образом. Каждая новая запись внесенная в базу данных теперь будет отображаться в одинаковом формате. Достаточно изменить разметку файла index.html и все записи добавленные в базу данных отображаться в заданном формате. Очень удобно.
Внешний вид. Статические файлы
Давайте теперь добавим несколько блюд, я удалил поле с рецептом, все-таки мы тут учим django, а не создаем меню, которым кто-то будет пользоваться, поэтому для экономии времени ограничимся четырьмя полями.
from django.db import models
class Menu(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
calories = models.IntegerField()
img = models.ImageField(upload_to='menu/images', blank=True)
И еще добавил необязательный параметр blank в значении True, это будет означать, что поле теперь необязательно для заполнения.
Сами варианты блюд, изображения и прочее я сворую в интернете.
По итогу, я утомился даже от добавления девяти разновидностей меню, кажется, готовить я точно никогда не начну. Ладно работаем с тем, что есть, может позже добавлю еще что-нибудь.
project | index.html | style.css
| |
dietforucle | {% load static %} | .menu_img {
media | <head> | width: 350px;
menu | <title>Menu</title> | height: 250px;
migrations | <link rel="stylesheet" href="{% static 'menu/style.css' %}"> | border: 1px solid black;
static | </head> | }
menu | <body> |
style.css | <h1>что съесть</h1> | .box {
templates | {% for meal in menu %} | width: 80%;
menu | <div class="box"> | height: 280px;
index.html | <div class="text"> | border: 2px solid black;
__init__.py | <h2><b>{{ meal.title }}</b></h2> | display: flex;
admin.py | <p class="description"> | justify-content: space-between;
apps.py | {{ meal.description }} | border-radius: 25px;
models.py | </p> | background-color: white;
tests.py | <p> Калорийность - {{meal.calories }} </p> | margin-left: 5px;
models.py | </div> | }
venv | <div class="img"> |
week | <img src="{{ meal.img.url }}" class="menu_img"> | h2 {
db.sqlite3 | </div> | font-size: 35px;
manage.py | </div> | }
| {% endfor %} |
| </body> | .text {
| margin-left: 15px;
| }
|
| .img {
| margin-top: 15px;
| margin-right: 15px;
| }
|
| h1 {
| font-size: 45px;
| margin-left: 25px;
| font-weight: 900;
| }
|
| .description {
| font-size: 25px;
| }
|
| body {
| background-color: rgb(216, 201, 201);
| }
Добавил чуть-чуть внешнего вида. Это в данном случае нужно в первую очередь для навыка работы со статическими файлами.
Итак, статические файлы - любые файлы, к которым мы обращаемся не при помощи шаблонизатора, а просто помещаем их в проект из папки static, такими файлами могут быть изображения используемые, например, в качестве логотипа, иконка сайта, какой-нибудь pdf файл содержащий скажем политику конфиденциальности и прочие файлы. Почему из папки static?
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = 'static/'
Потому что в файле settings.py по умолчанию STATIC_URL равен 'static/', замените это имя на другое и тогда статические файлы будет нужно помещать в папку с новым именем переменной STATIC_URL, но зачем вам это нужно, static общепринято, понятно и удобно. Для структурирования файлов в папке static придерживайтесь такого же правила, как и для папки templates, называйте первую вложенную папку так же как называется ваше приложение. После этого условия размещайте статические файлы как угодно.
Для добавления статических файлов в html файл добавьте строку {% load static %}
и добавьте ее раньше, чем будут использованы сами файлы, например, добавьте эту строку в самом начале файле. css подключается к html стандартно через тег link, за тем исключением, что в параметр href мы указываем путь к файлу с помощью записи {% static 'путь к файлу, имя папки static мы, конечно, игнорируем' %}
. На остальных изменениях html файла заострять внимание не будем, просто привел файл к более менее сносной структуре. На css тоже заострять внимание не станем, тем более ничего особенного я тут придумывать не стал, такой css файл это 10 минут работы.
Сделаю скриншот допустим из середины страницы. Получилось примерно следующее, ничего особенного, но этим я еще раз хотел показать насколько удобно использовать базы данных, теперь каждая новая запись в таблице Menus будет отображаться на странице в таком же виде как и остальные записи.
Второе приложение
Для второго приложения базы данных нам не понадобятся, но для практики и тут воспользуемся ими.
project | models.py
|
dietforucle | from django.db import models
media |
week |
migrations | # Create your models here.
static | class Week(models.Model):
week | day_of_week = models.CharField(max_length=20)
style.css |
templates |__________________________________________________
week | admin.py
index.html |
__init__.py | from django.contrib import admin
admin.py | from .models import Week
apps.py |
models.py |
tests.py | # Register your models here.
models.py | admin.site.register(Week)
venv |
menu |
db.sqlite3 |
manage.py |
Поэтому, для начала создадим модель и зарегистрируем ее. Пусть модель Week содержит одно поле, в этом поле будет храниться день недели.
После этого вернемся в админку и добавим семь записей в модель Weeks. Каждая эта запись будет содержать только день недели.
Не забудьте воспользоваться командами makemigrations и migrate для добавления модели в БД.
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('menu.urls')),
path('week/', include('week.urls')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
После этого вернемся в файл urls.py и изменим его. Когда приложений появляется больше чем одно возникает проблема связанная с главным файлом urls.py. В каждом приложении файл с отображением стандартно имеет название views и обращаться к функциям этого файла разных приложений через views. становится невозможно. Более того приложений и путей в этих приложениях у большого проекта может быть огромное множество, и по итогу файл urls.py может оказаться сильно загроможденным. Для этого принято разграничивать пути по соответствующим приложениям. Для этого существует функция include(), перед использованием не забудьте ее импортировать из django.urls. В эту функцию мы передаем путь к файлу urls.py нашего приложения, предварительно файл urls.py необходимо создать в каждом приложении. Первым же параметром функции path() будет являться 'главный' адрес приложения. Создадим эти файлы и заполним их.
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
]
from django.urls import path
from . import views
urlpatterns = [
path('', views.days_of_week, name='days_of_week'),
]
Сделаем это таким образом. Теперь на главной странице (http://127.0.0.1:8000/) будет располагаться приложение Menu, а по адресу (http://127.0.0.1:8000/week) будет располагаться приложение week.
from django.shortcuts import render
from .models import Week
# Create your views here.
def days_of_week(request):
week = Week.objects.all()
return render(request, 'week/index.html', {'week': week})
Создадим представление для views.py приложения week, точно так же как мы делали раньше.
{% load static %}
<head>
<title>Week</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'week/style.css' %}">
</head>
<body>
<p class="text">Составьте меню на неделю из вариантов представленных на сайте</p>
<button type="button"><a href="{% url 'home' %}">Вернуться к списку блюд</a></button>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
{% for day in week %}
<th scope="col">{{ day.day_of_week }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Завтрак</th>
<td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td>
</tr>
<tr>
<th scope="row">Обед</th>
<td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td>
</tr>
<tr>
<th scope="row">Перекус</th>
<td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td>
</tr>
<tr>
<th scope="row">Ужин</th>
<td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td><td>Выбрать</td>
</tr>
</tbody>
</table>
</body>
.table {
margin-top: 200px;
font-size: 20px;
border-bottom: 2px solid black;
border-top: 2px solid black;
background-color: white;
}
.text {
font-size: 45px;
font-weight: 900;
text-align: center;
}
body {
background-color: rgb(216, 201, 201);
}
button {
display: block;
margin-left: auto;
margin-right: auto;
color: black;
font-size: 20px;
border-radius: 15px;
border: 2px solid black;
padding: 10px;
background-color: white;
}
a {
color: black;
font-weight: 900;
}
Для отображения второго приложения возьмем таблицу из bootstrap и немного ее изменим, тоже ничего необычного. Обратить внимание хотелось бы обратить тут только на использование баз данных для оглавления столбцов таблицы. В остальном останавливаться тут не на чем. Также я добавил по кнопке для каждого приложения со ссылкой на другое для перехода между ними, но опять же подобное мы уже делали в первом сайте. Также файлы index.html и style.css приложения menu я также немного изменил, но совсем по мелочи, напоминаю, весь код можно найти на моем github. Давайте посмотрим, что из этого вышло.
На заглавной странице прежнее приложении, только добавилась кнопка, которая переадресовывает нас на страницу второго приложения.
Выглядит она вот так. И все также кнопка, которая возвращает нас уже к первому приложению.
Осталось как-то запрограммировать кнопки 'выбрать'. Постараемся сделать это максимально примитивным способом, чтобы не перегружать материал посвященный второму сайту.
{% load static %}
<head>
<title>Week</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'week/style.css' %}">
</head>
<body>
<p class="text">Составьте меню на неделю из вариантов представленных на сайте</p>
<button type="button"><a href="{% url 'home' %}">Вернуться к списку блюд</a></button>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
{% for day in week %}
<th scope="col">{{ day.day_of_week }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Завтрак</th>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
</tr>
<tr>
<th scope="row">Обед</th>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
</tr>
<tr>
<th scope="row">Перекус</th>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
</tr>
<tr>
<th scope="row">Ужин</th>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
<td><select name="Выбрать">{% for meal in menu %}<option>{{ meal.title }}</option>{% endfor %}</select></td>
</tr>
</tbody>
</table>
</body>
Например вот так. Добавим на месте каждого слова 'Выбрать' выпадающий список, элементами которого будут поля title модели Menu.
from django.shortcuts import render
from .models import Week
from menu.models import Menu
# Create your views here.
def days_of_week(request):
week = Week.objects.all()
menu = Menu.objects.all()
return render(request, 'week/index.html', {'week': week, 'menu': menu})
А для того, чтобы пользоваться этой моделью приложения Menu в html файле приложения week достаточно импортировать ее в файл views.py приложения week, обратиться к ее содержимому с помощью objects.all() и зарегистрировать эту переменную в словаре функции render. Вот так просто можно пользоваться любыми моделями внутри любого приложения. Ну и теперь посмотрим на финальный вариант нашего приложения week.
Теперь на месте слова выбрать появился выпадающий список, при раскрытии которого мы видим названия всех добавленных блюд в приложение menu. При чем название каждого нового добавленного блюда в модель Menu будет отображаться в этом выпадающем списке.
Второй сайт готов
При создании второго сайта мы закрепили материал освоенный в прошлом сайте и немного расширили свои знания, а также познакомились с моделями и панелью администратора. Как упоминалось выше подобный сайт можно довести до ума, посидев над ним денек-два и, конечно, обладая необходимыми навыками и на его примере можно объяснить еще многое, но на это у нас есть будущие проекты. Главная цель этого сайта показать насколько сильно модели важны при написании сайтов и насколько удобны они в использовании. В следующем сайте постараемся детальнее останавливаться на теории.
Весь код этого сайта можно найти на моем github.
Регистрация. Третий сайт.
В третьем сайте уделим внимание форме регистрации и хранению данных зарегистрированных пользователей. Идея для сайта пришла в голову во время работы над вторым сайтом, третий сайт будет похож по своей идее, но более практичен и полезен для программистов. Создадим сайт с регистрацией, а в личном кабинете каждого пользователя добавим возможность добавлять библиотеки python, которые хотелось бы выучить. Более подробно о функционале и возможностях этого сайта поговорим уже в процесс создания. А пока просто создадим пустую папку с новым проектом pythonpackagelearn. При создании боевых проектов также советую проверять, нет ли уже на github проекта с названием, которое вы придумали.
Регистрация пользователей
В любом только что созданном проекте уже есть некоторый предустановленный функционал для авторизации пользователей.
Именно из этого приложения мы будем импортировать необходимые инструменты для работы с авторизацией. Но, конечно, наличия одного этого приложения нам недостаточно.
Начнем стандартно с создания и регистрации приложения package.
from django.contrib import admin
from django.urls import path
from package import views
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
]
from django.shortcuts import render
def signupuser(request):
return render(request, 'package/signupuser.html')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Создать аккаунт</h1>
</body>
</html>
Затем зарегистрируем адрес signup/, создадим функцию signupuser и файл signupuser.html. Пока ничего нового.
И видим мы по этому адресу ожидаемый результат. Теперь внесем несколько изменений и воспользуемся встроенной в django формой регистрации.
from django.shortcuts import render
from django.contrib.auth.forms import UserCreationForm
def signupuser(request):
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Создать аккаунт</h1>
{{ registerform }}
</body>
</html>
Напишем следующее. Импортируем из django.contrib.auth.forms форму регистрации UserCreationForm. А после добавим эту форму в словарь функции render и передадим ключ этого словаря в шаблон регистрации.
Теперь по тому же адресу мы видим форму регистрации содержащую строку для ввода логина и два поля для первичного ввода пароля и для его подтверждения. Помимо этого есть текст с некоторыми правилами и описанием для этих полей. После этого остановим сервер и воспользуемся командой createsuperuser, панель администратора в этом сайте нам, конечно, понадобится. И не забудем произвести миграции, хоть ни одной собственной модели еще не создано, но команда migrate при только что созданном проекте необходима.
Кстати, больше всего 'базовых' миграций связаны именно с авторизационным приложением. Перезапустим сервер и зайдем в панель администратора под администраторскими данными.
В базе 'Users' будет содержаться информация об одном пользователе, только что созданном админе.
Теперь немного кастомизируем шаблон и саму форму, чтобы вы не пугались, что этот вид единственный возможный у стандартной формы регистрации.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Создать аккаунт</h1>
<form method="POST">
Введите имя <br> {{ registerform.username }}<br>
Введите пароль <br> {{ registerform.password1 }}<br>
Подтвердите пароль <br> {{ registerform.password2 }}<br><br>
<button type="submit">Регистрация</button>
</form>
</body>
</html>
У полей логина и пароля есть имена, для логина - username, для первого пароля - password1, а для второго - password2. Мы можем воспользоваться ими для легкой кастомизации формы регистрации. Существует еще запись 'имя_формы_регистрации.as_p' (в нашем случае было бы registerform.as_p), такая запись создала бы пробелы между блоками регистрации, пробелы это следствие обертывания компонентов формы в тег p, попробуйте воспользоваться им, может и этого покажется достаточно, p, как я упомянул ранее, означит тег p, можно написать также as_li и тогда компоненты формы будут представлены через тег li, а еще есть .as_table, такая запись отобразит компоненты формы как ячейки таблицы, а конкретно в виде тегов tr. Также обернем всю форму в тег form с методом протокола HTTP - POST. И добавим кнопку для подтверждения регистрации.
Вернемся по адресу signup/ и посмотрим, что получилось. Теперь форма выглядит более привычно. Попробуем зарегистрировать нового пользователя.
При попытке регистрации мы увидим следующую ошибку. В этой ошибке говорится о недостающем CSRF токене, и предлагается запись для добавления его в шаблон. CSRF - вид атаки на посетителей веб-сайтов, заключается в создании визуально похожей формы на подменном сайте с целью сбора данных пользователей, и django заботится о безопасности наших посетителей самостоятельно.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Создать аккаунт</h1>
<form method="POST">
{% csrf_token %}
Введите имя <br> {{ registerform.username }}<br>
Введите пароль <br> {{ registerform.password1 }}<br>
Подтвердите пароль <br> {{ registerform.password2 }}<br><br>
<button type="submit">Регистрация</button>
</form>
</body>
</html>
Вернемся в шаблон и добавим csrf токен в него. Теперь ошибки не будет, но разумеется кнопка 'регистрация' сейчас не создаст нового пользователя. Реализуем мы этот функционал в файле views.py. Но перед этим вспомним разницу между HTTP методами GET и POST.
GET мы должны использовать, когда запросы не влияют на изменение системы, так, например, в первом сайте мы использовали GET, поскольку генерируемые данные нигде не хранились, также информация о GET запросах обычно отображается в адресной строке, это тоже важный момент связанный с конфиденциальностью, о котором не стоит забывать. Также запомните каждый раз, когда вы пишете какой-нибудь запрос в адресной строке и нажимаете ввод вы используйте GET метод.
POST нужно использовать в остальных случаях, то есть тогда, когда вместе с запросом должны произойти какие-нибудь изменения на серверной части сайта. Добавление нового пользователя является ярким примером такого запроса.
Теперь, когда мы понимаем разницу, реализуем возможность регистрации.
from django.shortcuts import render
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
def signupuser(request):
if request.method == 'GET':
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm})
else:
if request.POST['password1'] == request.POST['password2']:
user = User.objects.create_user(requests.POST['username'], password=request.POST['password1'])
user.save()
Начнем с такого кода. Как упоминалось выше, при переходе на страницу используется метод GET, воспользуемся этим пониманием и при переходе по адресу signup/ будем показывать пользователю страницу с формой регистрации. В противном случае, то есть в случае использования метода POST, мы явно указали метод POST для формы в шаблоне. Так вот, в случае POST мы будем создавать объект пользователя, для этого импортируем модель User из django.contrib.auth.models, а после воспользуемся функцией create_user(). Первым параметром передадим в нее значение поля username, а вторым - значение поля password1, ранее мы разобрались, что это за поля. Присваивать только что созданного пользователя будем переменной user, а после сохранять его функцией save(). Помимо этого перед созданием нового пользователя сделаем проверку совпадения паролей в полях password1 и password2.
Попробуем создать пользователя теперь.
Введем логин и одинаковый пароль.
Мы увидим ошибку, в которой будет сказано, что мы ничего не возвращаем. Действительно сейчас мы только создаем нового пользователя, но после этого действия мы никуда не перенаправляемся или хотя бы не остаемся на той же самой странице, у нас пока просто-напросто не написано никакого подобного поведения. Но если мы теперь снова вернемся в панель администратора и перейдем в базу пользователей.
Мы увидим, что только что созданный пользователь в этой базе появился.
Мы создали работающую форму регистрации.
Дорабатываем форму регистрации. Обработка ошибок
Осталось еще несколько важных моментов связанных с регистрацией пользователей. Например, сейчас у нас написано условие проверки тождественности паролей, но поведение для ситуации, при которой пароли не совпадают, у нас не написано.
from django.shortcuts import render
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
def signupuser(request):
if request.method == 'GET':
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm})
else:
if request.POST['password1'] == request.POST['password2']:
user = User.objects.create_user(requests.POST['username'], password=request.POST['password1'])
user.save()
else:
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm, 'error': 'Пароли не '
'совпадают'})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Регистрация</title>
</head>
<body>
<h1>Создать аккаунт</h1>
<h2>{{ error }}</h2>
<form method="POST">
{% csrf_token %}
Введите имя <br> {{ registerform.username }}<br>
Введите пароль <br> {{ registerform.password1 }}<br>
Подтвердите пароль <br> {{ registerform.password2 }}<br><br>
<button type="submit">Регистрация</button>
</form>
</body>
</html>
Исправить это можно следующим образом. Добавим в условие else вывод той же самой страницы регистрации, но добавим в словарь функции render() новое значение с ключом, например, 'error', а значением этого ключа будет сообщение говорящее о несовпадении паролей. И выводить это значение в том шаблоне, который вызывается при попадании в оператор else.
И теперь при попытке регистрации с неверным паролем мы снова попадаем на страницу регистрации, только теперь на ней появляется сообщение об ошибке.
Также у нас осталась еще одна проблема, при попытке создания пользователя с именем, которое уже есть в системе мы также получаем ошибку.
Ошибку типа IntegrityError. Ошибка сообщает нам, что имя неуникально, но хотелось бы сообщать об этом и пользователям. Мы можем обработать этот тип ошибки.
from django.shortcuts import render
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.db import IntegrityError
def signupuser(request):
if request.method == 'GET':
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm})
else:
if request.POST['password1'] == request.POST['password2']:
try:
user = User.objects.create_user(requests.POST['username'], password=request.POST['password1'])
user.save()
except IntegrityError:
return render(request, 'package/signupuser.html',
{'registerform': UserCreationForm, 'error': 'Данное имя уже используется'})
else:
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm, 'error': 'Пароли не '
'совпадают'})
Воспользуемся для ее обработки блоками try и except, а возвращать в случае возникновения ошибки тот же шаблон регистрации с ошибкой, но текст ошибки при этом изменим.
Теперь при попытке создания пользователя с именем, которое уже зарегистрировано в базе данных мы увидим соответствующее сообщение об ошибке.
Регистрация. Авторизация. Выход
Что осталось еще? Нам явно не хватает одной только страницы с регистрацией, зачастую на сайтах после регистрации нас перенаправляют в личный кабинет, откуда есть возможность выхода. А вообще нам не помешала бы домашняя страница, на которой можно зарегистрироваться либо авторизоваться. Кстати, возможности авторизации у нас по-прежнему нет. Давайте разберемся со всеми этими моментами.
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.contrib.auth import login
def signupuser(request):
if request.method == "GET":
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm()})
else:
if request.POST['password1'] == request.POST['password2']:
try:
user = User.objects.create_user(request.POST['username'], password=request.POST['password1'])
user.save()
login(request, user)
return redirect('personal_cabinet')
except IntegrityError:
return render(request, 'package/signupuser.html',
{'registerform': UserCreationForm(), 'error': 'Данное имя уже используется'})
else:
return render(request, 'package/signupuser.html',
{'registerform': UserCreationForm(), 'error': 'Пароли не совпадают'})
def personal_cabinet(request):
return render(request, 'package/personal_cabinet.html')
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
path('personal_cabinet/', views.personal_cabinet, name='personal_cabinet'),
]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Личный кабинет</title>
</head>
<body>
<h1>Личный кабинет</h1>
</body>
</html>
Начнем с создания страницы личного кабинета. Пока просто создадим путь для нее в urls.py и напишем функцию для этого пути, которая будет ссылаться на шаблон, который пока содержит всего одну надпись. После этого в функции signupuser добавим две строки, первым делом обратимся к заранее импортированной функции login(), эта функция присоединяет пользователя к текущей сессии, принимать эта функция должна объект Http запроса и объект User. После присоединения нового пользователя к сессии будем перенаправлять этого пользователя на только что созданную страницу личного кабинета. Для этого воспользуемся функцией redirect, не забудьте сначала ее импортировать, принимает эта функция имя страницы, на которую нужно перенаправиться.
Теперь после введения данных пользователя и нажатия на кнопку 'Регистрация', которых еще нет в базе данных, нас будет перенаправлять на страницу личного кабинета.
Теперь мы авторизованы на сайте под данными пользователя vova, давайте попробуем теперь перейти в панель администратора.
При попытке входа в панель администратора мы увидим сообщение, что мы пытаемся войти как vova, поэтому для использования кабинета администратора пользователь должен быть создан именно командой createsuperuser, у других пользователей, как видно, доступа к этому кабинету нет.
Хорошо, личный кабинет, хоть пока и пустой, мы сделали. Вспомните, что обычно отображается в каждом личном кабинете? В каждом, или почти в каждом, личном кабинете где-нибудь вверху отображается статус авторизованности, обычно это просто ваш никнейм, под которым вы авторизовались. Давайте сделаем то же самое и на нашем сайте.
В данном сайте я пока не задействовал имя index.html (либо base.html) для какого-нибудь шаблона. Дело в том, что это имя принято использовать как имя дла главного шаблона сайта, в этом шаблоне обычно предписаны какие-то настройки интерфейса общие для каждой страницы сайта. А все остальные шаблона наследуются от этого главного шаблона. Давайте создадим такой шаблон.
<html lang="ru">
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
{% extends 'package/index.html' %}
{% block title %}Регистрация{% endblock %}
{% block content %}
<div class="loginuser">
<h1>Создать аккаунт</h1>
<h2>{{ error }}</h2>
<form method="POST">
{% csrf_token %}
Введите имя <br> {{ registerform.username }}<br>
Введите пароль <br> {{ registerform.password1 }}<br>
Подтвердите пароль <br> {{ registerform.password2 }}<br><br>
<button type="submit">Регистрация</button>
</form>
</div>
{% endblock %}
{% extends 'package/index.html' %}
{% block title %}Личный кабинет{% endblock %}
{% block content %}
<h1>Личный кабинет</h1>
{% endblock %}
Пока не станем добавлять ничего нового, а просто добавим шаблон index.html и отредактируем наши шаблоны в зависимости от этого шаблона. Перенесем всю стандартную структуру html:5 документа в этот базовый шаблон. А после создадим в нем два блока, первый для тега title, второй для содержимого тега body. Блоки всегда имеют одинаковую структуру -
{% block 'имя блока(придумываем сами)' %}{% endblock %}
.
Взглянем на блок для title, теперь в наследуемых шаблонах нам достаточно поместить внутрь этого блока желаемый заголовок и этот заголовок будет подставлен внутрь тега title базового шаблона. Для того чтобы унаследоваться от главного шаблона используется запись -
{% extends 'путь к базовому шаблону' %}
.
С контентным блоком ситуация аналогичная.
Теперь расширим базовый шаблон, добавив информацию, которая будет отображаться на каждой странице.
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% if user.is_authenticated %}
<p>Вы вошли как {{ user.username }}</p>
<button><a href="#">Выйти</a></button>
{% else %}
<button><a href="#">Зарегистрироваться</a></button>
<button><a href="#">Войти</a></button>
{% endif %}
{% block content %}
{% endblock %}
</body>
</html>
Сделаем это следующим образом. функция is_authenticated() возвращает True в случае авторизованности на сайте. Пускай в случае авторизованности мы будем видеть текст - 'Вы вошли как (имя пользователя)' и кнопку с выходом из учетной записи, а в противном случае будем видеть кнопку регистрации и авторизации. Ссылки пока забьем заглушками.
Теперь вернемся например на страницу регистрации.
Сверху теперь мы видим информацию о статусе авторизованности и кнопку выхода, то же самое мы увидим перейдя на страницу личного кабинета.
Теперь давайте сделаем кнопку выхода работающей. Куда должна перенаправлять нас кнопка выхода из учетной записи? Скорее всего на домашнюю страницу сайта, на данный момент такой страницы у нас нет и мы можем перенаправляться на страницу регистрации, но поскольку домашняя страница нам все-равно понадобится давайте сразу создадим ее.
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
path('personal_cabinet/', views.personal_cabinet, name='personal_cabinet'),
path('logout/', views.logoutuser, name='logoutuser'),
path('', views.home, name='home'),
]
... def home(request): return render(request, 'package/home.html')
{% extends 'package/index.html' %}
{% block title %}PyPaLearn{% endblock %}
{% block content %}
<h1>PyPaLearn</h1>
<p class="text">Для того, чтобы называть себя python разработчиком, недостаточно знать 'голый' язык
программирования, необходимо также быть знакомым хотя бы с небольшим набором библиотек
этого языка.</p>
<p>На этом сайте вы можете создать свой собственный список библиотек, которые вы планируете освоить.</p>
{% endblock %}
Создадим стартовую страницу с небольшим описанием сайта, сократим длинное название pythonpackagelearn до PyPaLearn. Делали мы это уже много раз, поэтому останавливаться тут не на чем.
Помимо этого сразу создадим путь для страницы logout/ и перейдем к написанию функции для этого пути.
...
from django.contrib.auth import login, logout
...
def logoutuser(request):
if request.method == 'POST':
logout(request)
return redirect('home')
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% if user.is_authenticated %}
<p>Вы вошли как {{ user.username }}</p>
<form method="POST" action="{% url 'logoutuser' %}">
{% csrf_token %}
<button type="submit">Выйти</button>
</form>
{% else %}
<button><a href="#">Зарегистрироваться</a></button>
<button><a href="#">Войти</a></button>
{% endif %}
{% block content %}
{% endblock %}
</body>
</html>
Функцию опишем следующим образом. Импортируем функцию logout() и выполняться эта функция будет только в случае инициализации POST метода, как вы помните при любом вводе запроса в адресной строке инициализируем GET метод, поэтому POST указать нам нужно явно в шаблоне. И после выхода из учетной записи мы будем переадресовываться на домашнюю страницу. Не забудьте про {% csrf_token %}.
После обернем кнопку выхода в форму, где действием будет переадресация на path() с именем 'logoutuser'. Теги 'a' можно убрать, они тут лишние.
Теперь при нажатии кнопки 'Выйти' мы будем выходить из аккаунта и перенаправляться на домашнюю страницу.
Осталось разобраться с авторизацией.
from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.contrib.auth import login, logout, authenticate
...
def loginuser(request):
if request.method == "GET":
return render(request, 'package/loginuser.html', {'authenticationform': AuthenticationForm()})
else:
user = authenticate(request, username=request.POST['username'], password=request.POST['password'])
if user is None:
return render(request, 'package/loginuser.html', {'authenticationform': AuthenticationForm(),
'error': 'Пользователь с такими данными не найден. '
'Проверьте правильность введенных данных.'})
else:
login(request, user)
return redirect('personal_cabinet')
...
...
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
path('personal_cabinet/', views.personal_cabinet, name='personal_cabinet'),
path('logout/', views.logoutuser, name='logoutuser'),
path('', views.home, name='home'),
path('login/', views.loginuser, name='loginuser'),
{% extends 'package/index.html' %}
{% block title %}Авторизация{% endblock %}
{% block content %}
<h1>Авторизация</h1>
<h2>{{ error }}</h2>
<form method="POST">
{% csrf_token %}
Введите имя <br> {{ authenticationform.username }}<br><br>
Введите пароль <br> {{ authenticationform.password }}<br><br>
<button type="submit">Войти</button>
</form>
{% endblock %}
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{% if user.is_authenticated %}
<p>Вы вошли как {{ user.username }}</p>
<form method="POST" action="{% url 'logoutuser' %}">
{% csrf_token %}
<button type="submit">Выйти</button>
</form>
{% else %}
<button><a href="{% url 'signupuser' %}">Зарегистрироваться</a></button>
<button><a href="{% url 'loginuser' %}">Войти</a></button>
{% endif %}
{% block content %}
{% endblock %}
</body>
</html>
Первым делом сделаем, чтобы кнопка 'Зарегистрироваться' вела на signupuser/ и займемся кнопкой 'Войти'.
Для начала добавим еще одну функцию path() для loginuser. И напишем функцию loginuser. Для авторизации также будем пользоваться встроенной формой - AuthenticationForm, импортируем ее также из django.contrib.auth.forms, ее поля имею название username - для логина и password - для пароля. В случае GET метода мы будем отправлять на страницу авторизации, в случае POST - будем пользоваться функцией authenticate, импортируется она из django.contrib.auth, первым параметром эта функция принимает request, далее идут username и password. Эта функция, как вы наверное догадались, ищет есть ли такие данные в базе данных. Возвращает эта функция объект User. Присвоим результат работы этой функции переменной user, далее в случае отсутствия введенных данных будем возвращать пользователя на ту же страницу авторизации, но дополнительно выведем сообщение ошибки. А в случае наличия введенных данных в базе данных будем применять функцию login() и перенаправлять пользователя в личный кабинет.
Шаблон для loginuser будет почти такой же, как и для signupuser, заменим заголовок, содержимое тега h1 ну и поля ввода данных будем брать уже от AuthenticationForm. В шаблоне index.html кнопка войти пусть перенаправляет нас на loginuser/.
Теперь посмотрим, что получилось.
При попытке авторизации под неверными данными мы видим сообщение о неправильности введенных данных, а в случае верных данных мы перенаправляемся в личный кабинет.
Таким образом, мы создали с вами полностью работающую систему аутентификации с возможностью регистрации, авторизации и выхода из системы.
Модель. Углубление в базы данных
Мы создали форму регистрации. Теперь мы должны приступить к созданию модели для работы с библиотеками, которые хотелось бы изучить конкретному авторизованному на сайте пользователю. Мы понимаем как создаются модели хранящие какие-нибудь записи, пусть мы знакомы не со всеми видами полей, но это мы еще наверстаем, но есть одно поле, которые заметно выделяется на фоне других. Это поле - ForeignKey или внешний ключ. Этот внешний ключ необходим для взаимодействия между базами данных. Давайте немного отвлечемся и разберемся боле подробно с базами данных здесь.
По умолчанию любой новый созданный проект на django использует СУБД (СУБД - система управления базами данных) - sqlite3. Также в папке с проектом есть файл db.sqlite3, в этом файле и хранится вся база данных нашего проекта. Давайте заглянем внутрь этой базы и посмотрим как это выглядит.
Для установки sqlite3 и программу для просмотра баз используйте следующие команды:
sudo apt update
sudo apt install sqlite3
sudo apt install sqlitebrowser
Далее откройте у себя на устройстве появившееся после установки приложение DB Browser for SQLite.
Вы увидите перед собой такое окно, сначала оно будет пустое, нажмите Open Database и выберите файл db.sqlite3 нашего проекта. Мы увидим 11 таблиц, это те таблицы, которые по умолчанию содержит каждый проект. Обратите внимание на столбец Schema, в этом столбце написана схема создания этой конкретной таблицы и у каждой из этих таблиц есть поле 'id'. При работе с чистым SQL это поле мы создаем вручную, а при работе с базами данных в django оно создается автоматически. Обычно это поле имеет параметры представленные на скриншоте, а именно: название - id, тип данных этого поля - integer, то есть целое число, NOT NULL - означает, что поле не может оставаться пустым и PRIMARY KEY AUTOINCREMENT - автозаполняемый первичный ключ, автозаполнение обычно происходит в стандартной целочисленной положительной последовательности.
Теперь давайте откроем какую-нибудь таблицу, например auth_user, где хранится информация о наших зарегистрированных пользователях.
Для этого перейдите в Browse Data и в списке таблиц выберите auth_user. Мы видим трех пользователей сайта, два, которых мы регистрировали через форму и один админ. Интересно нам тут поле id, как видно оно заполняется от единицы и далее, у каждой новой записи в этой таблице это значение будет увеличиваться на один, как и в других таблицах.
Теперь, когда мы немного заглянули внутрь баз данных, вернемся к проекту для создания новой модели, а после обратно возвратимся в DB Browser for SQLite. И думаю теперь для всех окончательно стало понятно, что базы данных это обычные таблицы.
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Package(models.Model):
title = models.CharField(max_length=150)
description = models.TextField(blank=True)
url = models.URLField(blank=True)
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
from django.contrib import admin
from .models import Package
# Register your models here.
admin.site.register(Package)
Создадим для начала такую модель. С CharField и TextField мы уже знакомы. Кстати, параметр max_length поля CharField является обязательным и если его не заполнить это значение будет равно 200. Далее идет, ранее не использованное, поле URLField, как понятно из названия это поле для ссылки, и это поле наследуется от CharField, так что параметр max_length для него также является обязательным, в данном случае оно осталось пустым значит максимальная длина ссылки будет 200 символов. И последнее поле ForeignKey, для его использования импортируем модель User, это та самая таблица auth_user. В этом поле написано, что мы будем прикреплять запись к конкретному пользователю, и прикрепляться эта запись будет именно по id пользователя. Параметр on_delete=models.CASCADE означает каскадное удаление всех объектов ссылающийся на этот объект. В данном случае, при удалении пользователя будут удалены все записи этого пользователя.
Метод __str__ написанный ниже отвечает за отображение записей модели в панели администратора. Сейчас покажу, что я имею в виду. И не забудьте зарегистрировать модель в admin.py после чего произвести миграции.
Теперь запустим сервер и перейдем в панель администратора.
Создадим новую запись в этой модели и прикрепим ее к пользователю vova.
Отображается запись в этой модели как Django, за это и отвечает метод __str__.
Итак, запись в модели мы создали, теперь вернемся в DB Browser for SQLite и взглянем на эту таблицу.
Перед нами таблица со всеми нашими полями, еще тут видно поле created, которое при создании модели мы описали, но при создании записи в этой модели в админке мы не видели этого поля. Произошло это потому, что это поля заполняется автоматически и мы не можем его менять. Но интересует нас тут другое. Поле id и user_id, содержат эти поля цифру 3, хотя запись в таблице всего одна. Это и есть та самая связь с помощью поля ForeignKey, если вернуться к скриншоту таблицы auth_user мы увидим, что vova имеет id 3. Таким образом сайт будет понимать какие записи нужно отобразить в личном кабинете каждого конкретного пользователя.
Давайте создадим еще одну запись привязанную к Вове и посмотрим как это повлияет на таблицу.
user_id по прежнему равен 3, все логично модель прикреплена к тому же пользователю, а вот id увеличился на 1, на самом деле это тоже логично, ведь к записям конкретного пользователя нам тоже нужно обращаться по какому-то уникальному ключу.
Надеюсь теперь, заглянув внутрь баз данных, вы лучше понимаете устройство баз данных и как возможно осуществлять связь между ними. Конечно, рассказать о базах данных можно еще многое. Но, как говорится, всему свое время.
Работа с моделями не через панель администратора. forms.py
Работать с записями через панель администратора мы научились, но хотелось бы, чтобы авторизованный пользователь, с обычными правами, сам мог добавлять записи. В этом блоке этим и займемся.
...
from .forms import NewPackage
def createnewpackage(request):
if request.method == "GET":
return render(request, 'package/createnewpackage.html', {'NewPackage': NewPackage})
else:
try:
form = NewPackage(request.POST)
newpackage = form.save(commit=False)
newpackage.user = request.user
newpackage.save()
return redirect('personal_cabinet')
except ValueError:
return render(request, 'package/createnewpackage.html', {'NewPackage': NewPackage, 'error': 'Недопустимая длина названия'})
...
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
path('personal_cabinet/', views.personal_cabinet, name='personal_cabinet'),
path('logout/', views.logoutuser, name='logoutuser'),
path('', views.home, name='home'),
path('login/', views.loginuser, name='loginuser'),
path('createnewpackage/', views.createnewpackage, name='createnewpackage'),
]
from django.forms import ModelForm
from .models import Package
class NewPackage(ModelForm):
class Meta:
model = Package
fields = ['title', 'description', 'url']
{% extends 'package/index.html' %}
{% block title %}Добавить новую запись{% endblock %}
{% block content %}
<h1>Создать новую запись</h1>
<h2>{{ error }}</h2>
<form method="POST">
{% csrf_token %}
Хочу выучить - {{ NewPackage.title }}<br><br>
Описание - {{ NewPackage.description }}<br><br>
Ссылка на документацию - {{ NewPackage.url }}<br><br>
<button type="submit">Создать</button>
</form>
{% endblock %}
Для добавления новых записей в базу данных нам нужна форма, в которую мы будем записывать данные находясь на соответствующей странице сайта. Начнем с создания адреса для этой страницы (create/) и соответствующей функции (createnewpackage). Далее в папке с приложением package создадим файл forms.py. В этот файл нам нужно импортировать ModelForm из django.forms и нашу ранее созданную модель Package. В этом файле создаются формы, которые будут отображаться непосредственно для пользователя. Поэтому содержать эти формы должны те же поля, которые содержат модели, в которые мы хотим отправить введенную в эти поля информацию. Создадим класс NewPackage и унаследуем его от ModelForm. Благодаря наследованию от этого класса и создаются поля, которые можно заполнять. Далее нам нужен вложенный метакласс, как вы помните в python что-то более локальное наследуется от чего-то более глобального, так вот метаклассы в этой цепочки самые глобальные объекты, метаклассы отвечают за конструирование его дочерних классов.
Метаклассом ModelForm является ModelFormMetaclass. Напоминаю, перейти к коду какой-то модуля django достаточно кликнуть по нему с нажатой клавишей ctrl. А сам ModelFormMetaclass участвует в создании полей модели, не в одиночку, конечно, но в том, что касается моделей, ноги растут именно от этого метакласса.
В качестве модели используем нашу модель Package, а из полей оставим только те, которые пользователь может заполнять, поля с датой и временем создания записи, а также поле привязки к пользователю нам не нужны.
Теперь вернемся в views.py и создадим отображение создания записи. Для этого сначала импортируем из forms.py NewPackage. В случае GET метода будем возвращать страницу создания записи, в файле createnewpackage.html воспользуемся полями формы NewPackage, а в остальном она пока пусть будет примерно такая же, как loginuser.html и signupuser.html. В случае POST метода сохраним все переданные записи в переменную form, нам тут достаточно только записи request.POST, без указания в квадратных скобках конкретных полей, django в данном случае сам понимает в какое поле какую запись надо добавить. Для сохранения записи в базу данных используется метод .save(). Параметр commit в значении False вернет объект, который еще не сохранен в базе данных. Этот возвращенный объект, который уже содержит в себе записи мы сохраним в еще одну переменную, newpackage в нашем случае. И уже после этого сопоставим запись с пользователем, который ее создал. А создал ее тот пользователь, под которым мы авторизованы на сайте. Только теперь мы можем вызвать метод save() без параметра commit (commit=True значение по умолчанию) и сохранить запись в базу данных. Try Except тут нужен на тот случай, если пользователь вдруг введет в поле title или в полу url количество символов превышающее ограничение, в случае с url понадеемся, что такое вообще не возможно, хотя по-хорошему можно было бы обработать и это, а вот с полем title это более вероятно, поэтому текст ошибки оставим именно со словом title. На ваше усмотрение его текст можно сделать и более нейтральным, затрагивающим поля title и url сразу. Ну и перенаправляться после нажатия кнопки 'создать' мы будем в личный кабинет.
Теперь перейдем по адресу create/ и посмотрим на результат.
Получилось так. Теперь мы можем добавлять записи в базу данных будучи пользователем с обычными правами. Попробуем добавить запись и вернуться в панель администратора, чтобы убедиться в создании новой записи и в том, что она принадлежит именно Вове.
Запись создана и принадлежит она именно Вове. Все работает.
Вывод записей в личном кабинете
У нас есть интерфейс создания записей, но личный кабинет по прежнему пустой.
...
from .models import Package
def personal_cabinet(request):
packages = Package.objects.filter(user=request.user).order_by('-title')
return render(request, 'package/personal_cabinet.html', {'packages': packages})
{% extends 'package/index.html' %}
{% block title %}Личный кабинет{% endblock %}
{% block content %}
<h1>Личный кабинет</h1>
<h2>Мои записи</h2>
{% for package in packages %}
<div>
<b>{{ package.title }}</b><br>
{{ package.description }}<br>
{% if package.url %}
<a href="{{ package.url }}" target="_blank">Ссылка на документацию</a>
{% endif %}
</div>
{% endfor %}
{% endblock %}
Добавим модель Package в функцию personal_cabinet, только брать мы будем не все записи. Мы применим новый для нас метод - filter(). Данный фильтр проверяет какой user сейчас авторизован, и брать записи мы будем именно этого пользователя. Метод order_by() позволяет отсортировать записи по какому-нибудь признаку. Например, тут мы сортируем записи пользователя по алфавитному порядку, а минус перед словом title означает, что сортировать нужно в обратном порядке. А почему тогда буква 'a' стоит раньше буквы 'D', если сортировка в обратном алфавитном порядке? Потому что заглавные буквы в последовательности букв стоят раньше, чем прописные.
В html файле для данной функции ничего нового для нас нет. Единственное можно обратить внимание на строку для ссылки на документацию, это поле мы сделали необязательным для заполнения, и если это поле останется пустым мы все равно увидим содержимое тега 'a' и при клике на эту ссылку мы никуда переходить не будем. Это, конечно, неправильно. Поэтому вначале сделаем проверку на наличие содержимого в этом поле с помощью {% if %}{% endif %}.
Вернемся теперь в кабинет Вовы, вторую запись я тоже предварительно перевел на него для наглядности.
Выглядит личный кабинет теперь так. Содержимого тега 'a' для второй записи действительно нет. А теперь сменим пользователя.
Зайдем допустим под Ильей. И увидим, что в кабинете этого пользователя действительно пусто.
С этим разобрались.
Личная страница для каждой записи
Давайте лучше в личном кабинете будет выводить только названия записей и сделаем их кликабельными. А при клике на название будем попадать на личную страницу каждой записи, где уже и будет описание, ссылка, дата создания и все, что вы захотите придумать.
from django.shortcuts import render, redirect, get_object_or_404
...
def currentpackage(request, package_pk):
package = get_object_or_404(Package, pk=package_pk)
return render(request, 'package/view_package.html', {'package': package})
...
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
path('personal_cabinet/', views.personal_cabinet, name='personal_cabinet'),
path('logout/', views.logoutuser, name='logoutuser'),
path('', views.home, name='home'),
path('login/', views.loginuser, name='loginuser'),
path('createnewpackage/', views.createnewpackage, name='createnewpackage'),
path('package/<int:package_pk>', views.currentpackage, name='currentpackage'),
]
{% extends 'package/index.html' %}
{% block title %}Личный кабинет{% endblock %}
{% block content %}
<h1 class="h1">Личный кабинет</h1>
<h2>Что я хочу выучить</h2>
{% for package in packages %}
<div class="what_learn">
<a href="{% url 'currentpackage' package.id %}"><b>{{ package.title }}</b><br></a>
</div>
{% endfor %}
{% endblock %}
{% extends 'package/index.html' %}
{% block title %}{{ package.title }}{% endblock %}
{% block content %}
<h1>{{ package.title }}</h1>
<h4>{{ package.created }}</h4>
<h3>Описание</h3>
<p>{% if package.description %}{{ package.description }}{% else %}Тут пока ничего нет...{% endif %}</p>
{% if package.url %}<b><a href="{{ package.url }}" target="_blank">Ссылка на документацию</a></b>{% endif %}
{% endblock %}
Обратим внимание на путь для новой страницы, с таким мы еще не сталкивались, знаки больше меньше означают, что их содержимое воспринимается как параметр, а int перед этой записью приводит этот параметр к целочисленному значению. Раз это параметр, то мы можем, а в случае таких параметров даже должны, где-то этот параметр использовать. И использовать мы должны его в функции-представлении для этого пути, передается параметр вместе с request. В этот параметр попадает то, что мы пишем через / после запроса, например в package/this, this в данном случае поступило бы в переменную package_pk. Но нас интересуют, конечно, не слова, а нас интересуют pk, то есть primary key, уникальный идентификатор каждой записи. Его мы будем преобразовывать к целочисленному типу и выводит после /.
Создадим функцию-представление во views.py. Для этой функции импортируем get_object_or_404, тоже новая для нас функция. Эта функция возвращает пользователю 404, когда будет передан несуществующий адрес записи. Принимать эта функция будет первым параметром модель, а вторым будет принимать первичный ключ записи из этой модели. Сохраним результат работы этой функции в переменную и через эту переменную будет обращаться к содержимому записи в соответствующем html файле.
В этом html нет для нас ничего нового. Останавливаться на нем не станем. И файл personal_cabinet.html нам тоже нужно немного изменить.
Теперь в личном кабинете мы видим только кликабельные названия, который ведут на личную страницу каждой записи. Запись asincio имеет описание и ссылку и они выводятся на экране, а запись Django имеет только название и в поле 'описание' мы видим 'Тут пока ничего нет...', а поле с ссылкой вообще не видно.
Редактирование и удаление записей
Осталось добавить недостающий функционал.
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.contrib.auth import login, logout, authenticate
from .forms import NewPackage
from .models import Package
# Create your views here.
def home(request):
return render(request, 'package/home.html')
def personal_cabinet(request):
packages = Package.objects.filter(user=request.user).order_by('-title')
if request.method == 'POST':
return redirect('personal_cabinet')
else:
return render(request, 'package/personal_cabinet.html', {'packages': packages})
def currentpackage(request, package_pk):
package = get_object_or_404(Package, pk=package_pk, user=request.user)
if request.method == 'POST':
return redirect('editpackage')
else:
return render(request, 'package/view_package.html', {'package': package})
def editpackage(request, package_pk):
package = get_object_or_404(Package, pk=package_pk, user=request.user)
form = NewPackage(instance=package)
if request.method == 'GET':
return render(request, 'package/edit_package.html', {'package': package, 'form': form})
else:
try:
form = NewPackage(request.POST, instance=package)
form.save()
return redirect('personal_cabinet')
except ValueError:
return render(request, 'package/edit_package.html', {'form': form, 'package': package, error': 'Недопустимая длина названия'})
def deletepackage(request, package_pk):
package = get_object_or_404(Package, pk=package_pk, user=request.user)
if request.method == 'POST':
package.delete()
return redirect('personal_cabinet')
def signupuser(request):
if request.method == "GET":
return render(request, 'package/signupuser.html', {'registerform': UserCreationForm()})
else:
if request.POST['password1'] == request.POST['password2']:
try:
user = User.objects.create_user(request.POST['username'], password=request.POST['password1'])
user.save()
login(request, user)
return redirect('personal_cabinet')
except IntegrityError:
return render(request, 'package/signupuser.html',
{'registerform': UserCreationForm(), 'error': 'Данное имя уже используется'})
else:
return render(request, 'package/signupuser.html',
{'registerform': UserCreationForm(), 'error': 'Пароли не совпадают'})
def loginuser(request):
if request.method == "GET":
return render(request, 'package/loginuser.html', {'authenticationform': AuthenticationForm()})
else:
user = authenticate(request, username=request.POST['username'], password=request.POST['password'])
if user is None:
return render(request, 'package/loginuser.html', {'authenticationform': AuthenticationForm(), error': 'Пользователь с такими данными не найден.'
'Проверьте правильность введенных данных.'})
else:
login(request, user)
return redirect('personal_cabinet')
def logoutuser(request):
if request.method == 'POST':
logout(request)
return redirect('home')
def createnewpackage(request):
if request.method == "GET":
return render(request, 'package/createnewpackage.html', {'NewPackage': NewPackage})
else:
try:
form = NewPackage(request.POST)
newpackage = form.save(commit=False)
newpackage.user = request.user
newpackage.save()
return redirect('personal_cabinet')
except ValueError:
return render(request, 'package/createnewpackage.html', {'NewPackage': NewPackage, 'error': 'Недопустимая длина названия'})
from django.contrib import admin
from django.urls import path
from package import views
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', views.signupuser, name='signupuser'),
path('personal_cabinet/', views.personal_cabinet, name='personal_cabinet'),
path('logout/', views.logoutuser, name='logoutuser'),
path('', views.home, name='home'),
path('login/', views.loginuser, name='loginuser'),
path('createnewpackage/', views.createnewpackage, name='createnewpackage'),
path('package/<int:package_pk>', views.currentpackage, name='currentpackage'),
path('package/<int:package_pk>/edit', views.editpackage, name='editpackage'),
path('package/<int:package_pk>/delete', views.deletepackage, name='deletepackage'),
]
{% extends 'package/index.html' %}
{% block title %}{{ package.title }}{% endblock %}
{% block content %}
<h1>{{ package.title }}</h1>
<h4>{{ package.created }}</h4>
<h3>Описание</h3>
<p>{% if package.description %}{{ package.description }}{% else %}Тут пока ничего нет...{% endif %}</p>
{% if package.url %}<b><a href="{{ package.url }}" target="_blank">Ссылка на документацию</a></b>{% endif %}
<div><form method="POST" action="{% url 'editpackage' package.id %}">
{% csrf_token %}
<button type="submit">Редактировать</button>
</form></div>
{% endblock %}
{% extends 'package/index.html' %}
{% block title %}{{ package.title }}{% endblock %}
{% block content %}
<form method="POST">
{% csrf_token %}
<h2>Редактирование записи - {{ package.title }}</h2>
<p>Изменить название<br> {{ form.title }}<br>
Изменить описание<br> {{ form.description }}<br>
Изменить ссылку<br> {{ form.url }}</p>
<button type="submit">Сохранить</button>
</form>
<form method="POST" action="{% url 'personal_cabinet' %}">
{% csrf_token %}
<button type="submit">Вернуться без изменений</button>
</form>
<form method="POST" action="{% url 'deletepackage' package.id %}">
{% csrf_token %}
<button type="submit">Удалить</button>
</form>
{% endblock %}
Создадим два пути, для редактирования и для удаления. Поскольку к этим страницам мы обращаемся со страницы личной записи, то url приписку адреса мы пишем после переменной адреса. На странице для редактирования воспользуемся формой NewPackage, параметр instance свяжет модель редактирование с содержимым конкретной модели. После внесения изменения будем сохранять форму и перенаправляться на страницу личного кабинета и поскольку на странице редактирования пользователь также может использовать больше символов, чем доступно, то эту гипотетическую ситуацию мы обработаем. Для удаления создадим функцию-представление, которая в случае POST метода будет применять метод .delete(), вместо save(). И находиться эта кнопка пусть будет там же где мы редактируем запись.
Теперь у нас есть личный кабинет со списком записей, с возможностью их просмотра и редактирования и с возможностью их удаления.
Давайте на этом закончим с функционалом, добавить можно еще много, но основные инструменты для этого мы с вами уже освоили, так что дальше все зависит от вашей фантазии.
Внешний вид
Осталось заняться оформлением нашего сайта. Много тратить на это время не стану, набросаю простенький дизайн с помощью bootstrap и css.
Вот так теперь выглядит личный кабинет, посмотреть на весь сайт можно скачав весь проект с моего github
Третий сайт готов
За эти три сайта мы познакомились со всем 'скелетом' возможностей django. Освоив этот материал и хотя бы повторив за мной все, что тут описано, вы уже умеете создавать полноценные сайты на django. На этом обучающие материалы по django не заканчиваются, в следующем блоке мы напишем еще один сайт, заострив внимание на нескольких, так скажем, 'базовых' моментах, которые мы не рассмотрели в этом блоке.
Комментарии
Разделы
- НАВЕРХ
- Первый сайт
- Venv
- Установка django
- Создание нового проекта
- Структура проекта
- settings.py
- Первое приложение
- Введение в urls
- Стартовая страница
- Шаблоны
- Тег form
- Реализовываем
необходимый функционал - Внешний вид
- Первый сайт на django готов
- Второй сайт
- Панель администратора
- Модели
- Отображаем информацию из баз данных на сайте.
Знакомство с Django.templates - Внешний вид. Статические файлы
- Второе приложение
- Второй сайт готов
- Третий сайт
- Регистрация пользователей
- Дорабатываем форму регистрации.
Обработка ошибок - Регистрация. Авторизация. Выход
- Модель. Углубление в базы данных
- Работа с моделями не через панель администратора.
forms.py - Вывод записей в личном кабинете
- Личная страница для каждой записи
- Редактирование и удаление записей
- Внешний вид
- Третий сайт готов
Для отправки комментария необходимо авторизоваться
Комментарии
Здесь пока ничего нет...