Django的测试驱动开发
#python #django #pytest #testdriven

初始化测试项目

打开终端并导航到包含您的Django项目的目录。在我的计算机上,该文件夹称为项目。

cd Projects

使用django-admin.py实用程序创建一个新的Django项目, test-test-driven ,然后将目录更改为新生成的文件夹。

django-admin.py startproject test-driven && cd test-driven

使用virtualenvwrapper实用程序创建一个新的虚拟环境。

mkvirtualenv testdriven

或使用venv实用程序创建新的虚拟环境。

python -m venv env 

启动pycharm,然后单击“文件”>在菜单栏中打开。查找并选择测试驱动的项目文件夹。提示时,选择在新窗口中打开项目。等待该项目打开,然后单击菜单栏中的Pycharm> Pexences>项目解释器以打开项目解释器面板。单击面板右上角的齿轮图标;然后,选择“添加本地...”选项。在文件资源管理器中,导航到您的虚拟环境文件夹,找到 test-testriven-em>目录,然后在 bin 文件夹中选择 python2.7 文件。在我的计算机上,此文件位于〜/virtualenvs/test-test-drive/bin 。在Pycharm中,关闭首选项窗口。您刚刚添加的虚拟环境现在被Pycharm用于 test-test-driven 项目。

现在我们已经安装了虚拟环境,我们必须在项目中添加一些依赖项。首先,安装django。

pip install django

通过从菜单栏中选择运行,返回Pycharm并运行Django项目。 Pycharm应启动开发服务器并在终端窗口中记录进度。单击PyCharm提供的链接,您的浏览器应打开到读取“它工作的默认Django”页面,

>

接下来,安装Psycopg Python数据库适配器,以便我们可以将PostgreSQL用作数据库。

pip install psycopg2

在pycharm中,打开 test-testriven-em> app目录中的settings.py文件。将DATABASES属性更改为使用PostgreSQL代替SQLITE。

# test-driven/settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'test-driven',
        'USER': 'admin',
        'PASSWORD': 'password1',
        'HOST': 'localhost',
        'PORT': '5432'
    }
}

启动PGADMIN III程序,并创建一个名为 testDriveDrived 的新数据库。在出现的窗口中,在“名称”字段中输入 test-driend-driend 在所有者字段中 admin 。关闭窗口,然后找到新创建的数据库。注意它还没有表。

返回pycharm并删除最初运行django项目时创建的 db.sqlite3 文件。迁移数据库以创建默认的Django表。

python manage.py migrate

如果您在PGADMIN III中检查了数据库,则应看到10个新表。在这一点上,我们准备开始使用git跟踪文件更改。初始化git存储库并提交我们到目前为止所做的所有文件更改。

git init
git add .
git commit -m "Initial commit."

开始测试驱动的开发过程

通常,测试驱动的开发工作流程遵循以下步骤:

  1. 写一个短篇小说,捕获用户完成要求所需的内容。
  2. 线框是满足要求的应用程序或功能的简单模型。
  3. 编写一个用例,该用例叙述了基于线框的场景。
  4. 写一个功能测试,遵循用例中概述的操作。
  5. 编写单元测试以评估低级代码的可操作性。
  6. 编写满足测试的功能代码。

这些是测试驱动开发的关键原则:

  • 在编程任何功能代码之前,请始终写出失败的测试。
  • 最小进行测试所需的代码数量。
  • 测试通过时,在必要时重新启动过程或重构代码。

写一个用户故事

传统上,用户的故事应足够短,可以写在书信上。它是用客户的语言编写的,避免使用技术词汇。这是我们的用户故事:

约翰想要一个允许他管理组织列表的应用程序。

线框应用程序

我们的应用程序将由两个页面组成,a 主页列出了组织和生成新组织的A 创建页面。绘制应用程序的简单布局,并确保包括组件和页面状态。有关使用模型流创建的应用程序框架的示例,请参见附件文档。

编写用例

使用我们的线框来想象典型用户将如何与应用程序进行交互。遵循预期用户体验的大纲:

约翰转到主页。他看到一个带有单个单元格的数据表,上面写着“无组织”。他还看到一个标有“创建组织”的按钮。他单击创建按钮。页面刷新和约翰看到了带有单个文本输入控件和提交按钮的表格。约翰进入组织名称并单击“提交”按钮。页面刷新,约翰注意到该桌子现在有一排,其中包含他添加的组织的详细信息。

