Django入门-从0开始编写一个投票网站(三)

接上一篇Django入门-从0开始编写一个投票网站(二)
开始笔记的part5-6。

part 5

  • 第一个自动化测试
    我们之前加的Question.was_published_recently()有一个bug:如果日期是在未来,返回的也是True。要验证这个,可以通过shell来创建一个quesiton,日期是未来的某一天,然后验证:
    >>> import datetime
    >>> from django.utils import timezone
    >>> from polls.models import Question
    >>> future_question = Question(pub_date=timezone.now() + date time.timedelta(days=10))
    >>> future_question.was_published_recently()
    
    噼里啪啦一顿下来返回True,显然有问题。

  • 创建一个test来暴露bug,test的方法写在tests.py里。
    import datetime
    from django.utils import timezone
    from django.test import TestCase
    from .models import Question
    class QuestionMethodTests(TestCase):
          def test_was_published_recently_with_future_question(self):
              time = timezone.now()+datetime.timedelta(days=30)
              future_question = Question(pub_date=time)
              self.assertIs(future_Question.was_published_recently(), False)
    
    我们这里创建了一个django.test.TestCase的子类,定义了方法(方法一定要以test开头)。这个方法里创建了一个未来日期的Question,我们通过断言来判断它的was_published_recently()是否是期望的False

  • 跑测试。在终端运行:
    python manage.py test polls
    
    然后看到
    test_polls_failed.png
    这里发生了这些:
    python manage.py test polls去polls应用找tests
    如果发现了django.test.TestCase的子类
    它会为测试创建一个特殊的数据库
    查找test开头的 测试方法
    在这里就是找到test_was_published_recently_with_future_question,创建一个日期在一个月以后的Question
    调用断言assertIs()方法,发现返回Ture,跟希望的False不一致,然后给出上面的提示。

  • 发现了bug,就要修复。在polls/models.py
    class Question(models.Model):
          ...
          def was_published_recently(self):
              now = timezone.now()
              return now-datetime.timedelta(days=1) <= self.pub_date <= now
    

    重新跑,可以看到
    test_polls_success.png

    更多综合测试

    def test_was_published_recently_with_old_question(self):
        time = timezone.now() - datetime.timedelta(days=30)
        old_question = Question(pub_date=time)
        self.assertIs(old_question.was_published_recently(), False)
    
    def test_was_published_recently_with_recent_question(self):
        time = timezone.now() - datetime.timedelta(hours=1)
        recent_question = Question(pub_date=time)
        self.assertIs(recent_question.was_published_recently(), True)
    

  • 测试views。刚才我们在代码本身找bug,接下来可以在与用户交互的界面上来找找问题。
    django提供了一个测试的客户端模拟器来测试view,我们可以在tests.py和shell使用。
    我们从shell用:
    >>> from django.test.utils import setup_test_environment
    >>> setup_test_environment()
    
    setup_test_environment()方法安装了一个可以模版渲染的东西。这个方法不会建立测试的数据库,所有的都会运行在当前存在的数据库上并且输出的结果可能跟你已经创建的会有一点点的区别,比如如果你在settings.py里设置的TIME_ZONE不对,那么你的结果就会不大一样,为了避免这个,检查一下TIME_ZONE的设置。
    使用client类
    >>> from django.test import Client
    >>> client = Client()
    >>> response = client.get("/")
    # Not Found: /
    >>> response.status_code
    # 404
    >>> from django.urls import reverse
    >>> response = client.get(reverse('polls:index'))
    >>> response.status_code
    # 200
    >>> response.content
    >>> response.context['latest_question_list']
    

  • 提升我们的view,在polls/views.py的IndexView类里,我们修改一下get_queryset(),使它在查询的时候可以把日期和当前日期比对一下:
    from django.utils import timezone
         class IndexView(generic.ListView):
                ...
                def get_queryset(self):
                    return Question.objects.filter(pub_date__lte=timezone.now()).order('-pub_date')[:5]
    
    这里的filter返回了一个包含日期早于或等于Question的查询集,__lte(注意是2条_)是django数据模型日期属性的一个固定写法,用在filter方法里,属性__lte=日期表示日期早于或等于指定的那个日期。
  • 测试我们的新的view,在tests.py里:
    from django.urls import reverse
    def create_question(question_text, days):
        time = timezone.now() + date time.timedelta(days=days)
        return Question.objects.create(question_test=question_text, pub_date=time)
    
    class QuestionViewTests(TestCase):
          def test_index_view_with_no_question(self):
              response = self.client.get(reverse('polls:index'))
              self.assertEqual(response.status_code, 200)
              self.assertContains(response, "No polls are available")
              self.assertQuesrysetEqual(response.context['latest_question_list'], [])
    
          def test_index_view_with_a_past_question(self):
              create_question(question_text="Past question", days=-30)
              response = self.client.get(reverse('polls:index'))
              self.assertQuerysetEqual(response.context['latest_question_list'], ['<Question: Past question.>'])
    
          def test_index_view_with_a_future_question(self):
              create_question(question_text="Future question", days=30)
              response = self.client.get('polls:index')
              self.assertQuerysetEqual(response.context['latest_question_list'], [])
    
          def test_index_view_with_future_question_and_past_question(self):
              create_question(question_text="Past question.", days=-30)
              create_question(question_text="Future question.", days=30)
              response = self.client.get(reverse('polls:index'))
              self.assertQuerysetEqual(response.context['latest_question_list'], ['<Question: question.>'])
    
          def test_index_view_with_two_past_questions(self):
              create_question(question_text="Past question 1.", days=-30)
              create_question(question_text="Past question 2.", days=-5)
              response = self.client.get(reverse('polls:index'))
              self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question 1.>', '<Question: Past question 2.>'])
    
    下面来仔细看:
    create_question()是快捷创建question的方法
    test_index_view_with_no_question不创建任何的question对象,而是验证latest_question_list是否是空的。
    执行每一个测试方法的时候测试数据库都会重置。

  • 测试DetailView
    目前日期是未来的question不会显示到index.html里,但是如果用户猜出来了正确的URL,那还是能获得这些未来日期的question,所以要在DetailView里加一个类似的约束:
    class DetailView(generic.DetailView):
          ...
          def get_queryset(self):
              return Question.objects.filter(pub_date__lte=timezone.now())
    
    在test.py里
    class QuestionIndexDetailTests(TestCase):
          def test_detail_view_with_a_future_question(self):
              future_question = create_question(question_text='Future question.', days=5)
              url = reverse('polls:detail', args=(future_question.id,))
              response=self.client.get(url)
              self.assertEqual(response.status_code, 404)
    
          def test_detail_view_with_a_past_question(self):
              past_question=create_question(question_text='Past question.', days=-5)
              url=reverse('polls:detail', args=(past_question.id,))
              response=self.client.get(url)
              self.assertContains(response, past_question.question_text)
    
    以上是测试的部分。

