Django基础

  1. 云栖社区>
  2. 博客>
  3. 正文

Django基础

技术小阿哥 2017-11-27 16:41:00 浏览1379
展开阅读全文

书籍:http://docs.30c.org/djangobook2

Django方法的返回值一般是字典或列表

一、新建一个Django工程

Django版本1.11   Python版本2.7.8.10

1.创建Django工程,并使用PyCharm开发

 新建工程文件myproject下新建firstproject工程

>>django-admin.exe  startproject firstproject

此时产生的目录为

firstproject

│  manage.py

└─firstproject

        settings.py

        urls.py

        wsgi.py

        __init__.py


2.配置PyCharm使得点击RUN时,能启动Django服务器(命令行启动方式为: python manage.py runserver)

Run->Edit Configurations->点击'+' 选择‘python’新建一个启动配置,配置如下

Script:              F:\er\firstproject\manage.py

Script parameters:   runserver


3.现在点击运行,并访问http://127.0.0.1:8000/,如果出现

“It worked!” 说明服务器建立OK啦


4.为该web服务器工程创建第一个web APP:firstapp

>> cd firstproject\firstproject

>> python ../manage.py startapp  firstapp

现在的工程结构为:

F:\ER\FIRSTPROJECT

│  db.sqlite3

│  manage.py

└─firstproject

    │  settings.py

    │  settings.pyc

    │  urls.py

    │  urls.pyc

    │  wsgi.py

    │  wsgi.pyc

    │  __init__.py

    │  __init__.pyc

    │

    └─firstapp

        │  admin.py

        │  apps.py

        │  models.py

        │  tests.py

        │  views.py

        │  __init__.py

        │

        └─migrations

                __init__.py

firstproject是配置控制,而firstapp是应用代码

5.配置工程的setting.py文件,添加firstapp到firstproject中,使得服务器能够管理该工程

在setting.py文件的INSTALLED_APPS列表对象中添加“firstproject.firstapp”

INSTALLED_APPS = [

    'django.contrib.auth',

    'django.contrib.admin',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'firstproject.firstapp',

]

6.为firstapp添加helloworld页面

在firstapp\views.py中添加如下代码:


from django.shortcuts import render

from django.http import HttpResponse

def hello(request):

return HttpResponse("This is firstapp:Hello world! ")

7.在工程中添加访问hello函数接口的url(路由)

首先在urls.py文件中导入from blogapp.views import hello ,然后在urlpatterns 列表中添加url(r'^hello$', hello),表示访问hello页面时,调用blogapp.views。

from firstapp.views import hello

urlpatterns = [

    url(r'^admin/', admin.site.urls),

    url(r'^hello$', hello),

]


8.OK啦不需要重启Django,直接访问http://127.0.0.1:8000/hello,出现

This is firstapp:Hello world!


9.下面使用数据库,配置数据库

在settings.py中的DATABASES字典中配置了当前默认的sqlite数据库

在INSTALLED_APPS列表中添加admin和auth这2个app,使得用户可以通过web来直接修改数据库

INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'firstproject.firstapp',

]

10.在firstapp\models.py中添加类,并注册到数据库,这样该类就在数据库中对应一张表

from django.db import models

from django.contrib import admin


class FirstTable(models.Model):

    title=models.CharField(max_length=150)

    body=models.TextField()

    timestamp=models.DateTimeField()


admin.site.register(FirstTable)   //向admin APP注册该表,使得用户可以通过amdin APP来管理数据库


如果想在admin中让数据库便于阅读和修改,则还需要添加如下1个model

class FirstTableAdmin(admin.ModelAdmin):

list_display=('title','timestamp')

admin.site.register(FisrtTable,FirstTableAdmin) 替代admin.site.register(FirstTable)


11.在数据库中生成所有models对应的表

>>python manage.py makemigrations

>>python manage.py migrate

12.注册admin的登陆用户名

>>python manage.py createsuperuser   //创建登陆管理数据库的root用户名密码


13.一切都Ok之后,现在可以访问http://127.0.0.1:8000/admin/  向BlogPost表中插入数据了

注意每次修改了表的属性字段之后都需要重新做第11步,否则数据库中不会生成对应的表

整个过程都需不要重启Django服务器


二、Django的URL配置

1.从Django的角度来说,一个页面具有3个典型的组件:

a.一个模版:负责呈现传递进来的数据

b.一个视图函数:负责接收请求,向模版传递数据,并返回响应

c.一个URL模式:负责路由请求到视图函数

例子:

一个典型的模版:firstapp/templates/archive.html

(%for post in posts%)

<h2>((post.title))</h2>

<p>((post.timestamp))</p>

<p>((post.body))</p>