编写功能测试

功能测试(接受测试)遵循用户案例中规定的方案。它从用户的角度评估系统。功能测试很有帮助,因为它们使我们能够模仿真实用户的行为,并且可以随着时间的推移一致地重复。请勿编写详尽的功能测试,以触摸所有可能的交互,并涵盖系统内可能发生的每一个结果。取而代之的是,专注于用户体验的重要方面,并编写涵盖用户期望遵循的最流行和直接操作集的测试。实际上,单独的质量保证(QA)团队应评估错误的应用程序。

利用Selenium之类的软件来驱动Django中的功能测试。 Selenium提供了允许自动测试以真实方式与浏览器交互的功能。示例包括打开和关闭浏览器窗口,导航到URL以及使用鼠标和键盘事件(例如人类用户)与屏幕组件进行交互。

通过在测试驱动的项目文件夹中创建一个称为 function_tests 的新Python目录来实现功能测试。打开项目的 settings.py 文件,然后更改INSTALLED_APPS属性,如下所示。

# test-driven/settings.py

DEFAULT_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

LOCAL_APPS = (
    'functional_tests',
)

INSTALLED_APPS = DEFAULT_APPS + LOCAL_APPS

在终端安装硒包。

pip install selenium

function_tests 文件夹中创建一个新的python文件,并将其称为 test_organizations 。设置一个简单的测试用例。

# functional_tests/test_organizations.py

from selenium.webdriver.firefox.webdriver import WebDriver
from django.test import LiveServerTestCase


class OrganizationsTest(LiveServerTestCase):
    def setUp(self):
        self.browser = WebDriver()
        self.browser.implicitly_wait(5)

    def tearDown(self):
        self.browser.quit()

在Django中,功能测试扩展了django.test.LiveServerTestCase类。与标准TestCase不同,LiveServerTestCase启动了一个真实的开发服务器,该服务器允许像硒这样的自动测试客户端在浏览器中运行。 setUp()方法在测试开始之前立即运行,并且在测试完成后直接运行tearDown()方法。运行时,每个测试将打开Web浏览器,执行一些行为,然后关闭浏览器。运行功能测试并注意输出。

python manage.py test functional_tests

功能测试成功运行而没有故障。实际上,我们实际上尚未对任何测试进行编程。创建一个功能测试,遵循用例叙述的操作。在下面的示例中,用例分解为可挖掘的说明,以指导我们的开发。

# functional_tests/test_organizations.py

def test_new_organizations_are_shown_in_list(self):
    # John goes to the home page. 
    self.browser.get(self.live_server_url)

    # He sees a data table with a single cell that says "No organizations".
    # He also sees a button labelled 'Create organization'. He clicks the create button. 
    cell = self.browser.find_element_by_xpath('//table/tbody/tr/td')
    self.assertEqual(cell.text, 'No organizations')
    create_button = self.browser.find_element_by_id('create-button')
    self.assertEqual(create_button.text, 'Create organization')
    create_button.click()

    # The page refreshes and John sees a form with a single text input control and a submit button.
    name_input = self.browser.find_element_by_name('name')
    self.assertEqual(name_input.get_attribute('placeholder'), 'Organization name')

    # John enters an organization name and clicks the submit button. 
    name_input.send_keys('TDD Organization')
    submit_button = self.browser.find_element_by_id('submit')
    self.assertEqual(submit_button.text, 'Submit')
    submit_button.click()

    # The page refreshes and John notices that the table now has a single row containing 
    # the details of the organization that he added.
    row = self.browser.find_element_by_xpath('//table/tbody/tr')
    self.assertIn('TDD Organization', row.text)

我们的功能测试非常简单:14行代码和5条断言。每种测试方法都从前缀test_开始。硒提供了一些非常有用的方法来与页面上的元素进行交互。浏览测试中发生的事情:

  • 主页在浏览器中打开。
  • 浏览器搜索HTML内容为<td>元素搜索并确保其显示“没有组织”。
  • 浏览器为创建按钮搜索HTML内容,然后单击其。
  • 此时,客户应导航到新页面。刷新时,浏览器会检查文本输入控件和提交按钮的HTML内容。它还证实了文本字段的占位符,上面写着“组织名称”。
  • 浏览器将组织名称输入文本字段,然后单击“提交”按钮。
  • 此时,客户端应返回主页,并进行浏览器检查以确保表包含一行,其中包含上一页上添加的组织名称。

