Django的使用

本文仅作笔记使用,取自:https://docs.djangoproject.com/zh-hans/2.1/intro/

 

# 安装 pip install django==version
# 查看版本号 python -m django --version

1. 创建网站:admin-startproject mysite
2. 创建应用:admin-startapp polls

3. 环境设置:settings.py
1) INSTALLED_APPS中 polls.apps.PollsConfig
2) 本置数据库(待补)
3) 设置字符及时区:zh-hans, "Asia/shanghai"

4. python manage.py makemigrations
5. python manage.py migrate

6. models.py

from django.db import models

class Question(models.Model):
	question_text = models.CharField(max_length=200)
	pub_date = models.DateTimeField('date published')
	
	class Meta:
		db_table = 'Question'
	
class Choice(models.Model):
	question = models.ForeignKey(Question, on_delete=models.CASCADE)
	choice_text = models.CharField(max_length=200)
	votes = models.IntegerField(default=0)

7. python manage.py makemigrations polls
8. python manage.py sqlmigrate polls 0001 #返回对应的sql语句
9. python manage.py migrate

#脚本交互命令行
10. python manage.py shell
from polls.models import Choice, Question
Question.objects.all()
from django.utils import timezone
q = Question(question_text="What's new?", pub_date=timezone.now())
q.save()
q.id
q.question_text
q.pub_date
q.question_text="What's up?"
q.save()
Question.objects.all()

11. polls/models.py
from django.db import models
class Question(models.Model):
	def __str__(self):
		return self.question_text
		
	def was_published_recently(self):
		return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
		
class Choice(models.Model):
	def __str__(self):
		return self.choice_text
		

#命令行
12. python manage.py shell
from polls.models import Choice, Question
Question.objects.all()
Question.objects.filter(id=1)
Question.objects.filter(question_text__startswith='What')

from django.utils import timezone
current_year = timezone.now().year
Question.objects.get(pub_date__year=current_year)
Question.objects.get(id=2)
Question.objects.get(pk=1)

q = Question.objects.get(pk=1)
q.was_published_recently()

q = Question.objects.get(pk=1)
q.choice_set.all()
q.choice_set.create(choice_text='Not much', votes=0)
q.choice_set.create(choice_text='The sky', votes=0)
c = q.choice_set.create(choice_text='Just hacking again', votes=0)
c.question
q.choice_set.all()
q.choice_set.count()

Choice.objects.filter(question__pub_date__year=current_year)

c = q.choice_set.filter(choice_text__startswith='Just hacking')
c.delete()


13. 创建管理员账号
python manage.py createsuperuser
#浏览器打开 http://127.0.0.1:8000/admin

14. python run manage.py runserver

15. 向管理页面中加入应用
polls/admin.py
from django.contrib import admin
from .models import Question
admin.site.register(Question)

16. python manage.py runserver 0:8000

17.编写视图文件
polls/views.py
from django.http import HttpResponse

def index(request):
	return HttpResponse("Hello World, You're at the polls index.")

18.polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
	path('', views.index, name='index'),
]

19. mysite/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
	path('polls/', include('polls.urls')),
	path('admin/', admin.site.urls),
]

20. path的四个参数, route/view/kwargs/name

21. polls/views.py
def detail(request, question_id):
	return HttpResponse("Your're looking at question %s." % question_id)
	
def results(request, question_id):
	response = "You're looking at the results of question %s."
	return HttpResponse(response % question_id)
	
def vote(request, question_id):
	return HttpResponse("You're voting on question %s." % question_id)
	
	
22. polls/urls.py
from django.urls import path
from . import views

urlpatterns = [
	#ex: /polls/
	path('', views.index, name='index'),
	#ex: /polls/5/
	path('<int: question_id>/', views.detail, name='detail'),
	#ex: /polls/5/results/
	path('<int: question_id>/results/', views.results, name='results'),
	#ex /polls/5/vote/
	path('<int: question_id>/vote/', views.vote, name='vote'),
	#ex 也可以为每个url加上不必要的.html
	path('latest.html', views.index),
]


23. 每个视图必须要做的只有两件事,一个是返回HttpResponse, 一个是抛出异常,比如:Http404
polls/views.py

from django.http import HttpResponse
from .models import Question

def index(request):
	latest_question_list = Question.objects.order_by('-pub_date')[:5]
	output = ', '.join([q.question_text for q in latest_question_list])
	return HttpResponse(output)
	
24. 应用文件夹polls中新建templates目录,里面再建polls应用目录,里面再建index.html模板

