16 KiB
First Django App
Target: create a basic poll application, with 2 parts a public site and a admin site
- public site: let's pp view polls and vote
- admin site: let's you(admin) add/change/delete poll
Part 1
- Create web project
- Create 1st app
- Create 1st view
- Add view & app into web url
Creating a project
In the source directory, create project by
$ django-admin startproject <project_name>
Constraint on <project_name>
:
- don't use names that conflict with django
- e.g.
django
,test
(test with built-in test file)
- e.g.
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
Explain file structure:
- top-level
mysite/
: container of the project, can be renamed latter w/o affect Django framework manage.py
: cmd-line utility let us to interact (admin) with Django projects.- inner
mysite/
dir: actual Python package for the project.- Importing any package from here: e.g.
mysite.urls
- Importing any package from here: e.g.
mysite/__init__.py
: empty file, tell python start from here.mysite/settings.py
: settings/config for this django project.mysite/urls.py
: URL declarations for this Django pjt; a "table of contents" of this Django website.mysite/asgi.py
: An entry-point for ASGI-compatible webserversmysite/wsgi.py
: Entry-point for WSGI-compatible webservers
The development server
Start server steps:
- cd into outer
mysite/
dir - Start server via:
python manage.py runserver
Outcome:
- A development server is started. So no need to config a production server (e.g. Apache)
- Note: don't use it as production environment.
Creating the Polls app
Projects vs. apps
What’s the difference between a project and an app? An app is a Web application that does something – e.g., a Weblog system, a database of public records or a small poll app. A project is a collection of configuration and apps for a particular website. A project can contain multiple apps. An app can be in multiple projects.
Create a poll app along mysite/
. For what?
$ python manage.py startapp polls
app hierarchy
polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
Write the first view
Step 1: Create a simple view of polls
app
modify polls/views.py
as
from django.http import request
from django.shortcuts import render
def index(request):
return HttpResponse("Hello, world. You're at the polls index")
def index(request):...
is the simplest view
Step 2: In polls
app, map the URL to this view
After creating a view, map it to a URL so we can call it. Create polls/urls.py
, and setup mapping urlpatterns
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
Step 3: Point the root URLconf for polls
To point the root URLconf at polls.urls
module:
- In
mysite/urls.py
- add import for
django.urls.inclue
- insert a
include()
inurlpatterns
list
- add import for
i.e.
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
django.urls.include()
django.urls.include(): A function that takes a full Python import path to another URLconf module that should be “included” in this place.
- new imported
include()
function allows referencing other URLconfs. 'polls.urls'
is the package that we are using, hencemysite
can connect topolls
app
path()
Here path()
function is passed route and view; two additional option available kwargs, and name
path()
argument route: a string that contains a URL pattern. When Django processing a request, it starts at the first pattern inurlpatterns
and go down the list, comparing requested URL against each patternpath()
argument view: when Django finds a matching pattern, it calls specified view function (aHttpRequest
obj as the 1st argument, and others captured values as keyword argument)path()
argument kwargs: passed in a dictionary to the target viewpath()
argument name: naming URL, so we can refer to it elsewhere.
Part 2
Database setup
- Modify
mysite/settings.py
for database binding python manage.py migrate
create database for each app
Models Creation
在 Django 里写一个数据库驱动的 Web 应用的第一步: 定义模型 - 也就是数据库结构设计和附加的其它元数据 Meta Data
Activate Models
改变模型需要这三步:
- 编辑 models.py 文件,改变模型
- 运行 python manage.py makemigrations 为模型的改变生成迁移文件。
- 运行 python manage.py migrate 来应用数据库迁移。
在这之前,确认polls已经在 INSTALLED_APPS的settings中。
详情如何使用 manage.py
可check Django后台文档
Test API
进入 python manage.py shell
可以使用Django创建的各种API,如数据库抽象API database API(建议细看)
Part 3
- In Django, web content & HTML come from Views models. Every view is a Python function/class. Django use customer requested URL to decide which View to generate.
- Django use 'URLconfs' to map btw URL and Views. Details ofpURL manager
- URL's general form:
/newsarchive/<year>/<month>/
3.1 Create more Views
当某人请求你网站的某一页面时——比如说, "/polls/34/" ,Django 将会载入 mysite.urls 模块,因为这在配置项 ROOT_URLCONF 中设置了。然后 Django 寻找名为 urlpatterns 变量并且按序匹配正则表达式。在找到匹配项 'polls/',它切掉了匹配的文本("polls/"),将剩余文本——"34/",发送至 'polls.urls' URLconf 做进一步处理。在这里剩余文本匹配了 "int:question_id/",使得我们 Django 以如下形式调用 polls.urls.detail():
detail(request=<HttpRequest object>, question_id=34)
3.2 Create a true useful View
每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常,比如 Http404 。至于你还想干些什么,随便你。
你的视图可以从数据库里读取记录,可以使用一个模板引擎(比如 Django 自带的,或者其他第三方的),可以生成一个 PDF 文件,可以输出一个 XML,创建一个 ZIP 文件,你可以做任何你想做的事,使用任何你想用的 Python 库。
django.shortcuts.render
可以“载入模板,填充上下文,再返回由它生成的 HttpResponse 对象“
shortcut function: render()
「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数 render
The render() function takes the request object as its first argument, a template name as its second argument and a dictionary as its optional third argument. It returns an HttpResponse object of the given template rendered with the given context.
3.3 Throw 404 error
如果指定问题 ID 所对应的问题不存在,这个视图就会抛出一个 Http404 异常。
尝试用 get() 函数获取一个对象,如果不存在就抛出 Http404 错误也是一个普遍的流程。Django 也提供了一个快捷函数, 从而取代try/except语句
什么我们使用辅助函数 get_object_or_404() 而不是自己捕获 ObjectDoesNotExist 异常呢?还有,为什么模型 API 不直接抛出 ObjectDoesNotExist 而是抛出 Http404 呢? 因为这样做会增加模型层和视图层的耦合性。指导 Django 设计的最重要的思想之一就是要保证松散耦合。一些受控的耦合将会被包含在 django.shortcuts 模块中。
3.4 Using template system
polls/detail.html
模板里正式的代码:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
模板系统统一使用点符号来访问变量的属性
3.5 Remove hardlink URL in template
In template polls/index.html
, links are hardcoded as shown
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
Consider index.html
will be reused numerous times, 去耦合is necessary.
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
3.6 为 URL 名称添加命名空间
在根 URLconf 中添加命名空间 (namespace)
- Add
app_name = 'polls'
for namespace inpoll.urls.py
- Change
detail
inindex.html
topolls:detail
Part 4. Form
4.1 编写一个简单的表单
Edit polls/templates/polls/detail.html
so it contain HTML<form>
elements:
<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>
Explain:
- 当有人选择一个单选按钮并提交表单提交时,它将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。这是 HTML 表单的基本概念。
- 我们设置表单的 action 为 {% url 'polls:vote' question.id %} ,并设置 method="post" 。使用 method="post"
(与其相对的是
method="get"`)是非常重要的,因为这个提交表单的行为会改变服务器端的数据。 无论何时,当你需要创建一个改变服务器端数据的表单时,请使用 ``method="post" 。这不是 Django 的特定技巧;这是优秀的网站开发技巧。 - 所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签。
修改 view.py
, 将Choice 逻辑灌输入其中。
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
# request.POST 是一个类字典对象 (dictionary-like object),让你可以通过关键字的名字获取提交的数据。
# request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# 如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# 在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
# 我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。在本例中,使用在 教程第 3 部分 中设定的 URLconf, reverse() 调用将返回一个这样的字符串:'/polls/3/results/' 其中 3 是 question.id 的值。重定向的 URL 将调用 'results' 视图来显示最终的页面。
当有人对 Question 进行投票后, vote() 视图将请求重定向到 Question 的结果界面 (result view), edit it:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
同时,我们需要创建一个polls/results.html
template
<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>
4.2 使用通用视图(generic views):代码还是少点好
Web 开发中的一个常见情况:根据 URL 中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。 由于这种情况特别常见,Django 提供一种快捷方式,叫做“通用视图”系统。
让我们将我们的投票应用转换成使用通用视图系统,这样我们可以删除许多我们的代码。我们仅仅需要做以下几步来完成转换,我们将:
- 转换 URLconf。
- 删除一些旧的、不再需要的视图。
- 基于 Django 的通用视图引入新的视图。
Modify URLconf of polls
Change
# 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'),
to
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'),
- 注意,第二个和第三个匹配准则中,路径字符串中匹配模式的名称已经由 <question_id> 改为 (primary key)。
Modify Views
Target: Replace index
, details
& results
views using Django's generic views that named in polls/urls.py
将
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)
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
改为
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
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'
两个通用视图: ListView 和 DetailView 。这两个视图分别抽象“显示一个对象列表”和“显示一个特定类型对象的详细信息页面”这两种概念。
- 每个通用视图需要知道它将作用于哪个模型。 这由 model 属性 (i.e. model members) 提供。
- DetailView 期望从 URL 中捕获名为 "pk" 的主键值,所以我们为通用视图把 question_id 改成 pk 。
解释 DetailView
- 默认情况下,通用视图 DetailView 使用一个叫做 /_detail.html 的模板。
- e.g. "polls/question_detail.html" template for us.
- 但是我们可以用 model member
template_name
来指定模板
解释 ListView
- ListView 也使用一个叫做 /_list.html 的默认模板
- 而这里我们使用
template_name
来指定模板 - 我们提供 context_object_name 属性,表示我们想使用 latest_question_list
重启服务器后,就是新的投票应用(同样的html),但是内部用了generic view