注意此测试如何结合用例和线框。再次运行功能测试,看看会发生什么。

python manage.py test functional_tests

Web浏览器打开,等待5秒钟,然后以失败而关闭。这很好!实际上,测试最初应始终失败。请注意,测试失败了消息“无法找到元素”。它在渲染的HTML中找不到<td>元素。您可能还观察到,当网页在浏览器中打开时找不到网页本身。为了解决这些失败,我们需要编写第一个单元测试。

编写单元测试

虽然功能测试模仿了实际用户与应用程序进行交互,但单位测试可确保代​​码位的功能本身可以按预期工作。我们的Web应用程序无法加载,因为我们还没有任何加载。我们缺少Django Web应用程序的基本元素:URL,视图和模板。

让我们创建一个名为组织的新的Django应用程序。我们正在创建一个新的应用程序,而不是使用默认项目应用程序来反应Django创建者制定的准则。这个想法是Django应用程序应该从项目中是自主的,以便可以轻松地将它们作为单个模块包装和分发。

python manage.py startapp organizations

您会注意到您的Django项目已在Pycharm中更新。打开您的项目 settings.py ,然后添加organizations作为安装的本地应用。此操作将模块配置和注册Django,因此在整个项目中都可以访问URL,视图,表单,模型等。

# test-driven/settings.py

LOCAL_APPS = (
    'functional_tests',
    'organizations',
)

组织内部文件夹,删除 tests.py 文件,并创建一个称为tests的python目录。创建一个名为 test_views 的新的Python文件,然后将其放在 tests 文件夹中。这是我们的单位测试将居住的地方。添加下面显示的代码为我们的视图创建单元测试类。

# organizations/tests/test_views.py

from django.test import Client
from django.test import TestCase


class HomeViewTest(TestCase):
    def setUp(self):
        self.client = Client()

此类创建一个测试用例,并设置每种测试方法,以使用伪造的客户端,该客户将与框架交互,就像真实一样。请记住,这是Django工作的基本方法:

  1. Web客户端将HTTP请求发送到服务器。
  2. 服务器将请求传递到Django Web框架。
  3. Django从请求中解析URL并将其解析为关联的视图方法。
  4. 视图处理一些通常涉及与数据库进行通信然后返回HTTP响应的代码。
  5. 响应通常包含一串HTML文本,通常是从模板呈现的。
  6. Web客户端接收响应并将内容显示为网页。

让我们测试我们的新单元测试。

python manage.py test organizations

我们的测试正在工作。

让我们在我们的单元测试中添加一些实际逻辑。在这一点上,我们可以合理地期望用户在浏览器中输入URL时应显示一个网页。因此,我们的第一个单元测试非常简单。

# organizations/tests/test_views.py

def test_view_renders_template(self):
    response = self.client.get('/')
    self.assertTemplateUsed(response, 'organizations/home.html')

在此测试中,我们正在模拟对主页的呼叫,并确认我们正在呈现预期的模板。当我们运行单元测试时,它们会失败。请记住,这是一件好事!现在,让我们编写进行此测试通过所需的最少代码。我们需要的第一件事是模板。在您的组织文件夹中创建模板/组织目录。在新目录中创建一个简单的HTML文件,并将其命名 home.html

<!-- organizations/templates/organizations/home.html -->

<!DOCTYPE html>

<html>

<head lang="en">
    <meta charset="utf-8">
    <title>Test-Driven</title>
</head>

<body></body>

</html>

接下来,从 ablysations 文件夹打开 views.pys.py 文件。添加渲染模板所需的最小代码。

# organizations/views.py

def home_view(request):
    return render(request, 'organizations/home.html')

最后,打开 urls.py test-test-drive 文件夹中的文件,然后调整URL配置,如图所示。

# test-driven/urls.py

from django.conf.urls import patterns
from django.conf.urls import re_path

urlpatterns = patterns('organizations.views',
    re_path(r'^$', 'home_view', name='home'),
)

再次运行单元测试。他们过去了!让我们再次尝试功能测试。它们的错误与以前相同,但至少我们实际上呈现了预期的网页。这是提交的好时机。查看项目的状态,然后添加并提交我们所有的新文件和修改的文件。