part 6

  • 定制app的外观。
    web app除了HTML以外,还需要诸如图片,JS文件或者CSS,这些一般都是静态文件,我们可以用django.contrib.staticfiles来应对这些:对于小的app可以放在指定的地方而不需要用这个模块,对于大的应用用这个统一管理比较好。
    首先在polls文件夹下创建一个static文件夹,类似templates。
    django的STATICFILES_FINDERS设置包含了一系列的finder,这些finder定义了查找静态文件的方式。其中有一个叫做AppDirectoriesFinder的会在每一个INSTALLED_APPS里查找一个叫做static的文件夹。
    在这个static文件夹里创建一个polls文件夹,在这个polls文件夹里再创建一个style.css文件,所以这个css文件的地址是polls/static/polls/style.css
    在这个style.css文件里:
    li a {
        color: green;
    }
    
    接下来在polls/templates/polls/index.html里的最顶部添加:
    {% load static %}
    <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}"></link>
    
    {% static %}生成了静态文件的绝对路径。重新加载http://127.0.0.1:8000/polls/你可以看到问题的链接变成原谅色了。

  • 添加一张背景图。
    polls/static/polls/下创建一个用来放置图片的images文件夹。在文件夹里放一张background.gif图片,所以这张图片的路径是polls/static/polls/images/background.gif
    在style.css里
    body {
        background: white url("images/background.gif") no-repeat right bottom;
    }
    
    重新加载就能看到背景图的效果
    part5-6就到这里。
    戳这里查看下面的教程:Django入门-从0开始编写一个投票网站(四)(完结)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容