{% if latest_question_list %}
    <ul>
        {%  for question in latest_question_list %}
{#            <li>#}
{#                <a href="/polls/{{ question.id }}/">{{ question.question_text }}</a>#}
{#            </li>#}
            <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
        {% endfor %}
    </ul>
{% else %}
    <p>No polls are available</p>
{% endif %}

25. polls/views.py
from django.http import HttpResponse
from .models import Question
from django.template import loader

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

26. 用快捷函数render()重写index()视图函数
polls/views.py

from django.shortcuts import render
from .models import Question

def index(request):
	latest_question_list = Question.objects.order_by('-pub_date')[:5]
	context = {'latest_question_list': latest_question_list}
	return render(request, 'polls/index.html', context)

27. 抛出Http404错误:polls/views.py
from django.http import Http404
from django.shortcuts import render

from .models import Question

def detail(request, question_id):
	try:
		question = Question.objects.get(pk=question_id)
	except Question.DoesNotExist:
		raise Http404("Question does not exist")
	return render(request, "polls/detail.html", {'question': question})
	

28. 快捷函数get_object_or_404()
polls/views.py
from django.shortcuts import get_object_or_404, render
from .models import Question

def detail(request, question_id):
	question = get_object_or_404(Question, pk=question_id)
	return render(request, 'polls/detail.html', {'question': question})

polls/detail.html
{{ question }}

29. 快捷函数 get_list_or_404()函数的工作原理同get_object_or_404()一样,除了get()函数被换成了filter()函数。

30. polls/detail.html模板
<h1>{{ question.question_text }}</h1>
<ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}</li>
    {% endfor %}
</ul>

31. 去除模板中的硬编码url
硬编码链接<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>,
硬编码和强耦合的链接,对于一个包含很多应用的项目来说,修改起来是十分困难的。
在polls.urls的url()函数中通过name参数为url定义了名字,可以使用{% url %}标签来代替它:
<li> <a href="{% url 'detail' question.id %}">{{ question.question_text }} </a> </li>
这个标签的工作方式是在polls.urls模块的url定义中寻找具有指定名字的条目。
path('<int: question_id>/', views.detail, name='detail')

32. 为url名称添加命名空间
当很多应用,不清楚名称'detail'对应的是哪个应用的url时,
在polls/urls.py中加上app_name设置命名空间:
polls/urls.py

from django.urls import path
from . import views

app_name = 'polls'
url_patterns = [
	path('', views.index, name='index'),
	path('<int: question_id>/', views.detail, name='detail'),
	path('<int: question_id>/results/', views.results, name='results'),
	path('<int: question_id>/vote/', views.vote, name='vote'),
]

再编辑polls/index.html
{% if latest_question_list %}
    <ul>
        {%  for question in latest_question_list %}
{#            <li>#}
{#                <a href="/polls/{{ question.id }}/">{{ question.question_text }}</a>#}
{#            </li>#}
{#            <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>#}
{#            <li> <a href="{% url 'detail' question.id %}">{{ question.question_text }} </a> </li>#}
            <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
        {% endfor %}
    </ul>
{% else %}
    <p>No polls are available</p>
{% endif %}


33. 编写一个简单的表单
polls/templates/polls/detail.html

<h1>{{ question.question_text }}</h1>
{% if error_message %}
    <p><strong>{{ error_message }}</strong></p>
{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label>
        <br>
    {% endfor %}
<input type="submit" value="Vote">
</form>

34. 创建post表单,具有修改数据的作用,需要小心跨站点请求伪造。
Django拥有一个用来防御它的非常容易使用的系统
简而言之,所有针对内部url的Post表单,都应该使用{% csrf_token %}模板标签

35. polls/urls.py
path('<int: question_id>/vote/', views.vote, name='vote')

36. polls/views
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question


def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    print("question:", question)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
        print("choice:", selected_choice)
    except(KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': "You didn't select a choice."}, )
    else:
        print("else:")
        selected_choice.votes += 1
        print("votes:", selected_choice.votes)
        selected_choice.save()
        print("saved")
        print("reverse polls path:", reverse('polls:results', args=(question.id,)))
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

#注 reverse函数中需传入url名,以及所需参数
'''
question: what's up?
choice: not much
else:
votes: 5
saved
reverse polls path: /polls/1/results/
[22/Sep/2019 14:57:35] "POST /polls/1/vote/ HTTP/1.1" 302 0
[22/Sep/2019 14:57:35] "GET /polls/1/results/ HTTP/1.1" 200 44

'''

37. polls/views.py
vote()视图将请求重定向到Question的结果界面

from django.shortcuts import get_object_or_404, render


def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})


38. polls/results.html
<h1>{{ question.question_text }}</h1>
<ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }} -- {{choice.votes}} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

39.  使用通用视图,减少代码量
ListView和DetailView, 这两个视图分别抽象”显示一个对象列表“和”显示一个特定类型对象的详细信息页面"
每个通用视图,需要知道它将作用于哪个模型,这是由model属性提供的
DetailView期望从url中捕获名为'pk'的主键值,所以我们为通用视图把question_id改为了pk

默认情况下,通用视图 DetailView 使用一个叫做 <app name>/<model name>_detail.html 的模板。在我们的例子中,它将使用 "polls/question_detail.html" 模板。template_name 属性是用来告诉 Django 使用一个指定的模板名字,而不是自动生成的默认名字。 我们也为 results 列表视图指定了 template_name —— 这确保 results 视图和 detail 视图在渲染时具有不同的外观,即使它们在后台都是同一个 DetailView 。
类似地,ListView 使用一个叫做 <app name>/<model name>_list.html 的默认模板;我们使用 template_name 来告诉 ListView 使用我们创建的已经存在的 "polls/index.html" 模板。
在之前的教程中,提供模板文件时都带有一个包含 question 和 latest_question_list 变量的 context。对于 DetailView , question 变量会自动提供—— 因为我们使用 Django 的模型 (Question), Django 能够为 context 变量决定一个合适的名字。然而对于 ListView, 自动生成的 context 变量是 question_list。为了覆盖这个行为,我们提供 context_object_name 属性,表示我们想使用 latest_question_list。作为一种替换方案,你可以改变你的模板来匹配新的 context 变量 —— 这是一种更便捷的方法,告诉 Django 使用你想使用的变量名。



改良视图 polls/views.py
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except(KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question, 'error_message': "You didn't select a choice."}, )
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))



改良URLconf
polls/urls.py

from django.urls import path
from . import views


app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]


40. 自动化测试
polls/tests.py

from django.test import TestCase
import datetime
from django.utils import timezone
from .models import Question


class QuestionModelTests(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)

41. 运行测试
终端运行:python manage.py test polls

'''
发生了什么呢?以下是自动化测试的运行过程:

    python manage.py test polls 将会寻找 polls 应用里的测试代码
    它找到了 django.test.TestCase 的一个子类
    它创建一个特殊的数据库供测试使用
    它在类中寻找测试方法——以 test 开头的方法。
    在 test_was_published_recently_with_future_question 方法中,它创建了一个 pub_date 值为 30 天后的 Question 实例。
    接着使用 assertls() 方法,发现 was_published_recently() 返回了 True,而我们期望它返回 False。

测试系统通知我们哪些测试样例失败了,和造成测试失败的代码所在的行号。
'''

42. 修复bug
polls/models.py


def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now
	
	
43. 更全面的测试
polls/tests.py

    def test_was_published_recently_with_old_question(self):
        time = timezone.now() - datetime.timedelta(days=1, seconds=1)
        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=23, minutes=59, seconds=59)
        recent_question = Question(pub_date=time)
        self.assertIs(recent_question.was_published_recently(), True)
		