git status
git add .
git commit -m "Added functional tests and organizations."

探索测试驱动的开发过程

在这一点上,我们对TDD的基本流程的工作原理有所了解。我们创建了功能测试,该测试正在推动整个工作流程。我们创建了第一个单元测试,以推动我们的功能测试失败。该过程是迭代的:运行功能测试,直到破裂,创建失败的单元测试,编写少量代码以使其通过,然后重复直到不再存在错误。在这一点上,我们的功能测试失败了,因为它找不到表单元格。让我们通过将其添加到模板中进行补救。

<body>
    <table>
        <tbody>
            <tr>
                <td>No organizations</td>
            </tr>
        </tbody>
    </table>
</body>

运行功能测试。他们再次失败,但请注意,我们有一个新的错误!我们已经向前迈出了一步。现在,找不到创建按钮。让我们返回模板。

<body>
    <a id="create-button" href="#">Create organization</a>
    <table>
        <tbody>
            <tr>
                <td>No organizations</td>
            </tr>
        </tbody>
    </table>
</body>

运行功能测试。我们进步了一点!现在,测试无法找到文本输入控件,但是如果我们查看用户的故事,我们会意识到测试失败,因为页面永远不会更改。如果我们查看线框,我们可以看到我们需要第二页,即创建页面。让我们遵循与创建主页相同的步骤。首先,创建一个单元测试。请注意我们如何使用新的测试用例作为新视图。

# organizations/tests/test_views.py

class HomeViewTest(TestCase): ...

class CreateViewTest(TestCase):
    def setUp(self):
        self.client = Client()

    def test_view_renders_template(self):
        response = self.client.get('/create/')
        self.assertTemplateUsed(response, 'organizations/create.html')

接下来,我们按照与主页相同的步骤来创建URL,查看和模板。在模板/组织中创建一个新的HTML文件文件夹,称为 create.html

<!-- organizations/templates/organizations/create.html -->

<!DOCTYPE html>

<html>

<head lang="en">
    <meta charset="utf-8">
    <title>Test-Driven</title>
</head>

<body></body>

</html>

创建其他django文件。

# organizations/views.py

def home_view(request): 

def create_view(request):
    return render(request, 'organizations/create.html')
# test-driven/urls.py

urlpatterns = patterns('organizations.views',
    re_path(r'^$', 'home_view', name='home'),
    re_path(r'^create/$', 'create_view', name='create'),
)

运行单元测试。他们过去了!现在我们有了一个工作的网页,我们可以在 home.html 模板中链接到它。

<!-- organizations/templates/organizations/create.html -->

<a id="create-button" href="{% url 'create' %}">Create organization</a>

再次运行功能测试。我们可以看到浏览器导航到创建页面,因此我们再过了一个障碍。测试失败是因为找不到文本输入字段。让我们添加它。

<!-- organizations/templates/organizations/create.html -->

<input type="text" name="name" placeholder="Organization name">

请记住,我们只希望在测试中前进所需的最低代码。不要领先自己!功能测试失败了,但是我们又进步了一步。我们需要提交按钮。

<!-- organizations/templates/organizations/create.html -->

<button id="submit">Submit</button>

我们迈出了又一步!再次,该测试失败了,因为它找不到元素,但是我们知道真正的原因是因为该页面尚未回到家中。在我们做其他事情之前,让我们进行更改。

git add .
git commit -m "Home and create pages rendering correctly."

我们需要我们的申请在发布请求后返回主页。让我们创建一个新的单元测试。

# organizations/tests/test_views.py

class CreateViewTest(TestCase):
    ...

    def test_view_redirects_home_on_post(self):
        response = self.client.post('/create/')
        self.assertRedirects(response, '/')

单元测试失败,所以让我们通过。

# organizations/views.py

from django.core.urlresolvers import reverse
from django.shortcuts import redirect

...

def create_view(request):
    if request.method == 'POST':
        return redirect(reverse('home'))

    return render(request, 'organizations/create.html')

单元测试通过。功能测试仍在失败,因为我们实际上还没有在控件中添加任何发布行为。让我们升级我们的模板,以便实际使用表单。请记住,所有帖子电话都需要CSRF令牌。

<!-- organizations/templates/organizations/create.html -->

<form action="{% url 'create' %}" method="post">
    {% csrf_token %}
    <input type="text" name="name" placeholder="Organization name">
    <button id="submit" type="submit">Submit</button>
</form>

使功能测试转向下一个故障。我们创建的新组织应显示在列表中,但事实并非如此。是时候进行一些更高级的测试了。如果我们想保存我们的组织,我们需要使用模型的使用。我们必须从提交表单时的帖子数据中获取组织的名称。接下来,我们需要用给定名称保存组织。最后,我们必须为主页提供组织列表。那是一个艰巨的任务。让我们开始一些单位测试。

# organizations/tests/test_views.py

from ..models import Organization

class HomeViewTest(TestCase):
    ...

    def test_view_returns_organization_list(self):
        organization = Organization.objects.create(name='test')
        response = self.client.get('/')
        self.assertListEqual(response.context['organizations'], [organization])

pycharm已经警告我们,它找不到该模型,但无论如何都要运行单元测试。当然,我们会收到导入错误。让我们创建模型。打开 model.py file 组织文件夹并添加以下代码。

# organization/models.py

from django.db import models


class Organization(models.Model):
    name = models.CharField(max_length=250)

pycharm停止抱怨,但是当我们运行设备测试时会发生什么?我们会收到一个编程错误!我们需要将Organization模型添加到数据库中。幸运的是,django使通过终端执行此操作真的很容易。

python manage.py makemigrations organizations
python manage.py migrate organizations

我们的单元测试仍在失败,但是已经处理了编程错误。让我们用最小的代码进行测试。

# organizations/views.py

from .models import Organization


def home_view(request):
    return render(request, 'organizations/home.html', {
        'organizations': list(Organization.objects.all())
    })

可以通过我们的单位测试。组织正在传递到主页,但尚未保存。让我们为我们的创建视图编写另一个单元测试。

# organizations/tests/test_views.py

class CreateViewTest(TestCase):
    ...

    def test_view_creates_organization_on_post(self):
        self.client.post('/create/', data={'name': 'test'})
        self.assertEqual(Organization.objects.count(), 1)
        organization = Organization.objects.last()
        self.assertEqual(organization.name, 'test')

此测试将邮政请求发送给使用一些数据的创建视图。然后,该测试检查以确保创建组织并具有与发送的数据相同的名称。单位测试不按预期失败。让我们编写一些代码以使其通过。

# organizations/views.py

def create_view(request):
    if request.method == 'POST':
        name = request.POST.get('name', '')
        Organization.objects.create(name=name)
        return redirect(reverse('home'))

    return render(request, 'organizations/create.html')

单元测试通过。让我们尝试功能测试。我们接近,我能感觉到。功能测试无法找到我们的组织,因此我们只需要调整家庭模板。

<!-- organizations/templates/organizations/home.html -->

<table>
    <tbody>
        {% for organization in organizations %}
            <tr>
                <td>{{ organization.name }}</td>
            </tr>
        {% empty %}
            <tr>
                <td>No organizations</td>
            </tr>
        {% endfor %}
    </tbody>
</table>

我们的功能测试通过!一切都起作用!让我们提出我们的改变。

git add .
git commit -m "Added organization model. All tests passing."

重构我们的代码

让我们看一下我们的网站。哇,这很实用,但真的很丑。同样,即使其功能性,它也无法最有效地发挥作用。我们应该使用表单处理数据传输和模板渲染。让我们让这个网站看起来很漂亮,让我们添加表单。

添加表格

我们想在应用程序中使用Django表单。在前端,我们希望表单渲染我们期望的控件(输入文本字段)。我们需要将表单实例传递给模板。我们还想替换正在提取帖子数据并用更平滑的模型手动保存模型的笨拙代码。让我们先处理视图。

# organizations/tests/test_views.py

from ..forms import OrganizationForm

class CreateViewTest(models.Model):
    ...

    def test_view_returns_organization_form(self):
        response = self.client.get('/create/')
        self.assertIsInstance(response.context['form'], OrganizationForm)

正如预期的那样,单元测试失败,导入错误。让我们创建表单。将新的python文件 forms.py 添加到组织文件夹并添加以下代码。

# organizations/forms.py

from django import forms


class OrganizationForm(forms.Form): pass

单元测试失败,但我们没有得到导入错误。添加代码以进行测试。

# organizations/views.py

from .forms import OrganizationForm


