经过一个调查问卷的例子来学习Django,它主要包括两部分:html
假设你已经安装了Django,可使用python -m django --version
来检测。前端
经过命令行来建立一个项目:django-admin startproject mysite
python
该命令会在当前目录建立一个mysite目录,切记不要用django这个关键字来给项目命名。mysql
建立获得的目录结构以下:web
mysite/ manage.py mysite/ __init__.py settings.py urls.py wsgi.py
接下来一一解释这些文件的做用:sql
mysite/
根目录是整个项目的容器,它的名字并不重要,你能够自行重命名;manage.py
:一个命令行的组件,提供多种方式来和Django项目进行交互mysite/
目录是项目实际的Python包,它的名字就是未来import
时要用的,好比mysite.urls
mysite/settings.py
:当前项目的配置文件mysite/urls.py
:当前项目的url声明语句mysite/wsgi.py
:WSGI兼容的web服务器的entry-point运行开发服务器python manage.py runserver
, 默认条件下即可以在浏览器中访问http://127.0.0.1:8000/
, 固然也能够更改端口python manage.py runserver 8080
, 这样的话在浏览器中也须要对应更改端口号。shell
若是想更改服务器的ip,也能够python manage.py runserver 0:8000
数据库
服务器的自动从新加载机制django
大多数状况下,当你更改了项目代码后,开发服务器会自动从新加载Python代码,这样就不用重启服务器了。固然,一些添加文件的操做时不会触发从新加载。浏览器
在manage.py
所处的目录中建立应用python manage.py startapp polls
该命令会建立以下目录结构:
polls/ __init__.py admin.py apps.py migrations/ __init__.py models.py tests.py views.py
打开polls/views.py
文件,而后编写以下代码:
from django.http import HttpResponse def index(request): return HttpResponse("hello, world. You're at the polls index.")
这是Django中最简单的view,为了调用这个view,咱们须要将其映射到一个URL上,所以咱们须要一个URLconf
为了建立一个URLConf,建立urls.py文件,此时应用的目录结构以下:
polls/ __init__.py admin.py apps.py migrations/ __init__.py models.py tests.py urls.py views.py
在polls/urls.py
文件中编写以下代码:
from django.urls import path from . import views urlpatterns = { path('', views.index, name='index'), }
下一步,在项目的urls.py
文件中添加一个include():
from django.contrib import admin from django.urls import include, path urlpatterns = { path('polls/', include('polls.urls')), path('admin/', admin.site.urls), }
这里的include()函数容许引用其余的URLConf,不管什么时候Django遇到include()函数,它将从url字符串中将当前urlconf匹配的部分砍掉,而后将剩余url字符串传递到include所指定的urlconf做进一步处理
path()有四个参数:
route
route是一个包含了url模式的字符串,当处理一个request的时候,Django从urlpattern中的第一个模式开始,顺序向下匹配url模式,直到遇到第一个匹配的项。匹配的内容只包含域名后面的部分,不包含get或者post参数,好比
http://www.example.com/myapp/
将只拿/myapp/
与urlpattern
中的模式进行匹配,http://www.example.com/myapp/?page=3
一样也只拿/myapp/
进行匹配view
当遇到一个匹配的url模式,Django就会调用指定的view函数,并将HttpResponse做为第一个参数传递过去,同时url中捕获的其他关键参数也会传递过去
kwargs
固然除了规定好的HttpResponse参数和url中捕获的关键字参数,还有一些关键字参数也能够传递给指定的view,可是本教程不会使用这个功能
name
给URL命名能够避免歧义
2.4.1 数据库启动 打开mysite/settings.py文件,这是个常规的Python模块,其中包含的模块级变量表明着Django的设置
Django默认使用SQLite数据库,这个数据库已经包含在了Python之中,因此不须要安装其余的任何东西
若是想使用其他的数据库,安装合适的数据库插件(database bindings)并更改配置文件中的DATABASE属性, ENGINE:取值的范围'django.db.backends.sqlite3', 'django.db.backends.postgresql', 'django.db.backends.mysql'或者'django.db.backends.oracle'等等 NAME:数据库的名字
编辑mysite/settings.py文件,设置TIME_ZONE为本身的时区
另外,注意INSTALLED_APPS设置包含的应用
默认状况下INSTALLED_APPS包含如下应用: django.contrib.admin 管理员站点 django.contrib.auth 认证系统,用于登陆注册的 django.contrib.contenttypes 一个content types的框架 django.contrib.sessions 一个会话框架 django.contrib.message 一个消息框架 django.contrib.staticfiles 一个处理静态文件的框架 为了知足常见的需求,这些应用默认状况都加入到了Django项目中,每一个应用都会用到若干个数据库表,经过如下命令来建立这些表格: python manage.py migrate 这个migrate命令查看INSTALLED_APPS设置,而后根据mysite/settings.py文件中的数据库设置来建立任何须要的数据库。
2.4.2 建立models 建立models就是在定义数据库的布局,包括额外的元数据
在一个简单的poll应用中,建立两个models:Question和Choice,一个Question包含一个问题和发布日期,一个Choice有两个字段:选项的文本和一个投票计数器,每一个选项和一个问题相关联。
这些概念都经过Python的类来实现。编辑polls/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 Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) 这些代码都很简单,每一个model都是django.db.models.Model的子类,而且都有一些类变量,每一个都表明着数据库字段,每一个字段都是Field类的实例,分别表示字段的不一样类型
每一个Field实例的名字都是字段的名字,不过是给机器读的,在建立实例时能够经过参数指定给人阅读的名字,好比上述中的date published就是以这种方式命名的
一些Field类须要一些参数,好比CharField必需要指定max_length,又好比有些Field能够指定默认值default=0
除此以外还能够经过ForeignKey来指定外键。Django支持经常使用的数据库关系:一对一,一对多,多对一
2.4.3 激活models 这些少许代码能够给Django带来不少信息,有了这些信息,Django能够为应用建立一个数据库模式,也能够建立能够访问Question和Choice类的API
接下来要把polls这个应用的引用添加到INSTALLED_APPS中去,PollsConfig类存在于polls/apps.py文件中,因此它的点路径为'polls.apps.PollsConfig',编辑mysite/settings.py文件,把这个点路径添加到INSTALLED_APPS设置中去 INSTALLED_APPS={ 'polls.apps.PollsConfig', ... } 如今Django已经包含了polls应用,输入以下命令: python manage.py makemigrations polls 该命令告诉Django Model代码已经更改,所作的更改将被存储为migrations
migrations以文件的形式被存储在硬盘上, Django把对models的更改都存储为迁移。能够去polls/migrations/0001_initial.py查看
接下来使用以下命令来运行migrations,并自动部署数据库模式,这就被称为migrate python manage.py migrate
将以上全部的操做总结为三个步骤: 更改models 运行python manage.py makemigrations来为这些改变建立migrations 运行python manage.py migrate来将这些更改应用到数据库
为何要将两个命令分开?主要是为了作好版本的控制,这些之后再详细讲解
2.4.4 使用API 本小节进入Python交互式shell来使用Django提供的API 输入以下命令来启动Python shell: python manage.py shell 进入shell后便可开始使用Django为数据库提供的API: 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 Question.objects.all() 固然,类对象的显式输出不适合人阅读,能够重写Question类进行更改,代码以下: from django.db import models
class Question(models.Model): def str(self): return self.question_text ...
class Choice(models.Model): def str(self): return self.choice_text ... 这样的话,在Python shell中打印出Question的类对象就能够显示出问题文本了。
下面将尝试给Question类添加一个自定义的方法: import datetime
from django.db import models from django.db import timezone
class Question(models.Model): def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1) 保存这些更改,而后从新进入Python shell: from polls.models import Choice, Question Question.objects.all() Question.ojbects.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() c = q.choice_set.filter(choice_text__startswith='Just hacking') c.delete()
2.4.5 介绍Django的Admin 这是一个管理员应用,Django默认支持的 建立一个管理员用户 python manage.py createsuperuser 输入用户名、邮箱、密码等信息 而后启动开发服务器,并进入管理员页面http://127.0.0.1:8000/admin/,而后登录进入管理员站点 能够看到管理员站点能够对用户组和用户进行可视化编辑 那么如何对Question和Choice也进行可视化编辑呢,须要将poll应用添加到管理员站点中去 打开polls/admin.py,输入以下代码: from django.contrib import admin from .models import Question
admin.site.register(Question) 而后开始探索管理员的功能,发现能够经过可视化操做Question,好比添加问题。因为在model中已经定义了各个字段的类型,因此管理员页面中的各个字段的输入组件会自动生成。好比字符串字段对应输入框,时间字段对应日期选择控件。
2.5 编写第一个Django应用(第三部分) 编写更多的view 接下来给polls/views.py文件编写更多的函数 def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id): response = "You're looking at results of question %s." return HttpResponse(response % question_id)
def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id) 而后将这些新的view函数添加到对应的path()上去: from django.urls import path from . import views
urlpatterns = { 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'), } 这里的int:question_id捕获名为question_id的整型参数。 记得别加上.html后缀,这样看起来很笨。
这样就完成了对每一个问题详情、结果、投票的查看。
2.5.3 编写views来真正作些事情 每个view主要作两件事情:返回一个HttpResponse对象,包含了请求页面的内容,或者触发诸如Http404的异常
view能够从数据库中读取记录
它可使用一个Django自带的或者第三方的Python模板系统,它能够生成一个PDF文件,输出XML,建立一个ZIP压缩文件等等,在此过程当中,可使用任何Python模块。
Django须要的只有一个HttpResponse,或者是一个异常exception
因为很方便,因此使用Django本身的数据库。下面的例子展现最近的5个poll问题,用冒号分割 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) 这种在views中定义前端页面的方式过于呆板。Django采用templates来实现后台和前端页面的分离。 首先在polls目录下建立templates目录,Django将在每一个应用的templates目录下寻找前端页面。 项目设置中的TEMPLATES设置描述了Django如何加载并渲染模板。默认的设置文件配置了DjangoTemplates属性,其中APP_DIRS设置为True,DjangoTemplates会在每一个INSTALLED_APPS下的templates中寻找模板文件。
在刚刚建立的templates目录中再建立一个目录polls,并在其中建立一个index.html文件,换句话说,模板文件应该在polls/templates/polls/index.html 因为上面已经介绍了app_direcotries模板加载原理,在Django中引用模板能够直接使用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> {% endfor %} </ul> {% else %} <p>No polls are available. </p> {% endif %} 接下来也把index的view函数更新下: from django.http import HttpResponse from django.template import loader from .models import Question
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)) 代码加载polls/index.html前端页面,并将context传递到前台页面。context是一个字典,它将模板变量映射为Python对象
快捷方式:render() 常见的三部曲:加载模板、填充context和返回HttpResponse对象。Django提供了render()这种快捷方式: 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) 根据上面的代码,render接受request对象为第一个参数,接受一个模板名字做为第二个参数,而后接受一个字典做为第三个参数。
2.5.4 触发异常 前面说到,view要么返回一个HttpResponse对象要么返回异常,这里介绍如何返回异常 以访问问题详情为例: 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}) 很简单,若是取不到对应id的问题,就触发404异常 那么如何在前台页面显示这个question呢,只须要在一个控件中注入question便可: {{ question }}
快捷方式: get_object_or_404() 因为上面的操做很常见,因此Django也提供了一种快捷方式来减小平时的代码量: 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}) 经过代码能够看到,get_object_or_404在没有获取到数据对象时,会获得一个异常对象,这个异常对象显示到前台页面会自动显示404
除此以外:还有get_list_or_404(),区别就是get()和filter()的区别了
2.5.5 使用模板系统 回到poll应用的detail()函数,对其进行显示:
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul> 模板系统使用点号.来访问变量属性 for choice in question.choice_set.all会自动解释为Python代码for choice in queston.choice_set.all()
2.5.6 移除模板中硬编码URL 好比:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> 在上述代码中polls/属于硬编码,将其改成: <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li> 这里的detail是一种url的引用,能够在urls.py中找到该名称对应的url地址: ... path('<int:question_id>/', views.detail, name='detail') ... 而'detail'后面的question.id变量则是会替代该url地址中的<int:question_id>,从而造成一个完整的url
移除硬编码的URL有什么好处? 若是前端页面多处须要使用某个具体地url地址,则均可以经过名字来引用,当该url地址须要更改时,只须要更改urls.py中对应的url地址便可,避免在前端页面每一个地方都去修改
2.5.7 URL名字的命名空间 若是不一样的应用中存在同名的url模式,那么前端页面在引用的时候光写url模式的名字会引发歧义,因此在urls.py中须要指定app的名字 from django.urls import path from . import views
app_name = 'polls' urlpatterns = { ... } 这样,在对前端页面引用处作相应修改
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
2.6 编写第一个Django应用(第四部分) 写一个简单的form表单
<h1>{{ question.questioin_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> 上面的代码中: 为问题的每一个选项都设计了一个单选框,每一个选项的id和问题的选项序号有关,forloop.counter是进入for循环体的次数;每一个选项的name都为choice 表单的跳转连接为投票地址,并以post方式提交,这样很是重要 csrf(Cross Site Request Forgeries),是一个数据保护,加上标签便可 接下来编写vote的响应函数 from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_objects_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) 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, ))) 第一,后台获取前台post提交的数据,使用POST[name]的形式,其中name指的是前端控件的name 第二,save()是对数据作即时保存 第三,一旦成功处理post请求数据,必定要使用HttpResponseRedirect()函数从新导向页面,它能够在用户点击后退按钮时避免数据提交两次。HttpResponseRedirect()只接受一个参数,就是要重定向页面的连接 第四,reverse是Django中的一个函数,它的做用也是避免硬编码
接下来,当用户投票完成后,就会进入投票结果页面,下面编写reuslts的处理函数 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}) 这里的代码和detail()中几乎同样了,惟一不一样的就是模板名字不一样,不过没有关系,稍后会对其进行修复
如今建立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> 如今能够去/polls/1/进行投票了
2.6.2 使用通用的view:代码越少越好 不少的view都是在获取数据并显示在前端页面上,为了下降代码的冗余性,Django提供了通用view机制 三部曲:转换urlconf、删除无用的旧的views、基于Django通用views引入新的views
修改urlconf 打开polls/urls.py,修改以下: from django.urls import path from . import views
app_name = 'polls' uslpatterns = { 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'), }
修改views 删除以前的index, detail和results三个view,取而代之使用Django通用views,打开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 ResultView(generic.DetailView): model = Question template_name = 'polls/results.html'
def vote(request, question_id): ... 上述代码使用了两个通用view:ListView和DetailView,两者分别为展现一个对象列表,一个展现特定类型对象的详情页面 每一个通用view必须指定model属性,而DetailView要求能从url中获取到pk属性,这就是为何上一步在urlconf中将question_id改成pk DetailView默认使用模板<app_name>/<model_name>_detail.html这个模板,可是这里另作了指定,避免Detail和Results进入了同一页面 同理,ListView默认使用<app_name>/<model_name>_list.html这个模板,这里也另行制定了
另外,还需强调的是,在以前的教程中,经过context向前台传输数据,包括latest_question_list和question两个变量,可是若是使用了DetailView,就不用指定context了,它会自动提供给前端页面,由于已经制定了model为Question;可是对于ListView,它默认传递到前端页面的变量是question_list,为了覆盖,这里特地制定了context_object_name
接下来运行服务器,看看如何基于通用view来使用最新的网站
2.7 编写第一个Django应用(第五部分) 网站已经基本写好了,如今写一写自动测试
2.8