<%endfor%>

可以看出该模版需要接收一个posts列表,于是我们就可以通过视图向该模版传递posts列表

from django.template import loader,Context

from django.http import HttpResponse

from firstproject.firstapp.views import FirstTable

def archive(request):

posts=FirstTable.objects.all()

t=loader.get_templates("archive.html")

c=Context({'posts':posts})

return HttpResponse(t.render(c))

此外每一个APP都应该有自己独立的URL文件,可以这样配置:

在工程文件的urls.py中配置:

url(r'^firstapp/',include('firstproject.firstapp.urls'))

然后在firstapp文件夹下新建一个firstapp/urls.py文件:


from django.config.urls.default import *

from firstproject.firstapp.views import archive

urlpatterns=patterns('',url(r'^$',archive),)


2.Django的工作流程(Django如何处理接收到的请求)

所有Django的运行要从setting.py开始。当运行python manage.py sunserver时,脚本将从manage.py同一个目录(工程目录)开始查找

setting.py文件,该文件包含了所有Django的配置信息,均为大写:TEMPLATE_DIRS,DATABASE_NAME,ROOT_URLCONF等,其中ROOT_URLCONF将

告诉服务器使用哪些urls.py文件,及告知服务器视图的路由配置。当浏览器请求路径及参数到达时,服务器将按照ROOT_URLCONF指定的

urls.py中搜索匹配请求路径的路由模式,并调用对应的视图函数,返回返回响应。


3.通过路由模式的配置向视图函数传递参数

在普通的web框架中,只能通过get(或post)方法,将参数表示为http://127.0.0.1/hello?para1=1;para2=2这样的形式

但是Django还有更便捷的传递参数的方法:URL分组匹配参数传递

例如:

url模式为: urlpatterns=patterns('',url(r'^$',index),url(r'^hello/(\d+{1,})/',hello))

地址:http://127.0.0.1/hello/15/163/

视图函数为:

def  hello(request,para1,para2):

     ...

分析:那么当匹配url(r'^hello/(\d+{1,})/(\d+{1,})/',hello)之后,其中的正则分组‘()’将传递给视图函数。即将调用函数

hello(request,'15','163')


三.Django的模板

1.Django有一套模板系统,单独定义了一些标签和变量的书写规则,该模板系统是给前端开发人员使用的。

2.python只是将Django模板当做普通的字符串

from django import template

t = template.Template('My name is ` name `.')  //使用普通字符串创建模板对象

c = template.Context({'name': 'Adrian'})  //Context对象传递字典数据,Context本身也可以像字典一样使用

print t.render(c)

--->My name is Adrian.

如果模板并没有渲染该` name `对象,那么该位置输出将是null,而不会有任何报错

c = template.Context({'Name': 'Adrian';'NAME':'lj'})

print t.render(c)

--->My name is .

3.简单的python命令与python manage.py shell的区别

python manage.py shell将加载本工程的配置和环境变量,具有python的一切功能


4.在深度变量的查找中,Django 模板遍历复杂数据结构的关键是句点字符‘.’

一切成员访问皆句点字符‘.’,所有类型数据的访问都是统一的:普通变量,对象方法,对象成员,字典,列表等

比如在模板中

a.访问字典成员:

 from django.template import Template, Context

 from django.template import loader

 person = {'name': 'Sally', 'age': '43'}

 t = Template('` person`.`name ` is ` person`.`age ` years old.')  // person['name']这样访问反而是不对的

 //t=Template(loader.get_template('c:\\lk\project\app\templates\lj.html'))

 c = Context({'person': person})

 t.render(c)

 --->u'Sally is 43 years old.'


b.也可以访问对象成员方法:

 from django.template import Template, Context

 t = Template('` var ` -- ` var`.`upper ` -- ` var`.`isdigit `')

 t.render(Context({'var': 'hello'}))

 --->u'hello -- HELLO -- False'

可见当访问成员方法时不能使用'()',从而无法访问有参的成员方法


c.列表的访问

 from django.template import Template, Context

 t = Template('Item 2 is ` items`.`2 `.')

 c = Context({'items': ['apples', 'bananas', 'carrots']})

 t.render(c)

 --->u'Item 2 is carrots.'


‘.’的关于优先级

句点查找规则可概括为: 当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找:

a.字典类型查找 (比如 foo["bar"] )

b.属性查找 (比如 foo.bar )

c.方法调用 (比如 foo.bar() )

d.列表类型索引查找 (比如 foo[bar] )

系统使用找到的第一个有效类型。 这是一种短路逻辑。

句点查找可以多级深度嵌套。 例如在下面这个例子中 `person`.`name`.`upper` 会转换成字典类型查找( person['name'] ) 然后是方法调用( upper() )


