- В чем разница между написанием автономных приложений и написанием приложений внутри проектов
- Как создать файл setup.cfg для публикации вашего приложения Django
- Как загрузить Django вне проекта Django, чтобы вы могли протестировать свое приложение
- Как протестировать несколько версий Python и Django с помощью tox
- Как опубликовать устанавливаемое приложение Django в PyPI с помощью Twine
Даже если вы изначально намеревались сделать свое приложение Django доступным в виде пакета, вы, скорее всего, начнете внутри проекта. Чтобы продемонстрировать процесс перехода от проекта Django к устанавливаемому приложению Django, я сделал две ветки доступными в репозитории. Ветка проекта является начальным состоянием из приложения внутри проекта Django. Основная ветка — это готовое устанавливаемое приложение.
Вы также можете скачать готовое приложение на странице пакета PyPI realpython-django-чеков
Приложение в качестве примера - краткое представление позиций в квитанции. В ветке проекта вы найдете каталог с именем sample_project , содержащий рабочий проект Django. Каталог выглядит так:
sample_project/
│
├── receipts/
│ ├── fixtures/
│ │ └── receipts.json
│ │
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ │
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
│
├── sample_project/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│
├── db.sqlite3
├── manage.py
├── resetdb.sh
└── runserver.sh
Самая последняя версия Django на момент написания этого руководства была 3.0.4, и все тестирование проводилось на Python 3.7. Ни один из шагов, описанных в этом руководстве, не должен быть несовместим с более ранними версиями Django - я использовал эти методы, начиная с Django 1.8. Однако некоторые изменения необходимы, если вы используете Python 2. Чтобы не усложнять примеры, я использовал Python 3.7 для всей базы кода.
Создание проекта Django с нуля
Пример проекта и приложение квитанций были созданы с помощью команды администратора Django с небольшими изменениями. Для начала запустите следующий код в чистой виртуальной среде:
$ python -m pip install Django
$ django-admin startproject sample_project
$ cd sample_project
$ ./manage.py startapp receipts
Это создает структуру каталогов проекта sample_project и подкаталог приложения квитанций с файлами шаблонов, которые вы будете использовать для создания своего устанавливаемого приложения Django.
Затем в файл sample_project / settings.py необходимо внести несколько изменений:
- Добавьте «127.0.0.1» в параметр ALLOWED_HOSTS, чтобы вы могли тестировать свой код локально.
- Добавьте «receipts» в список INSTALLED_APPS .
Вам также необходимо зарегистрировать в Квитанции URL - приложения в sample_project / urls.py файл. Для этого добавьте путь (‘receipts/', include (receipts.urls')) в список url_patterns .
Знакомство с приложением-образцом квитанций
Приложение состоит из двух классов модели ORM: Item и Receipt . Класс Item содержит объявления полей базы данных для описания и стоимости. Стоимость содержится в DecimalField . Использование чисел с плавающей запятой для представления денег опасно - вы всегда должны использовать числа с фиксированной запятой при работе с валютами .
Класс Receipt - это точка сбора для объектов Item . Это достигается с помощью ForeignKey по пункту, который указывает на квитанции. Receipt также включает total () для получения общей стоимости объектов Item, содержащихся в Receipt:
# receipts/models.py
from decimal import Decimal
from django.db import models
class Receipt(models.Model):
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Receipt(id={self.id})"
def total(self) -> Decimal:
return sum(item.cost for item in self.item_set.all())
class Item(models.Model):
created = models.DateTimeField(auto_now_add=True)
description = models.TextField()
cost = models.DecimalField(max_digits=7, decimal_places=2)
receipt = models.ForeignKey(Receipt, on_delete=models.CASCADE)
def __str__(self):
return f"Item(id={self.id}, description={self.description}, " \
f"cost={self.cost})"
Объекты модели предоставляют вам контент для базы данных. Короткое представление Django возвращает словарь JSON со всеми объектами Receipt и их объектами Item в базе данных:
# receipts/views.py
from django.http import JsonResponse
from receipts.models import Receipt
def receipt_json(request):
results = {
"receipts":[],
}
for receipt in Receipt.objects.all():
line = [str(receipt), []]
for item in receipt.item_set.all():
line[1].append(str(item))
results["receipts"].append(line)
return JsonResponse(results)
Представление Receipt выполняет итерацию по всем объектам Receipt , создавая пару объектов Receipt и список содержащихся в них объектов Item . Все это помещается в словарь и возвращается через Django JsonResponse () .
Чтобы сделать модели доступной в интерфейсе администратора Django , вы используете файл admin.py для регистрации моделей:
# receipts/admin.py
from django.contrib import admin
from receipts.models import Receipt, Item
@admin.register(Receipt)
class ReceiptAdmin(admin.ModelAdmin):
pass
@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
pass
Этот код создает Django ModelAdmin для каждого из классов Receipt и Item и регистрирует их у администратора Django.
Наконец, файл urls.py регистрирует отдельное представление в приложении по URL-адресу:
# receipts/urls.py
from django.urls import path
from receipts import views
urlpatterns = [
path("receipt_json/", views.receipt_json),
]
Теперь вы можете включать в receipts/ urls.py в вашего проекта url.py файл , чтобы сделать вид квитанции доступным на веб - сайте.
Когда все готово, вы можете запустить receipts/manage.py makemigrations , использовать администратор Django для добавления данных, а затем посетить / Receipt / Receiver_json / для просмотра результатов:
$ curl -sS http://127.0.0.1:8000/receipts/receipt_json/ | python3.8 -m json.tool
{
"receipts": [
[
"Receipt(id=1)",
[
"Item(id=1, description=wine, cost=15.25)",
"Item(id=2, description=pasta, cost=22.30)"
]
],
[
"Receipt(id=2)",
[
"Item(id=3, description=beer, cost=8.50)",
"Item(id=4, description=pizza, cost=12.80)"
]
]
]
}
В приведенном выше блоке вы используете curl для открытия представления receive_json , что возвращает JSON, содержащему объекты Receipt и соответствующие им объекты Item .
Тестирование приложения в проекте
Django дополняет пакет Python unittest своими собственными возможностями тестирования, позволяя предварительно загружать данные в базу данных и запускать тесты. Приложение receipts определяет tests.py файл и приспособление для тестирования. Этот тест ни в коем случае не является всеобъемлющим, но это достаточно хорошее доказательство концепции:
# receipts/tests.py
from decimal import Decimal
from django.test import TestCase
from receipts.models import Receipt
class ReceiptTest(TestCase):
fixtures = ["receipts.json", ]
def test_receipt(self):
receipt = Receipt.objects.get(id=1)
total = receipt.total()
expected = Decimal("37.55")
self.assertEqual(expected, total)
Приспособление создает два объекта « receipts» и четыре соответствующих объекта « Предмет» . Нажмите на складную секцию ниже, чтобы поближе познакомиться с кодом приспособления.
Вы можете проверить приложение квитанций с помощью команды Django manage.py :
$ ./manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.013s
OK
Destroying test database for alias 'default'...
При выполнении теста manage.py запускается единственный тест, определенный в файле receivets / tests.py, и отображаются результаты.
Создание устанавливаемого приложения Django
Ваша цель - поделиться приложением receipts без проекта и сделать его пригодным для повторного использования другими. Вы можете заархивировать каталог receipts/ и раздать его, но это несколько ограничивающее решение. Вместо этого вы можете разделить приложение на пакет, чтобы его можно было установить.
Самая большая проблема при создании устанавливаемого приложения Django заключается в том, что Django ожидает проект. Приложение без проекта - это просто каталог, содержащий код. Без проекта Django не знает, как что-либо делать с вашим кодом, включая выполнение тестов.
Удаление вашего приложения Django из проекта
Хорошая идея - сохранить образец проекта, чтобы вы могли запустить сервер Django dev и поиграть с действующей версией вашего приложения. Вы не будете включать этот образец проекта в пакет приложения, но он все равно может находиться в вашем репозитории. Следуя этой идее, вы можете начать упаковывать свое устанавливаемое приложение Django, переместив его в каталог вверх:
$ mv receipts ..
Структура каталогов теперь выглядит примерно так:
django-receipts/
│
├── receipts/
│ ├── fixtures/
│ │ └── receipts.json
│ │
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ │
│ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ ├── views.py
│ ├── admin.py
│ └── apps.py
│
├── sample_project/
│ ├── sample_project/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ │
│ ├── db.sqlite3
│ ├── manage.py
│ ├── resetdb.sh
│ └── runserver.sh
│
├── LICENSE
└── README.rst
Чтобы упаковать приложение, вам нужно вытащить его из проекта. Перемещение - это первый шаг. Обычно я храню исходный проект для тестирования, но не включаю его в итоговый пакет.
Загрузка Django вне проекта
Теперь, когда ваше приложение находится за пределами проекта Django, вам нужно указать Django, как его найти. Если вы хотите протестировать свое приложение, запустите оболочку Django, которая сможет найти ваше приложение или выполнить миграции . Вам нужно будет настроить Django и сделать его доступным.
Django settings.configure () и django.setup () являются ключевыми для взаимодействия с вашим приложением вне проекта. Дополнительная информация об этих вызовах доступна в документации Django .
Скорее всего, вам понадобится эта конфигурация Django в нескольких местах, поэтому имеет смысл определить ее в функции. Создайте файл с именем boot_django.py, содержащий следующий код:
1# boot_django.py
2#
3# This file sets up and configures Django. It's used by scripts that need to
4# execute as if running in a Django server.
5import os
6import django
7from django.conf import settings
8
9BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "receipts"))
10
11def boot_django():
12 settings.configure(
13 BASE_DIR=BASE_DIR,
14 DEBUG=True,
15 DATABASES={
16 "default":{
17 "ENGINE":"django.db.backends.sqlite3",
18 "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
19 }
20 },
21 INSTALLED_APPS=(
22 "receipts",
23 ),
24 TIME_ZONE="UTC",
25 USE_TZ=True,
26 )
27 django.setup()
Строки 12 и 27 устанавливают среду Django. Settings.configure () вызов принимает список аргументов, которые эквивалентны переменным, определенным в settings.py файла. Все, что вам может понадобиться в вашем settings.py для запуска вашего приложения, передается в settings.configure () .
Приведенный выше код представляет собой довольно урезанную конфигурацию. Приложение receipts ничего не делает с сеансами или шаблонами, поэтому для INSTALLED_APPS нужны только «receipts» , и вы можете пропустить любые определения промежуточного программного обеспечения. Значение USE_TZ = True необходимо, потому что модель получения содержит созданную метку времени. В противном случае вы столкнетесь с проблемами при загрузке тестового устройства.
Запуск команд управления с помощью устанавливаемого приложения Django
Теперь, когда у вас есть boot_django.py , вы можете запустить любую команду управления Django с помощью очень короткого скрипта:
#!/usr/bin/env python
# makemigrations.py
from django.core.management import call_command
from boot_django import boot_django
boot_django()
call_command("makemigrations", "receipts")
Django позволяет программно вызывать команды управления через call_command () . Теперь вы можете запустить любую команду управления, импортировав и вызвав boot_django (), а затем call_command () .
Ваше приложение теперь находится за пределами проекта, что позволяет вам делать с ним все, что угодно Django. Я часто определяю четыре служебных сценария:
- load_tests.py для тестирования вашего приложения
- makemigrations.py для создания файлов миграции
- migrate.py для выполнения миграции таблиц
- djangoshell.py для создания оболочки Django, которая знает о вашем приложении.
Тестирование устанавливаемого приложения Django
Load_test.py файл может быть столь же простым, как makemigrations.py, но тогда была бы единственная возможность запустить все тесты сразу. С помощью нескольких дополнительных строк вы можете передать аргументы командной строки средству выполнения тестов, что позволяет запускать выборочные тесты:
1#!/usr/bin/env python
2# load_tests.py
3import sys
4from unittest import TestSuite
5from boot_django import boot_django
6
7boot_django()
8
9default_labels = ["receipts.tests", ]
10
11def get_suite(labels=default_labels):
12 from django.test.runner import DiscoverRunner
13 runner = DiscoverRunner(verbosity=1)
14 failures = runner.run_tests(labels)
15 if failures:
16 sys.exit(failures)
17
18 # In case this is called from setuptools, return a test suite
19 return TestSuite()
20
21if __name__ == "__main__":
22 labels = default_labels
23 if len(sys.argv[1:]) > 0:
24 labels = sys.argv[1:]
25
26 get_suite(labels)
DiscoverRunner от Django — это класс обнаружения тестов, совместимый с модулем тестирования Python. Он отвечает за настройку тестовой среды, построение набора тестов, настройку баз данных, запуск тестов, а затем завершение. Начиная со строки 11, get_suite () берет список тестовых меток и напрямую вызывает для них DiscoverRunner.
Этот сценарий похож на то, что делает тест команды управления Django. Блок __main__ передает любые аргументы командной строки в get_suite (), и если их нет, то он передает набор тестов для приложения Receiver.tests. Теперь вы можете вызвать load_tests.py с аргументом тестовой метки и запустить один тест.
Строка 19 — это особый случай, помогающий при тестировании с помощью tox. Вы узнаете больше о tox в следующем разделе . Вы также можете проверить потенциальную замену DiscoverRunner в сворачиваемом разделе ниже.
Определение устанавливаемого пакета с помощью setup.cfg
Чтобы разместить устанавливаемое приложение Django на PyPI, вам нужно сначала поместить его в пакет. PyPI ожидает распространения в виде файлов с исходным кодом. Они создаются с использованием инструментов настройки. Для этого вам необходимо создать файлы setup.cfg и setup.py на том же уровне каталога, что и каталог receipts.
Однако, прежде чем углубляться в это, вы должны убедиться, что у вас есть некоторая документация. Вы можете включить описание проекта в setup.cfg , которое автоматически отображается на странице проекта PyPI. Обязательно напишите README.rst или что-то подобное с информацией о вашем пакете.
PyPI по умолчанию поддерживает формат reStructuredText , но он также может обрабатывать Markdown с дополнительными параметрами :
1# setup.cfg
2[metadata]
3name = realpython-django-receipts
4version = 1.0.3
5description = Sample installable django app
6long_description = file:README.rst
7url = https://github.com/realpython/django-receipts
8license = MIT
9classifiers =
10 Development Status :: 4 - Beta
11 Environment :: Web Environment
12 Intended Audience :: Developers
13 License :: OSI Approved :: MIT License
14 Operating System :: OS Independent
15 Programming Language :: Python :: 3 :: Only
16 Programming Language :: Python :: 3.7
17 Programming Language :: Python :: Implementation :: CPython
18 Topic :: Software Development :: Libraries :: Application Frameworks
19 Topic :: Software Development :: Libraries :: Python Modules
20
21[options]
22include_package_data = true
23python_requires = >=3.6
24setup_requires =
25 setuptools >= 38.3.0
26install_requires =
27 Django>=2.2
setup.cfg описывает пакет, который вы создадите. Строка 6 использует директиву file: для чтения в вашем файле README.rst. Это избавляет вас от необходимости писать длинное описание в двух местах.
Запись install_requires в строке 26 сообщает любым установщикам, таким как pip install , о зависимостях вашего приложения. Вы всегда захотите привязать свое устанавливаемое приложение Django к его минимально поддерживаемой версии Django.
Если в вашем коде есть зависимости, которые необходимы только для запуска тестов, вы можете добавить запись tests_require = . Например, до того, как mock стал частью стандартной библиотеки Python, обычно можно было увидеть tests_require = mock> = 2.0.0 в setup.cfg .
Рекомендуется включать в пакет файл pyproject.toml . Прекрасная статья Бретта Кэннона на эту тему подробно расскажет об этом. Pyproject.toml файл также включен образец кода.
Вы почти готовы создать пакет для устанавливаемого приложения Django. Самый простой способ проверить это - использовать ваш образец проекта - еще одна веская причина сохранить образец проекта под рукой. Команда pip install поддерживает локально определенные пакеты. Это можно использовать, чтобы убедиться, что ваше приложение по-прежнему работает с проектом. Однако есть одно предостережение: в этом случае setup.cfg не будет работать сам по себе. Вам также необходимо создать версию с прокладкой для setup.py :
#!/usr/bin/env python
if __name__ == "__main__":
import setuptools
setuptools.setup()
Этот сценарий автоматически использует ваш файл setup.cfg . Теперь вы можете установить локальную редактируемую версию пакета, чтобы протестировать ее из sample_project . Чтобы быть уверенным вдвойне, лучше всего начать с совершенно новой виртуальной среды. Добавьте следующий requirements.txt файл внутри sample_project каталога:
# requirements.txt
-e ../../django-receipts
«-e» говорит pip, что это локальная редактируемая установка. Теперь вы готовы к установке:
$ pip install -r requirements.txt
Obtaining django-receipts (from -r requirements.txt (line 1))
Collecting Django>=3.0
Using cached Django-3.0.4-py3-none-any.whl (7.5 MB)
Collecting asgiref~=3.2
Using cached asgiref-3.2.7-py2.py3-none-any.whl (19 kB)
Collecting pytz
Using cached pytz-2019.3-py2.py3-none-any.whl (509 kB)
Collecting sqlparse>=0.2.2
Using cached sqlparse-0.3.1-py2.py3-none-any.whl (40 kB)
Installing collected packages: asgiref, pytz, sqlparse, Django, realpython-django-receipts
Running setup.py develop for realpython-django-receipts
Successfully installed Django-3.0.4 asgiref-3.2.7 pytz-2019.3 realpython-django-receipts sqlparse-0.3.1
Список install_requires в setup.cfg сообщает pip install, что ему нужен Django. Django нуждается asgiref , pytz и sqlparse . Все зависимости учтены, и теперь вы можете запустить свой сервер sample_project Django dev. Поздравляем - теперь ваше приложение упаковано, и на него есть ссылки в образце проекта!
Тестирование нескольких версий с помощью tox
Django и Python постоянно развиваются. Если вы собираетесь поделиться своим устанавливаемым приложением Django со всем миром, вам, вероятно, придется протестировать его в нескольких средах . Инструменту tox требуется небольшая помощь, чтобы иметь возможность протестировать ваше приложение Django. Сделайте следующее изменение внутри setup.cfg :
1# setup.cfg
2[metadata]
3name = realpython-django-receipts
4version = 1.0.3
5description = Sample installable django app
6long_description = file:README.rst
7url = https://github.com/realpython/django-receipts
8license = MIT
9classifiers =
10 Development Status :: 4 - Beta
11 Environment :: Web Environment
12 Intended Audience :: Developers
13 License :: OSI Approved :: MIT License
14 Operating System :: OS Independent
15 Programming Language :: Python :: 3 :: Only
16 Programming Language :: Python :: 3.7
17 Programming Language :: Python :: Implementation :: CPython
18 Topic :: Software Development :: Libraries :: Application Frameworks
19 Topic :: Software Development :: Libraries :: Python Modules
20
21[options]
22include_package_data = true
23python_requires = >=3.6
24setup_requires =
25 setuptools >= 38.3.0
26install_requires =
27 Django>=2.2
28test_suite = load_tests.get_suite
Строка 28 сообщает менеджеру пакетов использовать сценарий load_tests.py для получения своего набора тестов.”tox” использует это, чтобы запустить испытания. Вспомните get_suite () в load_tests.py:
1# Defined inside load_tests.py
2def get_suite(labels=default_labels):
3 from django.test.runner import DiscoverRunner
4 runner = DiscoverRunner(verbosity=1)
5 failures = runner.run_tests(labels)
6 if failures:
7 sys.exit(failures)
8
9 # If this is called from setuptools, then return a test suite
10 return TestSuite()
По общему признанию, то, что здесь происходит, немного странно. Обычно поле test_suite в setup.cfg указывает на метод, который возвращает набор тестов. Когда tox вызывает setup.py , он считывает параметр test_suite и запускает load_tests.get_suite () .
Если этот вызов не вернул объект TestSuite , то tox пожаловался бы. Странная часть заключается в том, что нам не нужно, чтобы tox получал набор тестов, потому что tox не знает о тестовой среде Django. Вместо этого get_suite () создает DiscoverRunner и возвращает пустой объект TestSuite в строке 10.
Вы не можете просто заставить DiscoverRunner вернуть набор тестов, потому что вам нужно вызвать DiscoverRunner.run_tests () для правильного выполнения настройки и разборки тестовой среды Django. Простая передача правильных тестов в tox не сработает, потому что база данных не будет создана. get_suite () запускает все тесты, но как побочный эффект вызова функции, а не как обычный случай возврата набора тестов для выполнения tox .
Инструмент tox позволяет тестировать несколько комбинаций. Tox.ini файл определяет , какие комбинации сред для тестирования. Вот пример:
[tox]
envlist = py{36,37}-django220, py{36,37}-django300
[testenv]
deps =
django220: Django>=2.2,<3
django300: Django>=3
commands=
python setup.py test
В этом файле указано, что тесты следует запускать для Python 3.6 и 3.7 в сочетании с Django 2.2 и 3.0. Всего четыре тестовых среды. В разделе commands = вы указываете tox вызвать тест через setup.py . Вот как вы вызываете ловушку test_suite = load_tests.get_suite в setup.cfg .
Примечание: субкоманда test в setup.py была устаревшей . В настоящее время упаковка в Python быстро меняется. Хотя вызывать тест python setup.py обычно не рекомендуется, именно он работает в данной конкретной ситуации.
Публикация в PyPI
Наконец, пришло время поделиться своим устанавливаемым приложением Django на PyPI. Существует несколько инструментов для загрузки пакета, но в этом руководстве вы сосредоточитесь на Twine . Следующий код собирает пакеты и вызывает Twine:
$ python -m pip install -U wheel twine setuptools
$ python setup.py sdist
$ python setup.py bdist_wheel
$ twine upload dist/*
Первые две команды создают исходный и двоичный дистрибутивы вашего пакета. Вызов шпагата загружается в PyPI. Если у вас есть файл .pypirc в вашем домашнем каталоге, вы можете предварительно установить свое имя пользователя, поэтому единственное, что вам будет предложено, это ваш пароль:
[disutils]
index-servers =
pypi
[pypi]
username: <YOUR_USERNAME>
Заключение
Приложения Django полагаются на структуру проекта Django, поэтому их упаковка по отдельности требует дополнительных действий. Вы видели, как создать устанавливаемое приложение Django, извлекая его из проекта, упаковывая и публикуя в PyPI. Обязательно загрузите образец кода по ссылке ниже:
В этом уроке вы узнали, как:
- Использовать фреймворк Django вне проекта
- Вызывать команды управления Django в приложении, не зависящем от проекта
- Написать скрипт, который вызывает тесты Django , при желании используя одну тестовую метку.
- Создать файл setup.py для определения вашего пакета
- Изменить скрипт setup.py для работы с tox
- Использовать Twine для загрузки устанавливаемого приложения Django
Вы готовы поделиться своим следующим приложением со всем миром. Удачного кодирования!
Что изучать дальше?
Django, упаковка и тестирование - это очень глубокие темы. Там много информации. Чтобы копнуть глубже, ознакомьтесь со следующими ресурсами:
- Документация Django
- Начало работы с Django: создание приложения-портфолио
- Учебники по Django
- Управление несколькими версиями Python с помощью pyenv
- Что такое пип? Руководство для начинающих питонистов
- Как опубликовать пакет Python с открытым исходным кодом в PyPi
- Начало работы с тестированием на Python
- Поэзия
- Flit
PyPI имеет множество устанавливаемых приложений Django, которые стоит попробовать. Вот некоторые из самых популярных: