🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## Tasking 在我们不了解Django的时候,要对这样一个任务进行Tasking,有点困难。不过,我们还是可以简单地看看是应该如何去做: * 生成APP。对于大部分主流的Web框架来说,它们都可以手动地生成一些脚手架,如Ruby语言中的Ruby On Rails、Node.js中的Express等等。 * 创建对应的Model,即其在数据库中存储的模型与我们在代码中要使用的模型。 * 创建程序对应的View,用于处理数据。 * 创建程序的Template,用于显示数据。 * 编写测试来保证功能。 对于其他应用来说也是差不多的。 ## 创建BlogpostAPP ### 生成APP 现在我们可以开始创建我们的APP,使用下面的代码来创建: $ django-admin startapp blogpost 会在blogpost目录下,生成下面的文件: ~~~ . ├── __init__.py ├── admin.py ├── apps.py ├── migrations │   └── __init__.py ├── models.py ├── tests.py └── views.py ~~~ ### 创建Model 现在,我们需要来创建博客的Model即可。对于一篇基本的博客来说,它会包含下在面的几部分内容: * 标题 * 作者 * 链接(中文更需要一个好的链接) * 内容 * 发布日期 我们就可以按照上面的内容来创建我们的Blogpost model: ~~~ from django.db import models from django.db.models import permalink class Blogpost(models.Model): title = models.CharField(max_length=100, unique=True) author = models.CharField(max_length=100, unique=True) slug = models.SlugField(max_length=100, unique=True) body = models.TextField() posted = models.DateField(db_index=True, auto_now_add=True) def __unicode__(self): return '%s' % self.title @permalink def get_absolute_url(self): return ('view_blog_post', None, { 'slug': self.slug }) ~~~ 上面的`get_absolute_url`方法就是用于返回博客的链接。之所以使用手动而不是自动生成,是因为自动生成不靠谱,而且不利 然后在Admin注册这个Model ~~~ from django.contrib import admin from blogpost.models import Blogpost class BlogpostAdmin(admin.ModelAdmin): exclude = ['posted'] prepopulated_fields = {'slug': ('title',)} admin.site.register(Blogpost, BlogpostAdmin) ~~~ 接着进入后台,我们就可以看到BLOGPOST的一栏里,就可以对其进行相关的操作。 ![](https://box.kancloud.cn/2016-05-18_573be5282d639.png) Django后台界面 点击Blogpost的Add后,我们就会进入如下的添加博客界面: ![Django添加博客](http://growth-in-action.phodal.com/images/admin-blog.png) Django添加博客 实际上,这样做的意义是将删除(Delete)、修改(Update)、添加(Create)这些内容将给用户后台来做,当然它也不需要在View/Template层来做。在我们的Template层中,我们只需要关心如何来显示这些数据。 现在,我们可以执行一次新的代码提交——因为现在的代码可以正常工作。这样出现问题时,我们就可以即时的返回上一版本的代码。 ~~~ git add . git commit -m "create blogpost model" ~~~ 然后再进行下一步地操作。 ### 配置URL 现在,我们就可以在我们的`urls.py`里添加相应的route来访问页面,代码如下所示: ~~~ from django.conf import settings from django.conf.urls import patterns, include, url from django.conf.urls.static import static from django.contrib import admin apiRouter = routers.DefaultRouter() apiRouter.register(r'blogpost', BlogpostSet) urlpatterns = patterns('', (r'^$', 'blogpost.views.index'), url(r'^blog/(?P<slug>[^\.]+).html', 'blogpost.views.view_post', name='view_blog_post'), url(r'^admin/', include(admin.site.urls)) ) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ~~~ 在上面的代码里,我们创建了两个route: * 指向首页,其view是index * 指向博客详情页,其view是view_post 指向博客详情页的URL正规`r'^blog/(?P<slug>[^\.]+).html`,会将形如blog/hello-world.html中的hello-world提取出来作为参数传给view_post方法。 接着,我们就可以创建两个view。 ## 创建View ### 创建博客列表页 对于我们的首页来说,我们可以简单的只显示五篇博客,所以我们所需要做的就是从我们的Blogpost对象中,取出前五个结果即可。代码如下所示: ~~~ from django.shortcuts import render, render_to_response, get_object_or_404 from blogpost.models import Blogpost def index(request): return render_to_response('index.html', { 'posts': Blogpost.objects.all()[:5] }) ~~~ Django的render_to_response方法可以根据一个给定的上下文字典渲染一个给定的目标,并返回渲染后的HttpResponse。即将相应的值,如这里的Blogpost.objects.all()[:5],填入相应的index.html中,再返回最后的结果。 因此,在我们的index.html中,我们就可以拿到前五篇博客。我们只需要遍历出posts,拿出每个post相应的值,就可以完成列表页。 ~~~ {% extends 'base.html' %} {% block title %}Welcome to my blog{% endblock %} {% block content %} <h1>Posts</h1> {% for post in posts %} <h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2> <p>{{post.posted}} - By {{post.author}}</p> <p>{{post.body}}</p> {% endfor %} {% endblock %} ~~~ 在上面的模板里,我们还取出了博客的链接用于跳转到详情页。 ### 创建博客详情页 依据上面拿到的slug,我们就可以创建对应的详情页的view,代码如下所示: ~~~ def view_post(request, slug): return render_to_response('blogpost_detail.html', { 'post': get_object_or_404(Blogpost, slug=slug) }) ~~~ 这里的`get_object_or_404`将会根据slug来获取相应的博客,如果取不出相应的博客就会返回404。因此,我们的详情页和上面的列表页也是类似的。 ~~~ {% extends 'base.html' %} {% block head_title %}{{ post.title }}{% endblock %} {% block title %}{{ post.title }}{% endblock %} {% block content %} <h2>{{ post.title }}</a></h2> <p>{{post.posted}} - By {{post.author}}</p> <p>{{post.body}}</p> {% endblock %} ~~~ 随后,我们就可以再提交一次代码了。 ## 测试 TDD虽然是一个非常好的实践,但是那是对于那些已经习惯写测试的人来说。如果你写测试的经历非常小,那么我们就可以从写测试开始。 在这里我们使用的是Django这个第三方框架来完成我们的工作,所以我们并不对这个框架的功能进行测试。虽然有些时候正是因为这些第三方框架的问题而导致的Bug,但是我们仅仅只是使用一些基础的功能。这些基础的功能也已经在他们的框架中测试过了。 ### 测试首页 先来做一个简单的测试,即测试我们访问首页的时候,调用的函数是上面的index函数 ~~~ from django.core.urlresolvers import resolve from django.http import HttpRequest from django.test import TestCase from blogpost.views import index, view_post class HomePageTest(TestCase): def test_root_url_resolves_to_home_page_view(self): found = resolve('/') self.assertEqual(found.func, index) ~~~ 但是这样的测试看上去没有多大意义,不过它可以保证我们的route可以和我们的URL对应上。在编写完测试后,我们就可以命令提示行中运行: ~~~ python manage.py test ~~~ 来查看测试的结果: ~~~ Creating test database for alias 'default'... . ---------------------------------------------------------------------- Ran 1 test in 0.031s OK Destroying test database for alias 'default'... (growth-django) ~~~ 运行通过,现在我们可以进行下一个测试了——我们可以测试页面的标题是不是我们想要的结果: ~~~ def test_home_page_returns_correct_html(self): request = HttpRequest() response = index(request) self.assertIn(b'<title>Welcome to my blog</title>', response.content) ~~~ 这里我们需要去请求相应的页面来获取页面的标题,并用assertIn方法来断言返回的首页的html中含有`<title>Welcome to my blog</title>`。 ### 测试详情页 同样的我们也可以用测试是否调用某个函数的方法,来看博客的详情页的route是否正确? ~~~ class BlogpostTest(TestCase): def test_blogpost_url_resolves_to_blog_post_view(self): found = resolve('/blog/this_is_a_test.html') self.assertEqual(found.func, view_post) ~~~ 与上面测试首页不一样的是,在我们的Blogpost测试中,我们需要创建数据,以确保这个流程是没有问题的。因此我们需要用`Blogpost.objects.create`方法来创建一个数据,然后访问相应的页面来看是否正确。 ~~~ def test_blogpost_create_with_view(self): Blogpost.objects.create(title='hello', author='admin', slug='this_is_a_test', body='This is a blog', posted=datetime.now) response = self.client.get('/blog/this_is_a_test.html') self.assertIn(b'This is a blog', response.content) ~~~ 或许你会疑惑这个数据会不会被注入到数据库中,请看运行测试时返回的结果的第一句: ~~~ Creating test database for alias 'default'... ~~~ Django将会创建一个数据库用于测试。 同理,我们也可以为首页添加一个相似的测试: ~~~ def test_blogpost_create_with_show_in_homepage(self): Blogpost.objects.create(title='hello', author='admin', slug='this_is_a_test', body='This is a blog', posted=datetime.now) response = self.client.get('/') self.assertIn(b'This is a blog', response.content) ~~~ 我们用同样的方法创建了一篇博客,然后在首页测试返回的内容中是否含有`This is a blog`。