def create_view(request):
    ...

    return render(request, 'organizations/create.html', {
        'form': OrganizationForm()
    })

单元测试通过。让我们测试表格,以确保它呈现我们想要的控件。在组织/tests 目录中创建一个新的python文件,并将其称为 test_forms.pys.py 。添加一个检查检查是否存在文本输入控件。

# organizations/tests/test_forms.py

from django.test import TestCase
from ..forms import OrganizationForm


class OrganizationFormTest(TestCase):
    def test_form_has_required_fields(self):
        form = OrganizationForm()
        self.assertIn('id="id_name"', form.as_p())

运行单元测试,并看到一个失败。我们需要制作表单使用Organization型号。像以下内容一样调整OrganizationForm

# organizations/forms.py
from .models import Organization


class OrganizationForm(forms.ModelForm):
    class Meta:
        model = Organization

单元测试通过,但是,您可能会看到有关ModelForms的警告。修复表格以摆脱该警告。

# organizations/forms.py

class OrganizationForm(forms.ModelForm):
    class Meta:
        model = Organization
        fields = ('name',)

返回我们的视图单元测试。我们需要替换当前逻辑,以便表单处理所有数据传输。我们需要添加一些测试,但首先让我们安装一个新的Python库。

pip install mock

让我们为创建视图添加几个新的单元测试。

# organizations/tests/test_views.py

from mock import patch
from django.test import RequestFactory
from ..views import create_view


class CreateViewTest(TestCase):
    def setUp(self):
        self.factory = RequestFactory()

    ...

    @patch('organizations.views.OrganizationForm')
    def test_passes_post_data_to_form(self, mock_organization_form):
        request = self.factory.post('/create/', data={'name': 'test'})
        create_view(request)
        mock_organization_form.assert_any_call(data=request.POST)

    @patch('organizations.views.OrganizationForm')
    def test_saves_organization_for_valid_data(self, mock_organization_form):
        mock_form = mock_organization_form.return_value
        mock_form.is_valid.return_value = True
        mock_form.save.return_value = None
        request = self.factory.post('/create/', data={'name': 'test'})
        create_view(request)
        self.assertTrue(mock_form.save.called)

让我们分解我们的变化。请注意,我们正在为这些测试使用不同的策略。我们不使用Django客户端,而是使用RequestFactory来制造Django HttpRequest并将其传递到视图本身。在这些新测试中,我们的目标不是测试表单的功能。我们只想测试该视图与应有方式相互作用。我们的重点是视图。

我们的第一个测试是确认请求数据正在传递给表单。以前,我们自己处理了数据,因此现在我们要确保实际上有机会处理该表格。我们通过暂时覆盖真实的形式来做到这一点。补丁函数可以执行此操作,并将假形式对象传递给单元测试进行使用。我们唯一需要知道的是,我们认为的形式是用邮政参数调用的。

我们的第二次测试需要更多的工作。同样,我们正在用假对象嘲笑表格,但是这次,我们必须添加一些假函数以反映真实形式的结构。当视图中使用表单时,它必须验证输入然后保存,以便成功创建一个新的模型对象。在此单元测试中,我们嘲笑is_valid()save()方法,以便它们按照我们的期望操作。然后,我们正在检查以确保在表单上调用保存功能。

单元测试按预期失败。让我们添加代码使它们通过。

# organizations/views.py

def create_view(request):
    form = OrganizationForm()

    if request.method == 'POST':
        form = OrganizationForm(data=request.POST)
        if form.is_valid():
            form.save()
        return redirect(reverse('home'))

    return render(request, 'organizations/create.html', {
        'form': form
    })

我们调整了create_view(),以便在每个请求上创建一个空表单,并将邮政数据传递给POST请求上的表单。我们还添加了功能,以检查表格是否有效,然后保存。单位测试通过。我们的最后一步是调整创建模板,以便使用Django表单呈现控件。我们用表单上下文替换硬编码的HTML。

<!-- organizations/templates/organizations/create.html -->

<form action="{% url 'create' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button id="submit" type="submit">Submit</button>
</form>

当我们运行功能测试时,我们会发现它们失败了。我们需要在名称字段中添加一个占位符属性。让我们调整我们的形式。

# organizations/forms.py

