1.1 polls案例
forks from:
http://python.usyiyi.cn/django/intro/tutorial02.html
thanks!
1.1.1 django安装配置与models设计
确认安装django:
$ python -c "import django;print(django.get_version())"
创建一个项目:
$ django-admin.py startproject mysite
//要将代码放置在Web服务器根目录以外的地方
生成的目录结构如下:
外层的mysite/根目录仅仅是的项目的一个容器,无关紧要且可以随意命名,内层的mysite/目录是你的项目的真正的Python包,这是初学者容易迷惑的地方。
mysite/__init__.py:一个空文件,它告诉Python这个目录应该被看做一个Python包。
mysite/settings.py:该Django 项目的设置/配置。
mysite/urls.py:该Django项目的URL声明;是Django站点的“目录”。
两个非常重要的文件。
mysite/settings.py默认的应用及其用途:
django.contrib.admin—— 管理站点。
django.contrib.auth—— 认证系统。
django.contrib.contenttypes—— 用于内容类型的框架。
django.contrib.sessions—— 会话框架。
django.contrib.messages—— 消息框架。
django.contrib.staticfiles—— 管理静态文件的框架。
数据库部分,这里是mysql:
先要装MySQL-python module:
yum install MySQL-python
配置数据库,在mysite/settings.py增加如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'myapp',
'USER': 'root',
'PASSWORD': 'password',
'HOST': 'x.x.x.x',
'PORT': '3306',
'OPTIONS': {
'init_command': 'SETstorage_engine=INNODB',
}
}
}
执行命令创建数据库表(先要手工创建数据库
create database mysite charset utf8;
):
python manage.py syncdb
开发web试运行:
$ python manage.py runserver 0.0.0.0:8000
创建polls应用:
django-admin.py startapp polls
或者:
python manage.py startapp polls
项目 vs. 应用
项目和应用之间有什么不同? 应用是一个Web应用程序,它完成具体的事项 —— 比如一个博客系统、一个存储公共档案的数据库或者一个简单的投票应用。 项目是一个特定网站中相关配置和应用的集合。一个项目可以包含多个应用。一个应用可以运用到多个项目中。
所以应用对于项目来说是即插即用的,而一个项目对应的是一个服务端口。
创建模型
Models是将数据部分独立管理起来,一个类对应一张表,类中的每个变量对应表的一个字段。
polls/models.py
import datetime
from django.db importmodels
from django.utils importtimezone
classQuestion(models.Model):
question_text =models.CharField(max_length=200)
pub_date = models.DateTimeField('datepublished')
def __unicode__(self): # __str__ on Python 3
return self.question_text
def was_published_recently(self):
return self.pub_date >=timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text =models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __unicode__(self): # __str__ on Python 2
return self.choice_text
为了让
>>>Question.objects.all()
得到可视结果,在各个class增加如下方法
def __unicode__(self): # __str__ on Python 3
return self.question_text
执行:
python manage.py syncdb
即可看到数据表创建:
使用API
进入python shell,
$ pythonmanage.py shell
>>> import django
>>> frompolls.models import Question, Choice
//导入模型中的类,类对应DB中的表
>>>Question.objects.all()
//获取表的全部记录,每条记录对应一个对象
>>> fromdjango.utils import timezone
>>> q =Question(question_text="What's new?", pub_date=timezone.now())
//增加记录,q是一条记录、一个对象
>>> q.save()
>>> q.id
>>>q.question_text
>>> q.pub_date
>>>q.question_text = "What's up?"
//修改数据
>>> q.save()
>>>Question.objects.filter(id=1)
>>> Question.objects.filter(question_text__startswith='What')
>>> fromdjango.utils import timezone
>>> current_year =timezone.now().year
>>>Question.objects.get(pub_date__year=current_year)
//时间、文本过滤记录
>>> q =Question.objects.get(pk=1)
>>>q.was_published_recently()
//自定义函数
>> q =Question.objects.get(pk=1)
>>>q.choice_set.all()
//由A表一条记录查B表记录,通过question_id关联的
>>>q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>>q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
//增加B表记录,FK是当前A表的PK
>>> 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')
//q.choice_set指与q记录关联的choice表的记录
>>> c.delete()
//删除记录
以上示例包含了数据的CRUD操作,还有数据过滤、关联表操作,这些是写数据库操作的基本功。
1.1.2 编写应用管理界面
$ python manage.pycreatesuperuser
Username: admin
Email address:admin@example.com
创建一个管理账号。
修改settings.py。在INSTALLED_APPS设置中添加“django.contrib.admin”。
运行python manage.py syncdb更新数据库
修改urls.py。改为:
# Uncomment thenext two lines to enable the admin:
from django.contrib importadmin
admin.autodiscover()
# Uncomment this for admin:
(r'^admin/', include(admin.site.urls)),
注意比较坑的是老版本是如下配置,新版本是如上配置
(r'^admin/', include('django.contrib.admin.urls')),
//如果出现Site matching query doesnot exist.错误,执行以下创建site即可
python manage.py shell
>>>fromdjango.contrib.sites.models import Site
>>>Site.objects.create(pk=1,domain='www.test.com', name='www.test.com')
就可以访问到管理页面了:
http://x.x.x.x:8000/admin/
加polls到管理页面
polls/admin.py
from django.contrib importadmin
from .models importQuestion
admin.site.register(Question)
重启服务,即可在管理页面看到questions
Polls/admin.py
from django.contrib importadmin
from .models import Choice,Question
classChoiceInline(admin.TabularInline):
model = Choice
extra = 3
# 以表格方式(TabularInline)加入关联表数据和3个空白增加项(extra = 3)
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields':['pub_date'], 'classes': ['collapse']}),
]
# 以层叠的方式展示时间项(collapse)
inlines = [ChoiceInline]
list_display = ('question_text','pub_date', 'was_published_recently')
list_filter = ['pub_date']
search_fields = ['question_text']
#显示项、过滤项、搜索项,在展示记录上都非常有用
admin.site.register(Question,QuestionAdmin)
polls/models.py
import datetime
from django.db importmodels
from django.utils importtimezone
classQuestion(models.Model):
question_text =models.CharField(max_length=200)
pub_date = models.DateTimeField('datepublished')
def __unicode__(self): # __str__ on Python 3
return self.question_text
def was_published_recently(self):
return self.pub_date >=timezone.now() - datetime.timedelta(days=1)
was_published_recently.admin_order_field ='pub_date'
was_published_recently.boolean = True
was_published_recently.short_description ='Published recently?'
# 自定义字段的排序、显示
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text =models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __unicode__(self): # __str__ on Python 2
return self.choice_text
个性化管理页面,略,用时参考文档。
管理页面提供了记录展示、修改、删除、排序、过滤、搜索、定制字段展示功能,完全涵盖常用数据操作。
1.1.3 Polls应用对外界面—视图
视图对应一个特定函数、一个特定模板、一个网页功能块。
url()函数具有四个参数:两个必需的regex和 view,以及两个可选的kwargs和name。
url() 参数:regex,正则表达式不会检索URL中GET和POST的参数以及域名,正则表达式可以传递参数给view,用好这个功能。
url() 参数:view,将HttpRequest对象作为第一个参数
url() 参数:kwargs,任何关键字参数都可以以字典形式传递给目标视图
url() 参数:name,命名你的URL可以让你在Django的其它地方,尤其是模板中,通过名称来明确地引用它。使用名字的好处是,名字不变,url改变只需修改一个文件,全局生效,强大。
@在站点主urls.py文件插入:
from django.conf.urlsimport include, url
from django.contrib importadmin
urlpatterns = [
url(r'^polls/',include('polls.urls')),
url(r'^admin/', include(admin.site.urls)),
]
@polls/views.py
def detail(request,question_id):
return HttpResponse("You're looking atquestion %s." % question_id)
def results(request,question_id):
response = "You're looking at theresults of question %s."
return HttpResponse(response % question_id)
def vote(request,question_id):
return HttpResponse("You're voting onquestion %s." % question_id)
@polls/urls.py
from django.conf.urlsimport url
from . import views
urlpatterns = [
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<question_id>[0-9]+)/$',views.detail, name='detail'),
# ex: /polls/5/results/,捕获符合正则表达式的输入,命名为question_id传递给view,?P表示用<>内命名匹配正则的字段
url(r'^(?P<question_id>[0-9]+)/results/$', views.results,name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
@启动web来验证:
pythonmanage.py runserver 0.0.0.0:8000
url可以通过include逐层包含
url使用正则表达式可以传递参数
每当Django遇到 include()时,它会将URL中之前匹配到的字符串去掉,然后将剩下的字符串交由include()指定的URLconf 做进一步处理。include()背后的想法是使URL变得即插即用。
匹配r'^(?P<question_id>[0-9]+)/$'并导致像下面这样调用detail()视图:
detail(request=<HttpRequestobject>, question_id='34')
快捷方式:render()
polls/views.py
from django.shortcutsimport get_object_or_404, render
return render(request,'polls/index.html', context)
引发一个404错误,获取不到记录报404
polls/views.py
def detail(request,question_id):
question = get_object_or_404(Question,pk=question_id)
return render(request, 'polls/detail.html',{'question': question})
还有get_list_or_404(),列表为空则404
@松耦合原则
在template文件中不要有url的硬编码,使用url方法
<li><ahref="/polls/` question`.`id `/">` question`.`question_text`</a></li>
改为
<li><ahref="{% url 'detail' question.id %}">` question`.`question_text`</a></li>
其中polls.urls中定义
url(r'^(?P<question_id>[0-9]+)/$',views.detail, name='detail'),
后续如想改url,只需在polls.urls中修改就可以了,如
url(r'^specifics/(?P<question_id>[0-9]+)/$',views.detail, name='detail'),
@命名空间
urls.py
url(r'^polls/',include('polls.urls', namespace="polls")),
polls/templates/polls/index.html
<li><ahref="{% url 'polls:detail' question.id %}">`question`.`question_text `</a></li>
Templates要建与应用同名子文件夹以便区分。目录结构:
polls/templates/polls
@@最后的相关文件:
myweb/urls.py
from django.conf.urlsimport patterns, include, url
from django.contrib importadmin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^polls/', include('polls.urls',namespace="polls")),
)
Polls/models.py
import datetime
from django.db importmodels
from django.utils importtimezone
classQuestion(models.Model):
question_text =models.CharField(max_length=200)
pub_date = models.DateTimeField('datepublished')
def __unicode__(self): # __str__ on Python 3
return self.question_text
def was_published_recently(self):
return self.pub_date >=timezone.now() - datetime.timedelta(days=1)
was_published_recently.admin_order_field ='pub_date'
was_published_recently.boolean = True
was_published_recently.short_description ='Published recently?'
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __unicode__(self): # __str__ on Python 2
return self.choice_text
Polls/views.py
from django.http importHttpResponse
from django.shortcutsimport get_object_or_404, render
from .models importQuestion
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):
response = "You're looking at theresults of question %s."
return HttpResponse(response % question_id)
def vote(request,question_id):
return HttpResponse("You're voting onquestion %s." % question_id)
polls/templates/polls/index.html
{% load url from future %}
{% if latest_question_list%}
<ul>
{%for question in latest_question_list %}
<li><a href="{% url'polls:detail' question.id %}">` question`.`question_text`</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
polls/templates/polls/detail.html
{% load url from future %}
<h1>`question`.`question_text `</h1>
<ul>
{% for choice inquestion.choice_set.all %}
<li>` choice`.`choice_text`</li>
{% endfor %}
</ul>
使用namespace的时候,template文件需要加上:
{% load url fromfuture %}
这个表示向前兼容。否则报“未注册的namespace错误”
1.1.4 表单处理和代码优化
polls/templates/polls/detail.html
<h1>{{question.question_text}}</h1>
{%iferror_message%}<p><strong>{{error_message}}</strong></p>{%endif%}
<formaction="{%url'polls:vote'question.id%}"method="post">
{%csrf_token%}
{%forchoiceinquestion.choice_set.all%}
<inputtype="radio"name="choice"id="choice{{forloop.counter}}"value="{{choice.id}}"/>
<labelfor="choice{{forloop.counter}}">{{choice.choice_text}}</label><br/>
{%endfor%}
<inputtype="submit"value="Vote"/>
</form>
Get读数据,post写数据,这是网站基础功能
forloop.counter:循环到第几次
此表单将发送一个POST数据choice=#
{% csrf_token%}对所有POST表单都应该使用,防止跨站伪造。
CSRF
CSRF:Cross-site request forgery跨站请求伪造
> 每次初始化一个项目时都能看到django.middleware.csrf.CsrfViewMiddleware 这个中间件
> 每次在模板里写 form 时都知道要加一个 {% csrf_token %} tag
> 每次发 ajax POST 请求,都需要加一个 X_CSRFTOKEN 的 header
避免被 CSRF 攻击
在返回的 HTTP 响应的 cookie 里,django会为你添加一个 csrftoken 字段,其值为一个自动生成的token
在所有的 POST 表单时,必须包含一个 csrfmiddlewaretoken 字段(只需要在模板里加一个 tag, django 就会自动帮你生成,见下面)
在处理 POST 请求之前,django 会验证这个请求的 cookie 里的 csrftoken 字段的值和提交的表单里的 csrfmiddlewaretoken 字段的值是否一样。(可以用JS修改这两个值为任意值,只要一致即可。)如果一样,则表明这是一个合法的请求,否则,这个请求可能是来自于别人的 csrf 攻击,返回 403 Forbidden.
在所有 ajax POST 请求里,添加一个 X-CSRFTOKEN header,其值为 cookie 里的 csrftoken 的值
Django 里如何使用 CSRF 防护
首先,最基本的原则是:GET 请求不要用有副作用。也就是说任何处理 GET 请求的代码对资源的访问都一定要是“只读“的。
要启用 django.middleware.csrf.CsrfViewMiddleware 这个中间件
再次,在所有的 POST 表单元素时,需要加上一个 {% csrf_token %} tag
在渲染模块时,使用 RequestContext。RequestContext 会处理
polls/views.py
fromdjango.shortcutsimport get_object_or_404, render
fromdjango.httpimport HttpResponseRedirect, HttpResponse
fromdjango.core.urlresolversimport reverse
from.modelsimport Choice, Question
#...
defvote(request, question_id):
p = get_object_or_404(Question, pk=question_id)
try:
selected_choice = p.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': p,
'error_message': "Youdidn't select a choice.",
})
else:
selected_choice.votes +=1
selected_choice.save()
# Always return an HttpResponseRedirect aftersuccessfully dealing
# with POST data. This prevents data frombeing posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
request.POST 是一个类字典对象,通过关键字的名字获取提交的数据,request.POST的值永远是字符串
HttpResponseRedirect值接收一个参数:用户将要被重定向的URL
reverse() 调用将namespace:view反向解析为url相对路径,如'/polls/3/results/'
使用通用视图
为什么要通用视图?这是因为web页面操作数据库的行为都是“差不多”的,如列出清单、查看明细,无非是表不同,通用视图避免写重复的view。
4种通用视图:
> 页面列表/详细页面
> 基于数据的记录分类(对于新闻或 blog 站点非常有用)
> 对象的创建、更新和删除(CRUD)
> 简单直接的模板表示或简单地对 HTTP 重新进行定向
通用视图的改变:
DetailView期望从URL中捕获名为"pk"的主键值,所以我们为通用视图把question_id改成pk 。
类视图:最初 django 的视图都是用函数实现的,但函数视图难以扩展和子定义,所以django 1.3之后出现了类视图
类视图的 as_view() 类方法:接受 request,并实例化类视图,接着调用实例的dispatch() 方法。(如果叫做create_view()可能更加容易理解);类视图继承通用视图,如generic.ListView。
如下过程:访问url,根据urls.py规则命中,调用类视图如DetailView,送入request、以及根据类视图定义load对应model、template、
Ref:
http://www.pythontip.com/blog/post/12172/
django.views.generic.base.TemplateView
django.views.generic.base.RedirectView
django.views.generic.list.ListView
django.views.generic.detail.DetailView
django.views.generic.edit.CreateView
django.views.generic.edit.UpdateView
django.views.generic.edit.DeleteView
django.views.generic.dates.ArchiveIndexView
django.views.generic.dates.YearArchiveView
django.views.generic.dates.MonthArchiveView
django.views.generic.dates.WeekArchiveView
django.views.generic.dates.DayArchiveView
django.views.generic.dates.TodayArchiveView
django.views.generic.dates.DateDetailView
polls/urls.py
fromdjango.conf.urlsimport url
from.import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
polls/views.py
fromdjango.shortcutsimport get_object_or_404, render
fromdjango.httpimport HttpResponseRedirect
fromdjango.core.urlresolversimport reverse
fromdjango.viewsimport generic
from.modelsimport Choice, Question
classIndexView(generic.ListView):
template_name ='polls/index.html'
context_object_name ='latest_question_list'
defget_queryset(self):
"""Return the last fivepublished questions."""
return Question.objects.order_by('-pub_date')[:5]
classDetailView(generic.DetailView):
model = Question
template_name ='polls/detail.html'
classResultsView(generic.DetailView):
model = Question
template_name ='polls/results.html'
defvote(request, question_id):
...# same as above