5.Context上下文对象

多数时间,你可以通过传递一个完全填充的字典给 Context() 来初始化 上下文(Context)。

但是初始化以后,你也可以使用标准的Python字典语法向‘上下文(Context)’ 对象添加或者删除条目

>>> from django.template import Context

>>> c = Context({"foo": "bar"})

>>> c['foo']

'bar'

>>> del c['foo']

>>> c['foo']

Traceback (most recent call last):

  ...

KeyError: 'foo'

>>> c['newvariable'] = 'hello'

>>> c['newvariable']

'hello'


6.基本的标签和过滤器

最基本的是:变量占位使用'{{}}',标签占位使用'{%%}'

基本的标签有:

a.{%if var%}...{%else%}...{%endif%}

b.{%for var in vars%}...{%endfor%}

for标签中有一些自动的变量:

forloop.counter 总是一个表示当前循环的执行次数的整数计数器。 这个计数器是从1开始的,所以在第一次循环时 forloop.counter 将会被设置为1。

forloop.counter0 类似于 forloop.counter ,但是它是从0计数的。 第一次执行循环时这个变量会被设置为0。

forloop.revcounter 是表示循环中剩余项的整型变量。 在循环初次执行时 forloop.revcounter 将被设置为序列中项的总数。 最后一次循环执行中,这个变量将被置1。

forloop.revcounter0 类似于 forloop.revcounter ,但它以0做为结束索引。 在第一次执行循环时,该变量会被置为序列的项的个数减1。

forloop.first 是一个布尔值,如果该迭代是第一次执行,那么它被置为```` 在下面的情形中这个变量是很有用的:

forloop.last 是一个布尔值;在最后一次执行循环时被置为True。 一个常见的用法是在一系列的链接之间放置管道符(|)

forloop.parentloop(.last) 是一个指向当前循环的上一级循环的 forloop 对象的引用(在嵌套循环的情况下)。 

c.{%ifequal var1 var2%}/{%ifnotequal var1 var2%}

例子:

{% ifequal user currentuser %}

<h1>Welcome!</h1>

{% endifequal %}

d.注释