class OrganizationForm(forms.ModelForm):
    class Meta:
        model = Organization
        fields = ('name',)
        widgets = {
            'name': forms.fields.TextInput(attrs={
                'placeholder': 'Organization name'
            })
        }

功能测试和单元测试都通过。我们已经成功实现了我们项目的表格!让我们提交我们的代码。

git add .
git commit -m "Replaced template and view code with forms."

使应用程序看起来很漂亮

我们的代码现在更有效。使用表单使我们能够避免每次添加更多字段到表单时必须编辑模板和视图。该代码很可靠,但是该应用程序仍然看起来很丑。让我们使用Bootstrap组件和样式将其涂抹起来。当我们使用时,让我们制作一个基本模板,并创建模板可以继承,因此我们避免重复代码。在模板/组织文件夹中创建一个新的HTML文件,并将其命名 base.html

<!-- organizations/templates/organizations/base.html -->

<!DOCTYPE html>

<html>

<head lang="en">
    <meta charset="utf-8">
    <title>Test-Driven</title>
    <meta name="viewport"
          content="user-scalable=no, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.2/yeti/bootstrap.min.css">
</head>

<body>
    <div class="container">
        {% block page-content %}{% endblock page-content %}
    </div>

    <script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
</body>

</html>

我们已经添加了一个<meta>元素来控制移动设备尝试在视口中渲染完整页面时发生的缩放。我们已经导入Bootstrap CSS和JS文件以及jQuery依赖性,并且我们还选择使用免费的Bootstrap主题来使我们的页面看起来不那么通用。在HTML的主体内,我们添加了一个块模板标签,该标签可以被扩展该底座的模板覆盖。让我们调整其他模板。

<!-- organizations/templates/organizations/home.html -->

{% extends 'organizations/base.html' %}

{% block page-content %}
    <div class="row">
        <div class="col-md-offset-3 col-md-6">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h4 class="panel-title">Organizations</h4>
                </div>
                <div class="panel-body">
                    <a id="create-button" class="btn btn-default" href="{% url 'create' %}">
                        Create organization
                    </a>
                </div>
                <table class="table table-striped">
                    <thead>
                        <tr>
                            <td><strong>Name</strong></td>
                        </tr>
                    </thead>
                    <tbody>
                        {% for organization in organizations %}
                            <tr>
                                <td>{{ organization.name }}</td>
                            </tr>
                        {% empty %}
                            <tr>
                                <td>No organizations</td>
                            </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
{% endblock page-content %}

我们在主页上铺上了整个主页,以便所有内容都适合屏幕中央的漂亮面板。该桌子具有条纹风格更有趣的外观。

<!-- organizations/templates/organizations/create.html -->

{% extends 'organizations/base.html' %}

{% block page-content %}
    <div class="row">
        <div class="col-md-offset-3 col-md-6">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h4 class="panel-title">Create an organization</h4>
                </div>
                <div class="panel-body">
                    <form action="{% url 'create' %}" method="post">
                        {% csrf_token %}
                        {% for field in form %}
                            <div class="form-group">
                                {{ field.label_tag }}
                                {{ field }}
                            </div>
                        {% endfor %}
                        <button id="submit" class="btn btn-default" type="submit">Submit</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
{% endblock page-content %}

我们给出了与创建模板相似的处理,将表单放入面板中。为了通过Bootstrap样式呈现我们的表格,我们有几个选择。曾经选择是使用像脆皮形式这样的第三方库。我选择通过在表单类中添加混合蛋白来手动实现它。

# organizations/forms.py

class BootstrapMixin(object):
    def __init__(self, *args, **kwargs):
        super(BootstrapMixin, self).__init__(*args, **kwargs)

        for key in self.fields:
            self.fields[key].widget.attrs.update({
                'class': 'form-control'
            })


class OrganizationForm(BootstrapMixin, forms.ModelForm): ...

让我们最后一次运行所有功能和单元测试。他们按预期通过。让我们访问我们的页面,看看。它更漂亮。将视觉更改提交给git。

git add .
git commit -m "Made the templates prettier with Bootstrap."

访问您的网站并尝试该功能。如果要部署网站或与他人共享网站,请确保添加远程GIT存储库并推动代码。另外,冻结您的要求并将其包含在文档中,以使您的虚拟环境重复。

pip freeze > requirements.txt
git add .
git commit -m "Added requirements document."
git remote add origin git@your_git_repository
git push -u origin master