如何编写可安装的 Django 应用程序

举报
Yuchuan 发表于 2021/12/24 19:27:57 2021/12/24
【摘要】 Django 应用程序依赖于 Django 项目结构,因此单独打包它们需要额外的步骤。您已经了解了如何通过从项目中提取、打包并在 PyPI 上共享来制作可安装的 Django 应用程序。请务必从以下链接下载示例代码: 在本教程中,您学习了如何: 在项目之外使用Django 框架 在独立于项目的应用程序上调用 Django管理命令 编写一个调用Django 测试的脚本,可选择使用单个测试标签 构

目录

Django框架中,项目是指特定网站的配置文件和代码的集合。Django 将业务逻辑分组到它所谓的应用程序中,它们是 Django 框架的模块。有很多关于如何构建项目和其中的应用程序的文档,但是当需要打包可安装的 Django 应用程序时,信息就更难找到了。

在本教程中,您将学习如何从Django 项目中取出应用程序并将其打包以使其可安装。打包应用程序后,您可以在PyPI上共享它,以便其他人可以通过pip install

在本教程中,您将学习:

  • 编写独立应用程序在项目内部编写应用程序有什么区别
  • 如何创建用于发布 Django 应用程序的setup.cfg文件
  • 如何在Django 项目之外引导 Django,以便您可以测试您的应用程序
  • 如何使用 Python 和 Django 的多个版本进行测试 tox
  • 如何使用Twine将可安装的 Django 应用程序发布到 PyPI

先决条件

本教程需要对DjangopipPyPIpyenv(或等效的虚拟环境工具)和tox. 要了解有关这些主题的更多信息,请查看:

在项目中启动示例 Django 应用程序

本教程包含一个工作包,可帮助指导您完成制作可安装 Django 应用程序的过程。

即使您最初打算将 Django 应用程序作为包提供,您也可能会从项目内部开始。为了演示从 Django 项目转移到可安装的 Django 应用程序的过程,我在 repo 中提供了两个分支。该项目的分支是一个Django项目的内部应用程序的起始状态。该主分支是完成安装的应用程序。

您还可以在PyPI realpython-django-receipts 包页面下载完成的应用程序。您可以通过运行安装该软件包pip install realpython-django-receipts

示例应用程序是收据上行项目的简短表示。在项目分支中,您将找到一个名为的目录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 项目

示例项目和收据应用程序是使用 Djangoadmin命令和一些小的编辑创建的。首先,在干净的虚拟环境中运行以下代码:

$ python -m pip install Django
$ django-admin startproject sample_project
$ cd sample_project
$ ./manage.py startapp receipts

这将创建sample_project项目目录结构和receipts应用程序子目录,其中包含您将用于创建可安装 Django 应用程序的模板文件。

接下来,该sample_project/settings.py文件需要进行一些修改:

  • 添加'127.0.0.1'ALLOWED_HOSTS设置中,以便您可以在本地进行测试。
  • 添加'receipts'INSTALLED_APPS列表中。

您还需要receiptssample_project/urls.py文件中注册应用程序的 URL 。为此,请添加path('receipts/', include('receipts.urls'))url_patterns列表中。

探索收据示例应用程序

该应用程序由两个 ORM 模型类组成:ItemReceipt. 本Item类包含了描述和成本数据库字段声明。成本包含在DecimalField. 使用浮点数来表示货币是危险的——在处理货币时你应该总是使用定点数

Receipt班是一个收集点Item的对象。这是一个实现ForeignKeyItem该点ReceiptReceipt还包括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_json()视图遍历所有的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

此代码ModelAdmin为每个ReceiptItem类创建一个 Django ,并将它们注册到 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文件中,以便在您的网站上提供收据视图。

一切就绪后,可以运行./manage.py makemigrations receipts,使用Django admin添加数据,然后访问/receipts/receipt_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访问receipt_json视图,从而产生包含Receipt对象及其对应Item对象的 JSON 响应。

在项目中测试 App

Djangounittest用自己的测试功能扩充了 Python包,使您能够将设备预加载到数据库中并运行您的测试。收据应用程序定义了一个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)

夹具创建两个Receipt对象和四个对应的Item对象。单击下面的可折叠部分以仔细查看夹具的代码。

receives.json 测试装置显示隐藏

您可以使用 Djangomanage.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'...

Runningmanage.py test运行 中定义的单个测试receipts/tests.py并显示结果。

制作可安装的 Django 应用

您的目标是在没有项目的情况下共享收据应用程序,并使其可被其他人重复使用。您可以压缩receipts/目录并将其分发出去,但这有些限制。相反,您希望将应用程序分成一个包,以便它可以安装。

创建可安装的 Django 应用程序的最大挑战是 Django 需要一个项目。没有项目的应用程序只是一个包含代码的目录。没有项目,Django 不知道如何处理您的代码,包括运行测试。

将您的 Django 应用程序移出项目

保留一个示例项目是个好主意,这样您就可以运行 Django 开发服务器并使用您的应用程序的实时版本。您不会将此示例项目包含在应用程序包中,但它仍然可以存在于您的存储库中。按照这个想法,您可以通过将可安装的 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 shell 。您需要配置 Django 并使其可用。

Django's settings.configure()anddjango.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().

上面的代码是一个相当精简的配置。收据应用程序不会对会话或模板执行任何操作,因此INSTALLED_APPS只需要"receipts",并且您可以跳过任何中间件定义。该USE_TZ=True值是必需的,因为Receipt模型包含created时间戳。否则,您将在加载测试夹具时遇到问题。