44. 测试视图
$ python manage.py shell

>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()

>>> # get a response from '/'
>>> response = client.get('/')
Not Found: /
>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    <ul>\n    \n        <li><a href="/polls/1/">What&#39;s up?</a></li>\n    \n    </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

45. 改善视图代码

from django.utils import timezone


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
        # return Question.objects.order_by('-pub_date')[:5]

46. 测试新视图
from django.test import TestCase
import datetime
from django.utils import timezone
from .models import Question

from django.urls import reverse


def create_question(question_text, days):
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)


class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_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_future_question(self):
        create_question(question_text='Future question.', days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertContains(response, "No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_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: Past question.>'])

    def test_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 2.>', '<Question: Past question 1.>']
        )
		
		
47. 就算在发布日期未来的那些投票不会在目录页index里出现,但如果用户知道或者猜到正确的url,还是可以访问到它们。

在DetailView中增加约束
polls/views.py
class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'
    
    def get_queryset(self):
        return Question.objects.filter(pub_date__lte=timezone.now())


48. 增加测试来检验pub_date在过去的Question可以显示出来,而在未来的不可以。

polls/tests.py


class QuestionDetailViewTests(TestCase):
    def test_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_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)
		
		
49. 规划测试的建议:
如果你对测试有个整体规划,那么它们就几乎不会变得混乱。下面有几条好的建议:

    对于每个模型和视图都建立单独的 TestClass
    每个测试方法只测试一个功能
    给每个测试方法起个能描述其功能的名字


50. 深入代码测试


在本教程中,我们仅仅是了解了测试的基础知识。你能做的还有很多,而且世界上有很多有用的工具来帮你完成这些有意义的事。

举个例子,在上述的测试中,我们已经从代码逻辑和视图响应的角度检查了应用的输出,现在你可以从一个更加 "in-browser" 的角度来检查最终渲染出的 HTML 是否符合预期,使用 Selenium 可以很轻松的完成这件事。这个工具不仅可以测试 Django 框架里的代码,还可以检查其他部分,比如说你的 JavaScript。它假装成是一个正在和你站点进行交互的浏览器,就好像有个真人在访问网站一样!Django 它提供了 LiveServerTestCase 来和 Selenium 这样的工具进行交互。

如果你在开发一个很复杂的应用的话,你也许想在每次提交代码时自动运行测试,也就是我们所说的持续集成 continuous integration ,这样就能实现质量控制的自动化,起码是部分自动化。

一个找出代码中未被测试部分的方法是检查代码覆盖率。它有助于找出代码中的薄弱部分和无用部分。如果你无法测试一段代码,通常说明这段代码需要被重构或者删除。想知道代码覆盖率和无用代码的详细信息,查看文档 Integration with coverage.py 获取详细信息。

文档 Django 中的测试 里有关于测试的更多信息。	

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值