就像HTML或者Python,Django模板语言同样提供代码注释。 注释使用 {# #} :

{# This is a comment #}

e.变量过滤器

模板过滤器是在变量被显示前修改它的值的一个简单方法。 过滤器使用管道字符,如下所示:

{{ name|lower }}

过滤管道可以被* 套接* ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入,如此下去。 下面的例子实现查找列表的第一个元素并将其转化为大写。

{{ my_list|first|upper }}

有些过滤器有参数。 过滤器的参数跟随冒号之后并且总是以双引号包含。 例如:

{{ bio|truncatewords:"30" }}

f.include 模板标签

 {% include %} 该标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,

 可以是一个变量,也可以是用单/双引号硬编码的字符串。 每当在多个模板中出现相同的代码时,

 就应该考虑是否要使用 {% include %} 来减少重复。

 {% include 'nav.html' %}

下面的例子包含了以变量 template_name 的值为名称的模板内容:

{% include template_name %}

7.render_to_response()和locals()技巧

from django.shortcuts import render_to_response

import datetime

def current_datetime(request):

    now = datetime.datetime.now()

    return render_to_response('current_datetime.html',locals())

8.模板继承

目的:提取相似网页的不同部分,拒绝重复代码

{% include %}只能去包含公共的代码部分,而如果包含者是公共部分呢?这时需要使用继承。

继承是{% include %}的逆向提取,它提取的是不同部分

例如:base.html为公共部分 {% block var %}{% endblock %} 为需要重新实现的位置

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">

<html lang="en">

<head>

    <title>{% block title %}{% endblock %}</title>

</head>

<body>

    <h1>My helpful timestamp site</h1>

    {% block content %}{% endblock %}

    {% block footer %}

    <hr>

    <p>Thanks for visiting my site.</p>

    {% endblock %}

</body>

</html>


继承实现:time.html

{% extends "base.html" %}

{% block title %}The current time{% endblock %}

{% block content %}

<p>It is now ` current_date `.</p>

{% endblock %}

//footer没有重新实现,则保存原样


工作方式:在加载time.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 

模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 

{% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 

中定义的标题,对 {% block content %} 也是如此。 所以,网页标题块将由 {% block title %}替换,

同样地,网页的内容一块将由 {% block content %}替换。注意由于子模板并没有定义 footer 块,模板

系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。


四、模型(数据库层)

一个python语法:

names = [row[0] for row in cursor.fetchall()]

每执行一次for再执行前面的row[0]表达式生成一个列表元素,从而最终生成冷(cursor.fetchall())个列表元素


1.数据库配置

在settings.py中配置数据库访问

DATABASE_ENGINE = ''  // http://www.djangoproject.com/r/python-mysql/需要使用Django提供的驱动

DATABASE_NAME = ''

DATABASE_USER = ''

DATABASE_PASSWORD = ''

DATABASE_HOST = ''

DATABASE_PORT = ''


2.数据模型与表

在models.py中用python来描述3张表

from django.db import models


class Publisher(models.Model):

    name = models.CharField(max_length=30)

    address = models.CharField(max_length=50)

    city = models.CharField(max_length=60)

    state_province = models.CharField(max_length=30)

    country = models.CharField(max_length=50)

    website = models.URLField()

 

class Author(models.Model):

    first_name = models.CharField(max_length=30)

    last_name = models.CharField(max_length=40)

    email = models.EmailField()


class Book(models.Model):

    title = models.CharField(max_length=100)

    authors = models.ManyToManyField(Author)

    publisher = models.ForeignKey(Publisher)

    publication_date = models.DateField()


a.首先要注意的事是每个数据模型都是 django.db.models.Model 的子类。它的父类 Model 包含了所有必要的和

数据库交互的方法,并提供了一个简洁漂亮的定义数据库字段的语法。 

b.每个模型相当于单个数据库表,每个属性也是这个表中的一个字段。 属性名就是字段名,

它的类型(例如 CharField )相当于数据库的字段类型 (例如 varchar )。

c.“每个数据库表对应一个类”这条规则的例外情况是多对多关系。 在我们的范例模型中, 

Book 有一个 多对多字段 叫做 authors 。 该字段表明一本书籍有一个或多个作者,

但 Book 数据库表却并没有 authors 字段。 相反,Django创建了一个额外的表(多对多连接表)

来处理书籍和作者之间的映射关系。

d.最后需要注意的是,我们并没有显式地为这些模型定义任何主键。 除非你单独指明,否则Django会自动为每

个模型生成一个自增长的整数主键字段每个Django模型都要求有单独的主键id

e. 当调用

>>python manage.py makemigrations

>>python manage.py migrate

将在数据库中生成数据模型对应的表。


3.基本的数据存储与访问

>>> from books.models import Publisher

>>> p1 = Publisher(name='Apress', address='2855 Telegraph Avenue',    

...     city='Berkeley', state_province='CA', country='U.S.A.',

...     website='http://www.apress.com/')

>>> p1.save()   //将在表中保存一条数据,INSERT INTO插入数据

>>> p.name = 'Apress Publishing'

>>> p.save() //UPDATE更新数据

>>> publisher_list = Publisher.objects.all()  //返回所有Publisher对象列表

>>> publisher_list

[<Publisher: Publisher object>, <Publisher: Publisher object>]


4.为模型重载__code__()方法

 __unicode__() 。 __unicode__() 方法告诉Python如何将对象以unicode的方式显示出来。

class Publisher(models.Model):

    name = models.CharField(max_length=30)

    address = models.CharField(max_length=50)

    city = models.CharField(max_length=60)

    state_province = models.CharField(max_length=30)

    country = models.CharField(max_length=50)

    website = models.URLField()


    def __unicode__(self):

        return self.name

5.数据查询

Publisher.objects.all()    //查询所有对象

Publisher.objects.filter(country="U.S.A.", state_province="CA") //where子句查询

Publisher.objects.filter(name__contains="press")  //LIKE子句查询

Publisher.objects.get(name="Apress")  //获取单个对象

Publisher.objects.order_by("name")     //查询并排序

Publisher.objects.order_by("‐name")   //降序

Publisher.objects.filter(country="U.S.A.").order_by("‐name")    //连锁查询

尽管很灵活,但是每次都要用 order_by() 显得有点啰嗦。 大多数时间你通常只会对某些 字段进行排序。 在这

种情况下,Django让你可以指定模型的缺省排序方式:

class Meta:

        ordering = ['name']

Publisher.objects.order_by('name')[0]   //limit限制返回的数据

p.delete() //删除对象

Publisher.objects.filter(country='USA').delete()



五、Django站点管理

1.django.contrib包

django.contrib.admin 管理界面工具(该设计是针对非技术人员的)

django.contrib.auth  用户鉴别系统

django.contrib.sessioins 支持匿名会话

django.contrib.comments  用户评注系统

django的附加组件都存在于该包中。


2.激活或使用管理界面的方法

a.将'django.contrib.admin'加入setting的INSTALLED_APPS配置中

b.'django.contrib.admin'又依赖于3个app,所以还必须添加:

'django.contrib.auth','django.contrib.contenttypes'和'django.contrib.sessions'

c.添加中间件

确保MIDDLEWARE_CLASSES 包含

'django.middleware.common.CommonMiddleware'

、'django.contrib.sessions.middleware.SessionMiddleware'

和'django.contrib.auth.middleware.AuthenticationMiddleware'

d.生成管理界面使用的额外数据库表

>>python manage.py makemigrations

>>python manage.py migrate

e.配置admin的URL路由

from django.contrib import admin

admin.autodiscover()

# And include this URLpattern...

urlpatterns = patterns('',

    # ...

    (r'^admin/', include(admin.site.urls)),

    # ...

)

 admin.autodiscover()这个函数遍历INSTALLED_APPS配置,并且寻找相关的 admin.py文件。 如果在指定的app目录下找到admin.py,它就执行其

中的代码。

f.将你的Models加入到Admin管理中

这样我们就能够通过这个漂亮的界面添加、修改和删除数据库中的对象了。 

在'firstapp' 目录下,创建一个文件:'admin.py' ,然后输入以下代码:

from django.contrib import admin

from mysite.books.models import Publisher, Author, Book

admin.site.register(FirstTable)

//admin.site.register(Publisher)

//admin.site.register(Author)

//admin.site.register(Book)

g.ok啦,启动开发服务器(如前:`` python manage.py runserver`` ),然后在浏览器中访问:http://127.0.0.1:8000/admin/


六、表单FORM对象

django.http.Request 接受请求对象    //查看官方手册获取包含的方法

django.http.HttpResponse 返回响应对象

request.GET  request.POST   可以用来获取表单元素的值

1.从html表单到DJANGO表单对象

    <form action="/search/" method="get">

        <input type="text" name="q">

        <input type="submit" value="Search">

    </form>

class ContactForm(forms.Form):

    subject = forms.CharField()

    email = forms.EmailField(required=False)

    message = forms.CharField()

from django.shortcuts import render_to_response

from mysite.contact.forms import ContactForm


def contact(request):

    if request.method == 'POST':

        form = ContactForm(request.POST)  //使用POST或GET字典就可以直接构建表单

        if form.is_valid():

            cd = form.cleaned_data

            send_mail(

                cd['subject'],

                cd['message'],

                cd.get('email', 'noreply@example.com'),

                ['siteowner@example.com'],

            )

            return HttpResponseRedirect('/contact/thanks/')

    else:

        form = ContactForm()

    return render_to_response('contact_form.html', {'form': form})


<html>

<head>

    <title>Contact us</title>

</head>

<body>

    <h1>Contact us</h1>


    {% if form.errors %}

        <p style="color: red;">

            Please correct the error{{ form.errors|pluralize }} below.

        </p>

    {% endif %}


    <form action="" method="post">

        <table>

            ` form`.`as_table `             //表单对象的一般用法

        </table>

        <input type="submit" value="Submit">

    </form>

</body>

</html>



2.表单

表单框架最主要的用法是,为每一个将要处理的HTML的<Form>定义一个Form类。在这个例子中,我们只有一个`` <Form>`` ,因此我们只需定义一个Form类。 这个类可以存在于任何地方,甚至直接写在`` views.py`` 文件里也行,但是社区的惯例是把Form类都放到一个文件中:forms.py。在存放`` views.py`` 的目录中,创建这个文件,然后输入:

from django import forms

class ContactForm(forms.Form):

    subject = forms.CharField()

    email = forms.EmailField(required=False)//验证可以为空

    message = forms.CharField()


>>> from contact.forms import ContactForm

>>> f = ContactForm()

>>> print f.as_table  //默认HTML的<table>格式输出

<tr><th><label for="id_subject">Subject:</label></th><td><input type="text" name="subject" id="id_subject" /></td></tr>

<tr><th><label for="id_email">Email:</label></th><td><input type="text" name="email" id="id_email" /></td></tr>

<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" /></td></tr>

//创建一个表单

>>> f = ContactForm({'subject': 'Hello', 'email': 'adrian@example.com', 'message': 'Nice site!'})

一旦你对一个Form实体赋值,你就得到了一个绑定form:

>>> f.is_bound    //表示已经赋值

True

>>> f.is_valid()  //判定数据是否合法

True


七、会话、用户和注册

1.关于cookie

Cookie的工作原理:由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。

怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都从设置时起就必须携带自己通行证。

这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,

就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。

当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。

服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

cookie的特点:

a.Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,

一个request或者response同时使用多个Cookie。

b.Cookie的不可跨域名性

根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。

Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。浏览器判断一个网站是否

能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie。

c.Unicode编码:保存中文

中文与英文字符不同,中文属于Unicode字符,在内存中占4个字符,而英文属于ASCII字符,

内存中只占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。

d.BASE64编码:保存二进制图片

Cookie不仅可以使用ASCII字符与Unicode字符,还可以使用二进制数据。例如在Cookie中使用数字证书,

提供安全度。使用二进制数据时也需要进行编码。

e.设置Cookie的所有属性

除了name与value之外,Cookie还具有其他几个常用的属性。每个属性对应一个getter方法

与一个setter方法。Cookie类的所有属性如表1.1所示。

String name 该Cookie的名称。Cookie一旦创建,名称便不可更改

Object value 该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码

int maxAge 该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1

boolean secure 该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false

String path 该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”

String domain 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”

String comment 该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明

int version 该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范

 

f. Cookie的有效期

Cookie的maxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)

方法来读写maxAge属性。如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数

的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时

该Cookie仍然有效;如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,

关闭窗口后该Cookie即失效。


注意:从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。

浏览器提交Cookie时只会提交name与value属性。maxAge属性只被浏览器用来判断Cookie是否过期。


g.Cookie的修改、删除

Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。

如果要删除某个Cookie,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。

注意是0而不是负数。负数代表其他的意义。读者可以通过上例的程序进行验证,设置不同的属性。

注意:修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,

都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。


h.Cookie的路径

domain属性决定运行访问Cookie的域名,而path属性决定允许访问Cookie的路径(ContextPath)。例如,如果只允许/sessionWeb/下的程序使用Cookie,可以这么写:

Cookie cookie = new Cookie("time","20080808");        // 新建Cookie

cookie.setPath("/session/");                          // 设置路径

response.addCookie(cookie);                           // 输出到客户端

设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。name相同但domain相同的两个Cookie也是两个不同的Cookie。

注意:页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。使用时一定要注意。


i.Cookie的安全属性

HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,

有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,

可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。

下面的代码设置secure属性为true:

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie

cookie.setSecure(true);                           // 设置安全属性

response.addCookie(cookie);                        // 输出到客户端

提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

j.JavaScript操作Cookie

Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使用脚本程序如JavaScript或者VBScript等操作Cookie。


k.案例:永久登录

如果用户是在自己家的电脑上上网,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。

实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。

保存登录信息有多种方案。最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,

与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。

还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。

如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。

这几种方案验证账号时都要查询数据库。


本例将采用另一种方案,只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。

实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。

本例把账号保存到名为account的Cookie中,把账号连同密钥用MD1算法加密后保存到名为ssid的Cookie中。

验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。相关代码如下:

代码1.8 loginCookie.jsp


<%@ page language="java"pageEncoding="UTF-8" isErrorPage="false" %>

<%!                                                  // JSP方法

    private static final String KEY =":cookie@helloweenvsfei.com"; // 密钥   

    public final static String calcMD1(Stringss) { // MD1 加密算法

       String s = ss==null ?"" : ss;                  // 若为null返回空

       char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', 

       'a', 'b', 'c', 'd', 'e', 'f' };                        // 字典

       try {

        byte[] strTemp =s.getBytes();                          // 获取字节

        MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 获取MD1

        mdTemp.update(strTemp);                                // 更新数据

        byte[] md =mdTemp.digest();                        // 加密

        int j =md.length;                                 // 加密后的长度

        char str[] = newchar[j * 2];                       // 新字符串数组

        int k =0;                                         // 计数器k

        for (int i = 0; i< j; i++) {                       // 循环输出

byte byte0 =md[i];

str[k++] =hexDigits[byte0 >>> 4 & 0xf];

str[k++] =hexDigits[byte0 & 0xf];

        }

        return newString(str);                             // 加密后字符串

       } catch (Exception e){return null; }

    }

%>

<%

   request.setCharacterEncoding("UTF-8");              // 设置request编码

   response.setCharacterEncoding("UTF-8");         // 设置response编码

   String action =request.getParameter("action");  // 获取action参数

   if("login".equals(action)){                        // 如果为login动作

        String account =request.getParameter("account"); // 获取account参数

        String password =request.getParameter("password"); // 获取password参数

        int timeout = newInteger(request.getParameter("timeout")); // 获取timeout参数

        String ssid =calcMD1(account + KEY); // 把账号、密钥使用MD1加密后保存

        CookieaccountCookie = new Cookie("account", account); // 新建Cookie

        accountCookie.setMaxAge(timeout);              // 设置有效期

        Cookie ssidCookie =new Cookie("ssid", ssid);   // 新建Cookie

        ssidCookie.setMaxAge(timeout);                 // 设置有效期

response.addCookie(accountCookie);             // 输出到客户端

        response.addCookie(ssidCookie);            // 输出到客户端

        // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容

        response.sendRedirect(request.getRequestURI() + "?" + System. 

        currentTimeMillis());

        return;

    }

    elseif("logout".equals(action)){                      // 如果为logout动作

        CookieaccountCookie = new Cookie("account", "");  // 新建Cookie,内容为空

        accountCookie.setMaxAge(0);                       // 设置有效期为0,删除

        Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,内容为空

        ssidCookie.setMaxAge(0);                   // 设置有效期为0,删除

        response.addCookie(accountCookie);         // 输出到客户端

        response.addCookie(ssidCookie);         // 输出到客户端

        //重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容

        response.sendRedirect(request.getRequestURI() + "?" + System. 

        currentTimeMillis());

        return;

    }

    boolean login = false;                        // 是否登录

    String account = null;                        // 账号

    String ssid = null;                           // SSID标识

    if(request.getCookies() !=null){               // 如果Cookie不为空

        for(Cookie cookie :request.getCookies()){  // 遍历Cookie

           if(cookie.getName().equals("account"))  // 如果Cookie名为 account                                   

               account = cookie.getValue();       // 保存account内容

           if(cookie.getName().equals("ssid")) // 如果为SSID

               ssid = cookie.getValue();          // 保存SSID内容

        }

    }

    if(account != null && ssid !=null){    // 如果account、SSID都不为空

        login =ssid.equals(calcMD1(account + KEY));  // 如果加密规则正确, 则视为已经登录                              

    }

%>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">

       <legend><%= login ? "欢迎您回来" : "请先登录"%></legend>

        <% if(login){%>

            欢迎您, ${ cookie.account.value }. &nbsp;&nbsp;

           <a href="${ pageContext.request.requestURI }?action=logout"> 

            注销</a>

        <% } else {%>

        <formaction="${ pageContext.request.requestURI }?action=login" 

        method="post">

           <table>

               <tr><td>账号: </td>

                   <td><input type="text"name="account" style="width: 

                   200px; "></td>

               </tr>

               <tr><td>密码: </td>

                   <td><inputtype="password" name="password"></td>

               </tr>

               <tr>

                   <td>有效期: </td>

                   <td><inputtype="radio" name="timeout" value="-1" 

                   checked> 关闭浏览器即失效 <br/> <input type="radio"  

                   name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天 

                   内有效 <br/><input type="radio" name="timeout" value=  

                   "<%= Integer.MAX_VALUE %>"> 永久有效 <br/> </td> </tr>

               <tr><td></td>

                   <td><input type="submit"value=" 登  录 " class=  

                   "button"></td>

               </tr>

           </table>

        </form>

        <% } %>


2.关于session

a.什么是session?

Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。

客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。

客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,

那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。

Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

Session对象是在客户端第一次请求服务器的时候创建的。Session也是一种key-value的属性对,

通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。

Servlet里通过request.getSession()方法获取该客户的Session,

b.session的分离

当多个客户端执行程序时,服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。

Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。

各客户的Session也彼此独立,互不可见。

提示:Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。


c.Session的生命周期

Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。

Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。

Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。


d.Session的有效期

由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。

Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。

Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。


e.Session的常用方法

Session中包括各种方法,使用起来要比Cookie方便得多。Session的常用方法如表

void setAttribute(String attribute, Object value) 设置Session属性。value参数可以为任何Java Object。通常为Java Bean。value信息不宜过大

String getAttribute(String attribute) 返回Session属性

Enumeration getAttributeNames() 返回Session中存在的属性名

void removeAttribute(String attribute) 移除Session属性

String getId() 返回Session的ID。该ID由服务器自动创建,不会重复

long getCreationTime() 返回Session的创建日期。返回类型为long,常被转化为Date类型,例如:Date createTime = new Date(session.get CreationTime())

long getLastAccessedTime() 返回Session的最后活跃时间。返回类型为long

int getMaxInactiveInterval() 返回Session的超时时间。单位为秒。超过该时间没有访问,服务器认为该Session失效

void setMaxInactiveInterval(int second) 设置Session的超时时间。单位为秒

void putValue(String attribute, Object value) 不推荐的方法。已经被setAttribute(String attribute, Object Value)替代

Object getValue(String attribute) 不被推荐的方法。已经被getAttribute(String attr)替代

boolean isNew() 返回该Session是否是新创建的

void invalidate() 使该Session失效

 

f.Session对浏览器的要求

虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。

该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。

因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击桌面浏览器图标等打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。

如果客户端浏览器将Cookie功能禁用,或者不支持Cookie怎么办?例如,绝大多数的手机浏览器都不支持Cookie。Java Web提供了另一种解决方案:URL地址重写。


3.Djaango打开session功能

a.添加'django.contrib.sessions'  app 

b.添加'django.contrib.sessions.middleware.SessionMiddleware'中间件

c.SessionMiddleware 激活后,每个传给视图(view)函数的第一个参数``HttpRequest`` 对象都有一个 session 属性,这是一个字典型的对象。 你可以象用普通字典一样来用它。

request.session["fav_color"] = "blue"

del request.session["fav_color"]  //删除数据库,变化即保存

if "fav_color" in request.session:

...


4.浏览器关闭即失效会话 vs 持久会话

你可能注意到了,Google给我们发送的cookie中有 expires=Sun, 17-Jan-2038 19:14:07 GMT; cookie可以有过期时间,这样浏览器就知道什么时候可以删除cookie了。 如果cookie没有设置过期时间,当用户关闭浏览器的时候,cookie就自动过期了。 你可以改变 SESSION_EXPIRE_AT_BROWSER_CLOSE 的设置来控制session框架的这一行为。

缺省情况下, SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 False ,这样,会话cookie可以在用户浏览器中保持有效达 SESSION_COOKIE_AGE 秒(缺省设置是两周,即1,209,600 秒)。 如果你不想用户每次打开浏览器都必须重新登陆的话,用这个参数来帮你。

如果 SESSION_EXPIRE_AT_BROWSER_CLOSE 设置为 True ,当浏览器关闭时,Django会使cookie失效。

session的过期时间取决于cookie,因为session的id存在于cookie中。


5.登录和退出

>>> from django.contrib import auth

>>> user = auth.authenticate(username='john', password='secret')

>>> if user is not None:

...     print "Correct!"

... else:

...     print "Invalid password."



from django.contrib import auth


def login_view(request):   //登陆

    username = request.POST.get('username', '')

    password = request.POST.get('password', '')

    user = auth.authenticate(username=username, password=password)

    if user is not None and user.is_active:

        # Correct password, and the user is marked "active"

        auth.login(request, user)

        # Redirect to a success page.

        return HttpResponseRedirect("/account/loggedin/")

    else:

        # Show an error page

        return HttpResponseRedirect("/account/invalid/")


from django.contrib import auth


def logout_view(request):   //注销

    auth.logout(request)

    # Redirect to a success page.

    return HttpResponseRedirect("/account/loggedout/")


6.在view中限制已登录用户的访问

有很多原因需要控制用户访问站点的某部分。

一个简单原始的限制方法是检查 request.user.is_authenticated() ,然后重定向到登陆页面:

from django.http import HttpResponseRedirect


def my_view(request):

    if not request.user.is_authenticated():

        return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)

    # ...