使用可安装的 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-y 事情。我经常定义四个实用程序脚本:

  1. load_tests.py 测试您的应用
  2. makemigrations.py 创建迁移文件
  3. migrate.py 执行表迁移
  4. djangoshell.py 生成一个知道你的应用程序的 Django shell

测试可安装的 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)

DjangoDiscoverRunner是一个测试发现类,与 Python 的unittest. 它负责设置测试环境、构建测试套件、设置数据库、运行测试,然后将其全部拆除。从第 11 行开始,get_suite()获取测试标签列表并直接调用DiscoverRunner它们。

该脚本类似于 Django 管理命令的test作用。该__main__块将任何命令行参数传递给get_suite(),如果没有,则它将传递到应用程序的测试套件中receipts.tests。您现在可以load_tests.py使用测试标签参数调用并运行单个测试。

第 19 行是一个特殊情况,可在使用tox. 您将tox后面的部分中了解更多信息。您还可以DiscoverRunner在下面的可折叠部分中查看潜在的替代品。

DiscoverRunner 替代方案显示隐藏

定义您的可安装包 setup.cfg

要将可安装的 Django 应用程序放在 PyPI 上,您需要先将其放入一个包中。PyPI中期望一个eggwheel或源分布。这些是使用setuptools. 为此,您需要在与目录相同的目录级别创建一个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文件。这使您不必在两个地方编写长描述。

26 行install_requires条目告诉任何安装程序,例如,您的应用程序具有的依赖项。您将始终希望将可安装的 Django 应用程序与其支持的最低 Django 版本联系起来。pip install

如果您的代码具有任何仅运行测试所需的依赖tests_require =项,则您可以添加一个条目。例如,在mock成为标准 Python 库的一部分之前,tests_require = mock>=2.0.0setup.cfg.

pyproject.toml在你的包中包含一个文件被认为是最佳实践。布雷特·坎农 (Brett Cannon)关于该主题出色文章可以让您了解详细信息。pyproject.toml示例代码中还包含一个文件。

您几乎已准备好为可安装的 Django 应用程序构建包。测试它的最简单方法是使用您的示例项目——另一个保留示例项目的好理由。该pip install命令支持本地定义的包。这可用于确保您的应用程序仍然适用于项目。但是,一个警告是setup.cfg在这种情况下它不会单独工作。您还必须创建一个 shim 版本setup.py

#!/usr/bin/env python

if __name__ == "__main__":
    import setuptools
    setuptools.setup()

此脚本将自动使用您的setup.cfg文件。您现在可以安装包的本地可编辑版本以从sample_project. 可以肯定的是,最好从一个全新的虚拟环境开始。requirements.txtsample_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 需要asgirefpytz, 和sqlparse. 所有依赖项都已处理完毕,您现在应该可以运行sample_projectDjango 开发服务器了。恭喜 - 您的应用程序现在已从示例项目中打包和引用!

测试多个版本 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字段 insetup.cfg指向返回一组测试的方法。当tox调用时setup.py,它读取test_suite参数并运行load_tests.get_suite()

如果这个调用没有返回一个TestSuite对象,那么tox就会抱怨。奇怪的是,您实际上并不想tox获得一套测试,因为tox您不知道 Django 测试环境。相反,在第 10 行get_suite()创建 aDiscoverRunner并返回一个空TestSuite对象

您不能简单地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_suitesetup.cfg.

注意:test子命令setup.py已被弃用。目前,Python 中的打包正在迅速变化。虽然python setup.py test通常不推荐调用,但在这种特定情况下它是有效的。

发布到 PyPI

最后,是时候在 PyPI 上分享您的可安装 Django 应用程序了。有多种工具可用于上传包,但在本教程中您将重点介绍Twine。以下代码构建包并调用 Twine:

$ python -m pip install -U wheel twine setuptools
$ python setup.py sdist
$ python setup.py bdist_wheel
$ twine upload dist/*

前两个命令构建包的源代码和二进制分发版。调用twine上传到 PyPI。如果您.pypirc的主目录中有一个文件,那么您可以预设您的用户名,这样系统只会提示您输入密码:

[disutils]
index-servers =
    pypi

[pypi]
username: <YOUR_USERNAME>

我经常使用一个小的shell脚本来grep从代码中获取版本号。然后我调用git tag用版本号标记repo,删除旧的build/dist/目录,并调用上述三个命令。

有关使用 Twine 的更多详细信息,请参阅如何将开源 Python 包发布到 PyPI。Twine 的两个流行替代品是PoetryFlit。Python 中的包管理正在迅速变化。PEP 517PEP 518正在重新定义如何描述 Python 包和依赖项。

结论

Django 应用程序依赖于 Django 项目结构,因此单独打包它们需要额外的步骤。您已经了解了如何通过从项目中提取、打包并在 PyPI 上共享来制作可安装的 Django 应用程序。请务必从以下链接下载示例代码:

在本教程中,您学习了如何:

  • 在项目之外使用Django 框架
  • 在独立于项目的应用程序上调用 Django管理命令
  • 编写一个调用Django 测试的脚本,可选择使用单个测试标签
  • 构建一个setup.py文件来定义你的包
  • 修改setup.py脚本以适应tox
  • 使用Twine上传可安装的 Django 应用

您已准备好与全世界分享您的下一个应用程序。快乐编码!

进一步阅读

Django、打包、测试都是非常深奥的话题。那里有很多信息。要深入挖掘,请查看以下资源:

PyPI有大量可安装的 Django 应用程序,值得一试。以下是一些最受欢迎的:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。