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