def vote(request):

    if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):

        # vote here

    else:

        return HttpResponse("You can't vote in this poll.")

7.一个登陆的案例

url路由:

urlpatterns = [

    url(r'^login/$',login),

    url(r'^$',index),

    url(r'^admin/', admin.site.urls),

    url(r'^login/login$', authlogin),  ###表单提交时访问

    url(r'^logout/$', logout_view),

    url(r'^hello/$', hello),

]

视图:

def index(request):

    return HttpResponseRedirect("/login/")


def login(request,a=0):

    return render_to_response('login.html')


@csrf_exempt

def authlogin(request):

    username = request.POST.get('username', '')

    password = request.POST.get('password', '')

    user = auth.authenticate(username=username, password=password)

    if user is not None and user.is_active:

        auth.login(request, user)

        return render_to_response('haslogin.html')

    else:

        return HttpResponseRedirect("/login/")


def logout_view(request):

    auth.logout(request)

    return render_to_response('logout.html')


@login_required    #注解验证,需要登陆才能请求该页面

def hello(request):

    return render_to_response('hello.html')

login_required 自动完成做下面的事情:

a.如果用户没有登录, 重定向到 /accounts/login/ , 把当前绝对URL作为 next 在查询字符串中传递过去, 例如: /accounts/login/?next=/polls/3/ 。

b.如果用户已经登录, 正常地执行视图函数。 视图代码就可以假定用户已经登录了。



八、通用视图

1.为什么要使用通用视图

Django的通用视图抽象出一些在视图开发中常用的代码和模式,这样就可以在无需编写大量代码的情况下,快速编写出常用的数据视图。

2.使用通用视图的方法

使用通用视图的方法是在URLconf文件中创建配置字典,然后把这些字典作为URLconf元组的第三个成员。 (对于这个技巧的应用可以参看第八章向视图传递额外选项。)

from django.conf.urls.defaults import *

from django.views.generic.simple import direct_to_template


urlpatterns = patterns('',

    (r'^about/$', direct_to_template, {

        'template': 'about.html'    //不需要任何view代码

    })

)

网友评论

登录后评论
0/500
评论
技术小阿哥
+ 关注