Дипломная работа
Курс: Разработчик на Python 2018 Тема: Миграция сайта kiteup.ru платформы ModX + MySql на платформу Django 2 + PostgreSql. Задание:
- Установить и настроить веб фреймворк на Python - Django 2. В качестве БД использовать PostgreSQL стабильной версии, макет сайта сверстать на Bootstrap 4
- Осуществить миграцию всех существующих разделов: “Главная”, “Кайт школа”, “ЧаВо”, “Новости”, “Статьи”, “Фото – Видео”, “Контакты”, “Мероприятия”
- Разработать личный кабинет пользователя (стена записей, контакты)
Внимание, будет много буковок перед основной частью. Сайт kiteup.ru сделал самостоятельно несколько лет тому назад. Сайт делал под проект определенной тематики и целевой аудитории: активные виды парусного спорта – кайтсерфинг, сноукайтинг, лэндкайтинг.
Выбрал и приобретел шаблон с подходящим этому направлению цветом и стилем, и веб фреймворк Mod с готовыми плагинами, на котором реализовал определенный функционал. Дальнейшие доработки: личный кабинет пользователя, расширенный календарь событий, комментарии под постами, подсчет посещений, лайки и прочие ништяки требовали либо привлечения разработчика, либо углубленного изучения PHP.
При этом я штудировал и посещал курсы по программированию на PHP и верстке HTML, но самостоятельно дорабатывать не получалось – все казалось слишком трудным и запутанным. В общем, программирование – мой личный «камень преткновения», хотя в ИТ я не новичок.
И совсем недавно я открыл для себя язык программирования Python. К этому подтолкнул случай: знакомому понадобилось сделать коллектор с датчиков температур. А мой любимый мини ПК Raspberry Pi 3 (RPi3) идеально подходил для реализации «хотелки». На примере домашней метеостанции на RPi 3 написанной на Python мне удалось сделать проект.
Не помню, чем именно понравился мне Python, но позже, посмотрев востребованность разработчиков на нем и растущий активный спрос – принял важное на тот момент решение – стать разработчиком на Python и устроиться на работу. Это желание привело меня в учебный цент otus.ru на курс “Разработчик на Python” – пишу диплом и одновременно воплощаю свои мечты.
На ПК с Windows 10 установил:
- Python версии 3.7.1
- Python IDE PyCharm
В PyCharm создал новый проект dilpom, настроил виртуальное окружение.
Далее внутри виртуального окружения:
./pip install django=2.1.9
Скачиваем geckodriver и его поддержку в Python:
./pip install selenium
Создаем новое приложение Django:
./python manage.py startproject kiteup
Создалась структура каталогов:
diplom
├───manage.py
├───kiteup
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
Прежде чем приступить к миграции сайта на Django, испробовал несколько подходов, но в качестве основного выбрал методологию Test Driven Development – Разработка через тестирование wiki.
Именно такой подход позволил мне перейти от теоретической части к самой разработке, не боясь нечаянно разрушить написанный код и потом мучиться над поиском ошибок, как это было со мной.
Да, TDD медленный и нудный: сначала пишется тест, покрывающий желаемый функционал, затем пишется код, заставляющий пройти тест и, в заключении, производится рефакторинг кода к соответствующему стандарту.
Очень короткими циклами, маленькими, но уверенными шагами движемся к завершению поставленной задачи – объявленной в теме дипломного проекта. И спим спокойно, ведь каждая строка кода обложена тестами (функциональными, интеграционными и модульными) , сам код получается чище и короче.
Создаем в Django первое приложение, в моем случае это News (раздел сайта с новостями):
./python manage.py startapp news
В структуре каталога появилась новая папка News:
diplom
├───manage.py
├───kiteup
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
├───news
│ __init__.py
│ admin.py
│ apps.py
│ test.py
│ models.py
│ views.py
И, правильно, прежде чем начать разрабатывать приложение News, пишу тест в test.py:
from selenium import webdriver
browser = webdriver.Firefox()
browser.get(‘http://127.0.0.0:8000’)
assert ‘Новости’ in browser.title
Пробуем выполнить:
./python manage.py test
Что происходит?
Запускается окно реального браузера Firefox, открывается веб страница по адресу: http://127.0.0.0:8000 и проверятся тестовое утверждение, что в заголовке страницы есть слово “Новости”.
И, в консоль получаем кучу ошибок (исключения, отсутствие ответа и т.д.)
Анализируем трейсбэк и понимаем, что не запущен тестовый сервер.
Запускаем его:
./python manage.py runserver 127.0.0.0:8000
В другом окне терминала в рабочей директории diplom далее по тексту весь код будет запускаться из-под этой директории:
./python manage.py test
Снова запустился Firefox, и в окне появилась стартовая страница Django с приветствием, но assertionError сообщил, что нет слова “Новости” в заголовке страницы.
Использую небольшой код: в test.py мы управляем браузером и со стороны пользователя мы видим как функционирует приложение – с этого момента, тесты с использованием Selenium будут называться функциональными тестами (ФТ).
В структуре директории diplom создадим отдельную папку functional_tests и перенесем код теста в файл test_news_creation.py. Не забыв при этом создать файл init.py, что позволяет питону интерпретировать директорию как package.
Получилась такая структура каталогов:
diplom
├───manage.py
├───functional_tests
│ __init__.py
│ test_news_creation.py
├───kiteup
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
├───news
│ __init__.py
│ admin.py
│ apps.py
│ test.py
│ models.py
│ views.py
И еще очень важный момент: после отработки теста окно браузера Firefox осталось открытым и assertionError дает не информативный трейсбэк, по которому сложно отследить - где и в каком модуле произошла ошибка.
Значительно расширить ФТ поможет модуль unittest стандартной библиотеки Python. Теперь test_news_creation.py содержит в себе следующий код:
import unittest
from selenium import webdriver
class NewVisitorTest(unittest.TestCase):
‘’’тест посетителя’’’
def setUp(self):
‘’’установка’’’
self.browser = webdriver.Firefox()
def teardown(self):
‘’’демонтаж’’’
self.browser.quit()
def test_can_start_news_page(self):
self.browser.get(‘http://127.0.0.0:8000’)
self.assertIn(‘Новости’, self.browser.title)
if __name__ == “__main__”:
unittets.main()
Запускаем ./python functional_tests.test_news_creation.py снова получаем не работающий ФТ.
Т.к. Django проект может иметь много приложений, а ФТ оценивают весь проект таким, как видит его пользователь, настал момент перейти к модульным тестам, которые будут оценивать одно приложение, его внутреннюю структуру (модель, представление и тесты), таким как видит его программист. Модульные тесты помогут написать код чистым и лаконичным.
Такой подход может выглядеть избыточным, но впоследствии, когда проект будет разрастаться, это поможет отлаживать его до функционального состояния гораздо быстрее.
Напишем первый модульный тест для тестирования представления. Открываем news/test.py и размещаем следующий код:
from django.test import TestCase
from django.urls import resolve
from news.views import news_list
class NewsPageTest(TestCase)
def test_resolve_url_to_news_list_page(self):
‘’’тест: url news преобразуется в представление news_list’’’
response = resolve(‘news’)
self.assertEqual(response.func, news_list)
Запускаем тест:
./python manage.py test
ImportError : невозможно импортировать имя news_list
Почему появилась ошибка? Произошел импорт того, что еще не существует.
Все старания свелись к тому, что уже есть не работающий ФТ и модульный тест. Это главная идиома TDD, которая говорит о том, что пора уже приступить к разработке приложения, т.е. непосредственного написания кода приложения. Во news/views.py создаем:
News_list = None
Снова запускаем модульный тест. Теперь в трейсбэке замелькали исключения django.urls.exceptions.Resolver404:…… На самом деле тест показывает, что требуется отображение url - адреса на обработчик.
Подключаем приложение New в kiteup/urls.py:
"""kiteup URL Configuration"""
from news import views
urlpatterns = [
path('', views.index , name='index'),
]
Еще раз выполняем модульные тесты ./python manage.py test
[….]
TypeError: представление должно быть вызываемым объектом……
Исчезла ошибка 404 и тест пожаловался на то, что представление news_list не является вызываемым объектом. Исправляем код:
from django.shortcuts import render
def news_list():
pass
Снова запускаем ./python manage.py test и получаем на выходе:
Ran 1 test in 0.001 s Ok ...
Модульный тест представления сработал. Но ФТ еще не проходит. Путем подобных маленьких шагов и запуска тестов,
доработки модели News в News/models.py и подключении приложения в kiteup/settings.py пришел к тому, что ФТ выдал ОК.
Подобным образом были разработаны минимально функциональные приложения Pages (статические страницы), Articles (статьи),
Events (мероприятия) соответствующие основным разделам сайта kiteup.ru
А также приложение Accounts – регистрация и авторизация пользователей на сайте. Для тестирования каждого нового
приложения я добавлял соответствующий файл ФТ в папку functional_test и внутри самого приложения удалив test.py создал
паку test в котором разместил файлы модульных тестов отдельно для views и отдельно для models
(tests/test_views.py и test/test_models.py). На определенных этапах и при всех работающих тестах производил
рефакторизацию кода, чтобы привести код в лаконичный, компактный и удобочитаемый вид. После рефакторизации убеждался,
что все тесты снова проходят.
## Развертывание.
Основная часть дипломного проекта готова, и настало время пройти следующий квест – развертывание на реальном сервере, чтобы предоставить на проверку.
На данном этапе приобрел VDS по минимальному тарифу, настроил DNS и сервер стал доступен по адресу http://dev.kiteup.ru , установил и настроил окружение:
1. Centos 7, авторизация ssh по RSA ключу (чтобы не вводить постоянно пароли)
2. Скачал и скомпилировал python3.7.1
3. Настроил Nginx и Gunicorn и PostgresSql
Вся автоматизация развертывания основана на использовании fabric – инструмента выполнения определенных команд.
Ставим fabric:
./pip install fabric3
Создаем папку deploy_tools и размещаем код в файле fabfile.py (показан фрагмент кода):
from fabric.contrib.files import append, exists, sed from fabric.api import env, local, run import random
REPO_URL = 'https://github.com/kolun4ik/diplom_kiteup'
def deploy(): """развернуть""" site_folder = f'/home/{env.user}/sites/{env.host}' source_folder = site_folder + '/source' _create_directory_structure_if_necessery(site_folder) _get_latest_source(source_folder) _update_settings(source_folder, env.host) _updatevirtualenv(source_folder) _update_static_file(source_folder) _update_database(source_folder)
def _create_directory_structure_if_necessery(site_folder): """создаем, если нужно, структуру каталогов""" for subfolder in ('database', 'static', 'virtualenv', 'source'): run(f'mkdir -p {site_folder}/{subfolder}') …..
Конфигурационные файлы сервисов кладем здесь же deploy_tools/*.template.service и все руководства к развертыванию
будут находиться в файле provisionins_notes.md.
С ПК, на котором осуществлялась разработка, с помощью fabric заливаем исходный код на сервер под правами
непривилегированного пользователя (запускаем в папке с файлом fabfile.py):
fab -i c:\Temp\s_key.ppk deploy:host=deploy@dev.kiteup.ru
s_key.ppk - файл приватного ключа (предварительно настроен доступ к ssh с помощью публичного ключа)
Для того, чтобы сообщить ФТ, что требуется проверка удаленного сервера, модифицировал setUp(self) введя переменную
STAGING_SERVER:
def setUp(self): """Установка, выполняется для каждого метода test_*()""" self.browser = webdriver.Firefox() staging_server = os.environ.get('STAGING_SERVER') if staging_server: self.live_server_url = 'http://' + staging_server
Запускаем функциональные тесты для проверки работоспособности проекта, размещенного на dev.kiteup.ru:
$ export STAGING_SERVER=dev.kiteup.ru ../python manage.py test functional_test
На одном из этапов пришлось изменить модель News, и тесты меня выручили - помогли быстро найти все сломанные участки
кода и привести их в рабочее состояние.
## Заключение.
На самом деле при разработке диплома я столкнулся со многими трудностями. Такими, что до обучения не справился бы с
ними и сдался. На курсе otus.ru получил теоретические знания и практические навыки разработки на Python. Плюс освоение
методологии TDD позволило пусть маленькими шагами, но уверено топать к намеченной цели – переделать сайт kiteup.ru и
развернуть на нем новый функционал.