Python学习之旅—Django基础


前言   

  前段时间业务比较繁忙,没时间更新博客,从这周开始将继续为各位更新博客。本次分享的主题是Django的基础部分,涵盖Django MTV三部分,并通过一个简单的班级管理系统来说明如何使用Django进行开发,好啦,开始今天的主题吧!


一.浅谈MVC、MTV和MVVM

   要学习Django,我们很有必要了解下MVC,MTV和MVVM三种模式。

  【001】MVC

  MVC(Model View Controller 模型-视图-控制器)是一种Web架构的模式(本文不讨论桌面应用的MVC),它把业务逻辑、模型数据、用户界面分离开来,让开发者将数据与表现解耦,前端工程师可以只改页面效果部分而不用接触后端代码,DBA可以重新命名数据表并且只需更改一个地方,无需从一大堆文件中进行查找和替换。MVC模式甚至还可以提高代码复用能力。现在几乎所有的Web开发框架都建立在MVC模式之上。 当然,最近几年也出现了一些诸如MVP, MVVM之类的新的设计模式。 但从技术的成熟程度和使用的广泛程度来讲,MVC仍是主流。

   MVC模式的三要素

  1. Model(数据模型)。是对客观事物的抽象。比如对于学生管理系统而言,学生类就是一个模型。 而一个模型通常还带有很多的和业务相关的逻辑,比如添加,更新,获取学生成绩等等,这些组成了模型的方法。对于开发者模型的表示方法非常易懂和清晰,可以通过非常便捷的代码来做CURD操作而无需写一条又一条的SQL语句。
  2. View(视图)。呈现给用户的效果,呈现的内容是基于Model,它也是收集用户输入的地方。比如输入用户的ID或者姓名,我们可以在终端页面查看该学生的信息,效果是通过对应的模板和样式把这个数据展示出来。
  3. Contorller(控制器)。是Model和View之间的沟通者。 因为View中不会对Model作任何操作,Model不会输出任何用于表现的东西,如HTML代码或者某种效果等,它就是点数据。而Contorller用于决定使用哪些Model,对Model执行什么操作,为视图准备哪些数据,是MVC中沟通的桥梁。

  MVC的特点是通信单向的:

  1. 浏览器发送请求
  2. Contorller和Model交互获取数据
  3. Contorller调用View
  4. View渲染数据返回

   【002】MTV

   和Rails、Spring、Laravel等其他语言的Web框架不一样,在Python的世界中,基本(除了Pylons)都使用了MVC的变种MTV(Model Templates View 模型-模板-视图):

  1. Model(数据模型)。和MVC的Model一样,处理与数据相关的所有事务:如何存取、如何确认有效性、包含哪些行为以及数据之间的关系等。
  2. Template(模板)。处理与表现相关的决定:如何在页面或其他类型文档中进行显示出来。
  3. View。处理业务逻辑,视图就是一个特定URL的回调函数,回调函数中描述数据:从Model取出对应的数据,调用相关的模板。它就是Contorller要调用的那个用来做Model和View之间的沟通函数,从而完成控制。

   这里有一点大家要注意区分MVC中的View和MTV中的View:MVC中的View的目的是「呈现哪一个数据」,即它只是负责渲染数据,然后呈现给用户效果;而MTV的View的目的是「数据如何呈现」,说白了,它负责从Model中取出数据,然后调用相关的模板,从而完成对整个流程的控制。

   也就是把MVC中的View分成了视图(展现哪些数据)和模板(如何展现)2个部分,而Contorller这个要素由框架自己来实现了,我们需要做的就是把(带正则表达式的)URL对应到视图就可以了,通过这样的URL配置,系统将一个请求发送到一个合适的视图。需要注意,Flask这种微框架就不是一个MVC模式的,因为它没有提供Model,除非集成了SQLAlchemy之类的ORM进来。

二.Web框架本质

  对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。当我们在浏览器中输入一个网址时,会发生如下的情况:

  1. 浏览器查找域名的IP地址
  2. 这一步包括DNS具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存…
  3. 浏览器向Web服务器发送一个HTTP请求
  4. 服务器的永久重定向响应(从http://example.comhttp://www.example.com)
  5. 浏览器跟踪重定向地址
  6. 服务器处理请求
  7. 服务器返回一个HTTP响应
  8. 浏览器显示HTML
  9. 浏览器发送请求获取嵌入在HTML 中的资源(如图片、音频、视频、CSS、JS等等)
  10. 浏览器发送异步请求.

  基于上面的需求,我们就可以自己实现一个Web框架,在这里我们只需要实现Socket服务端即可。来看下面一段简单到不行的Socket网络编程代码:

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8000))
sock.listen(5)
while True:
    conn, addr = sock.accept()
    data = conn.recv(8096)
    conn.send(b"OK")
    conn.close()

可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定?你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。这个规则就是HTTP协议,以后你发送请求信息也好,回复响应信息也罢,都要按照这个规则来。这个规则就是HTTP协议,如果你对HTTP协议还不熟悉,可以参考笔者前面的这篇文章。

   我们所要做的是让我们的Web服务能够根据用户请求的URL不同而返回不同的内容,同时给浏览器返回HTML源代码,代码如下:

import socket
import pymysql

sk = socket.socket()
sk.bind(('127.0.01', 9090))
sk.listen(5)


def get_html_content(file_name):
    with open(file_name, encoding='utf-8') as file_obj:
        content = file_obj.read()
    return content


def get_record():
    conn = pymysql.connect(host="127.0.0.1", port=3306, user='root', password='cisco', db="day48", charset='utf8')
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    cursor.execute("select id, name, balance from user")
    user_list = cursor.fetchall()
    cursor.close()
    conn.close()
    return user_list


def index():
    index_data = get_html_content("index.html")
    user_list = get_record()
    ret = ""
    for item in user_list:
        ret += """
            <tr>
                <td>{0}</td>
                <td>{1}</td>
                <td>{2}</td>
            </tr>
        """.format(item['id'], item['name'], item['balance'])
    data_new = index_data.replace('@@xx@@', ret)
    return data_new


def login():
    login_data = get_html_content("login.html")
    return login_data


url_func_map = [
    ("/index", index),
    ("/login", login)
]

while True:
    conn, addr = sk.accept()
    data = conn.recv(8096)
    header_str = data.decode('utf-8').split('\r\n')[0]
    request_target = header_str.split(' ')[1]

    func_name = None
    for i in url_func_map:
        if i[0] == request_target:
            func_name = i[1]
            break
    if func_name:
        response = func_name()
    else:
        response = "404,页面不见了"

    conn.send(b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n")
    conn.send(response.encode('utf-8'))
    conn.close()

上述代码模拟了一个web服务的本质.而对于真实开发中的Python Web程序来说,一般分为两部分:服务器程序和应用程序。服务器程序负责对Socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么它们就可以配合使用。一旦标准确定,双方根据约定的标准各自进行开发即可。WSGI(Web Server Gateway Interface)就是这样一种规范,它定义了使用Python编写的Web APP与Web Server之间接口格式,实现Web APP与Web Server间的解耦。

三. Django基础
【001】常用命令如下:

#安装Django,默认会安装最新版本的Django,目前生产环境下最稳定的Django版本是1.8.2,
#因此我们一般指定安装稳定的版本:pip install django==1.8.2
pip3 install django

# 在终端下创建Django项目,名为DjangoProject
$ django-admin startproject mysite

# Django项目环境终端,例如启动Django项目,创建项目中的应用,我们都使用该命令:
$ python manage.py shell

"""
注意如果在终端创建应用程序,我们需要确保此时的路径应该和 manage.py 是同一目录
"""
$ python manage.py startapp student

# 启动django
$ python manage.py runserver  # 如果不指定IP地址和端口,默认启动在本机IP 127.0.0.1的8000端口上
$ python manage.py runserver 8080  # 指定启动Django项目的端口8080
# 如果设置IP地址为0.0.0.0,表明局域网中的所有IP地址都可以通过8000端口访问Django项目
$ python manage.py runserver 0.0.0.0:8000  

"""
根据当前项目中的模型类来生成数据库脚本,并将脚本映射到数据库中去;
"""
$ python manage.py makemigrations
# 运行应用模型变化到数据库
$ python manage.py migrate

# admin创建管理员用户,按照提示输入用户名和对应的密码就好了邮箱可以留空,用户名和密码必填
$ python manage.py createsuperuser

# 修改用户密码
$ python manage.py changepassword username

【002】Django基本目录结构及作用

mysite/              # 项目的容器,名称自定义
    manage.py        # 命令行实用工具,以各种方式与该Django项目进行交互
    mysite/          # 实际的Python项目
        __init__.py  # 空文件,导入不出错
        settings.py  # 这个Django项目配置
        urls.py      # 这个Django项目的URL声明; 一个Django驱动网站的“目录”
        wsgi.py      # 一个入口点为WSGI兼容的Web服务器,以满足您的项目

   【003】静态文件配置

概述:

     静态文件交由Web服务器处理,Django本身不处理静态文件。简单的处理逻辑如下(以nginx为例):

              URI请求-----> 按照Web服务器里面的配置规则先处理,以nginx为例,主要求配置在nginx.conf里的location

                         |---------->如果是静态文件,则由nginx直接处理

                         |---------->如果不是则交由Django处理,Django根据urls.py里面的规则进行匹配

    以上是部署到Web服务器后的处理方式,为了便于开发,Django提供了在开发环境的对静态文件的处理机制。

static配置:

  初学者往往会搞不明白STATIC——URL和STATICFILES_DIRS 的区别,如下所示:

STATIC_URL = '/static/'  # 这是引用名
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),  # 静态文件存放的位置,例如项目中涉及到css,js,image等文件,这里推荐使用
# 列表的形式,要不然可能会报错:ImproperlyConfigured: Your STATICFILES_DIRS setting is not a tuple or list # 官方文档对此有详细的描述:https://docs.djangoproject.com/en/dev/howto/static-files/
] TEMPLATE_DIRS
= (os.path.join(BASE_DIR, 'templates'),)

注意点如下:

必须严格按照static目录下的路径来,也就是说在HTML中引用静态文件,开头必须是/static/目录
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"), 
]
<link href="/static/lib/sweetalert/sweetalert.css" rel="stylesheet">

如下图所示:

Media型文件在settings.py中的配置:

# in settings:

MEDIA_URL="/media/"
MEDIA_ROOT=os.path.join(BASE_DIR,"app01","media","upload")

# in urls:
from django.views.static import serve
url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
'''
        静态文件的处理又包括STATIC和MEDIA两类,这往往容易混淆,在Django里面是这样定义的:
        
        MEDIA:指用户上传的文件,比如在Model里面的FileFIeld,ImageField上传的文件。如果你定义
        
        MEDIA_ROOT=c:\temp\media,那么File=models.FileField(upload_to="abc/")#,上传的文件就会被保存到c:\temp\media\abc

        eg:
            class blog(models.Model):
                   Title=models.charField(max_length=64)
                   Photo=models.ImageField(upload_to="photo")
          上传的图片就上传到c:\temp\media\photo,而在模板中要显示该文件,则在这样写
          在settings里面设置的MEDIA_ROOT必须是本地路径的绝对路径,一般是这样写:
                 BASE_DIR= os.path.abspath(os.path.dirname(__file__))
                 MEDIA_ROOT=os.path.join(BASE_DIR,'media/').replace('\\','/')

        MEDIA_URL是指从浏览器访问时的地址前缀,举个例子:
            MEDIA_ROOT=c:\temp\media\photo
            MEDIA_URL="/data/"
        在开发阶段,media的处理由django处理:

           访问http://localhost/data/abc/a.png就是访问c:\temp\media\photo\abc\a.png

           在模板里面这样写<img src="/media/abc/a.png">

           在部署阶段最大的不同在于你必须让web服务器来处理media文件,因此你必须在web服务器中配置,
           以便能让web服务器能访问media文件
           以nginx为例,可以在nginx.conf里面这样:

                 location ~/media/{
                       root/temp/
                       break;
                    }

           具体可以参考如何在nginx部署django的资料。
           
           '''

【004】视图层之路由配置系统(views)

urlpatterns = [
  url(正则表达式, views视图函数,参数,别名),
  url(正则表达式, views视图函数,参数,别名),
] 参数说明: 一个正则表达式字符串 一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串 可选的要传递给视图函数的默认参数(字典形式) 一个可选的name参数

来看下面的例子:

urlpatterns = [
    url(r'login.html/', views.login, name="login")]
r'login.html/'表示匹配用户在浏览器中请求访问的URL地址,通常配合正则表达式进行精确匹配;
views.login表示一旦匹配到前面指定的地址,就执行这里的视图函数,login是views.py文件中的视图函数,一般会有
多个函数存在于views.py文件中,通常情况下,一般使用在指定应用的目录的urls.py文件下,导入,例如我新建了一个
应用app01,在它下面的urls.py文件中,我们定义具体url和函数的对应关系:
# 先导入具体的包,然后再指定具体url和视图函数的对应关系
from app01 import views 
url(r'login.html/', views.login, name="login")
name属性取别名。
来看如下的具体需求:
1.通常在form表单中,我们往往使用post方法向服务器传递数据,同时要指定哪个url地址来处理数据,我们往往
采用硬编码的形式将路径写死:
<form action="/login/" method="post">
    {% csrf_token %}
   <p>姓名:<input type="text" name="name"> </p>
   <p>密码:<input type="password" name="pwd"> </p>
   <p><input type="submit" name="提交"> </p>
</form>
上面的需求表示我们将用户输入的数据交给/login/处理,
现在有20个这样的需求,都是交给/login/处理,但是突然由于业务变动,我们想将
用户输入的数据交给应用app01下面的login.html,即处理地址变为:app01/login.html,
按照我们硬编码的方式,那就要重新修改上述20个函数,是不是太麻烦了!!此时就可以用到
name属性了,我们可以将form表单中的action属性设置为jinja2的模板形式:
action="{%  url "login" %}",采用url指定地址即可,url后面是别名,这样,在后台urls.py中,我们
可以设置name属性,其值为这里的别名:login,例如:
url(r'login.html/', views.login, name="login")

这样设置后,凡是用户端能够匹配到r'login.html/'这个正则表达式的url,我都采用login函数处理,并且将
login.html模板文件中的action地址替换为:app01/login.html,我们来看实际的运行效果:


以上我们称之为url的反向解析。下面通过实际例子分析name参数和include参数的作用


分析:用户在浏览器中输入:http://127.0.0.1:8090/app01/login.html/;首先到
与Django同名的app目录的urls.py文件中匹配:
url(r'app01/', include('app01.urls')匹配到第二条,这里我们来插播一个新的关键字参数include。
还记得我们开始创建Django项目时,会把所有与业务相关的url和视图函数的匹配关系全部都放到urls.py文件中,如下
所示:
urlpatterns = [
    url(r'^teacher_list/', teacher_list),
    url(r'^add_teacher/', add_teacher),
    url(r'^delete_teacher/', delete_teacher),
    url(r'^modal_teacher_list/', modal_teacher_list),
    url(r'^modal_edit_teacher/', modal_edit_teacher),

    url(r'^class_list/', class_list),
    url(r'^add_class/', add_class),
    url(r'^delete_class/', delete_class),
    url(r'^edit_class/', edit_class),
]
这只是我们学习时候的案例,在现实生产环境中,往往对应上百条的url,肯定不能全都写到url.py文件中,理由
有2点,如果一个url和视图函数的对应关系写错了,那么其他应用都运行不了,这就造成了严重的耦合,另外为了
保证应用之间的独立性,让它们互不影响,我们也不应该让它们都放在一起。于是出现了include关键字,也即路由分发策略
拿我们刚才的例子来说:
url(r'app01/', include('app01.urls'),用户输入:http://127.0.0.1:8090/app01/login.html/
首先匹配到app01,然后执行后面的视图函数,当遇到include关键字,此时相当于告诉Django你去include
里面的参数地址中寻找视图函数,此时Django会去应用app01中的urls.py文件中去寻找视图函数,内容如下:
urlpatterns = [
    url(r'login.html/', views.login, name="login"),
    url(r'index.html/', views.index)
]
到达app01.urls.py文件中,继续拿用户剩下的地址login.html/进行匹配,因此会首先匹配到第一条正则表达式,于是
执行后面的视图函数login,这里由于我们设置了url反向解析,因此模板文件中使用url进行反向解析的实际地址会被替换为
前面正则表达式匹配到的路径:login.html。从运行效果可以看到,action中的路径是绝对路径,即,action的实际地址是
/app01/login.html/。到此为止,我们总算讲完了这几个关键字参数。

【005】URL具体配置规则

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

上面是一些具体的匹配例子,我们来看如下几个需要注意的点:

NOTE:
    1 URL一旦匹配成功则不再继续往下面匹配;
    2 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号;例如url(r'^articles/([0-9]{4})/$', views.year_archive),
    我们在[0-9]{4}加入了(),表示我们想从这个url中捕获任意一个4位整数
    3 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
    4 每个正则表达式前面的'r'是可选的但是建议加上。

一些请求的例子:

    /articles/2005/3/ 不匹配任何URL模式,因为列表中的第三个模式要求月份应该是两个数字。
    /articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。
    /articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数
    views.month_archive(request, '2005', '03')。关于调用函数的传参问题,我们将在下面一节中详细描述。

【006】2.1.2 有名分组(named group)

  下面我们来看有名分组的几个例子:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]

这个实现与我们前面的例子几乎没有太大区别,但细心的同学可能已经发现了,在捕获具体值得时候,我们加上了
?P<year>,例如url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),我们在[0-9]{4}    加上了(?P<year>)
这表示我们为捕获到的值取一个名字year,并将该值以关键字传参的形式传递给后面的函数views.year_archive。
例如让地址 /articles/2005/03/ 去匹配上述的url地址,匹配成功后,我们将调用views.month_archive(request, year='2005', month='03')函数;
再例如:articles/2003/03/03/ ,请求将调用函数views.article_detail(request, year='2003', month='03', day='03')  。

通过对比上面的示例可知,使用(?p<关键字名称>)捕获的值将作为关键字参数而不是位置参数传递给视图函数。

【007】URLConf在什么上查找

URLconf 在请求的URL 上查找,将它当做一个普通的Python 字符串。不包括GET和POST参数以及域名。
例如,http://www.example.com/myapp/ 请求中,URLconf 将查找myapp/。
在http://www.example.com/myapp/?page=3 请求中,URLconf 仍将查找myapp/。
URLconf 不检查请求的方法。换句话讲,所有的请求方法 —— 同一个URL的POST、GET、HEAD等等 —— 都将路由到相同的函数。

【008】每个捕获的参数都作为一个普通的Python 字符串传递给视图,无论正则表达式使用的是什么匹配方式。例如,下面这行URLconf 中:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive)
views.year_archive() 的year 参数将是一个字符串    

【009】指定视图参数的默认值

# URLconf
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^blog/$', views.page),
    url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]

# View (in blog/views.py)
def page(request, num="1"):
在上面的例子中,两个URL模式指向同一个视图views.page —— 但是第一个模式不会从URL 中捕获任何值。如果第一个模式匹配,
page() 函数将使用num参数的默认值"1"。如果第二个模式匹配,page() 将使用正则表达式捕获的num 值。

三. 视图层

视图层主要负责处理用户的请求并返回响应。返回的内容可以是HTML内容的网页,或重定向,或404错误,或一个XML文件,或一个形象,字符串等等。

在Django中,http请求会产生两个核心对象:http请求:HttpRequest对象 http响应:HttpResponse对象 所在位置:django.http,下面我们分别来梳理下HttpRequest和HttpResponse的一些重点属性和方法:

#从django.http模块中导入HttpResponse类[1]
from django.http import HttpResponse
import datetime

def current_datetime(request):      #[2]
    now=datetime.datetime.now()
    html="<html><body>现在时刻:%s.</body></html>" %now
    return HttpResponse(html)

# 注意:这是一段很简单、简陋的例子
# 在这个(views.py)视图中每一个函数称作视图函数,视图函数都以一个HttpRequest对象为第一个参数,该参数通常命名为request,注意这里的参数也可以命名为其他的名称,只是request
这个名称能够做到见名知意。

由上面示例得到视图函数第一个参数是一个HttpRequest对象,那么通过这个对象可以拿到一些信息,如下:

A:HttpRequest

【01】HttpRequest对象中的常见属性:

1.HttpRequest.body

  一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。

  但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。

  另外,我们还可以用 python 的类文件方法去操作它,详情参考 HttpRequest.read() 。

 
2.HttpRequest.path

  一个字符串,表示请求的路径组件(不含域名),例如我们在浏览器中输入访问地址:127.0.0.1:8000/music/bands/the_beatles/
    HttpRequest.path得到的路径就是:"/music/bands/the_beatles/"


3.HttpRequest.method

  一个字符串,表示请求使用的HTTP 方法。必须使用大写。

  例如:"GET""POST"

 

4.HttpRequest.encoding

  一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。
   这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。
   接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。
   如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。

5.HttpRequest.GET 

  一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。
    例如我们在浏览器中输入访问地址:127.0.0.1:8000/student_list/?edit_id=3 表示我们想编辑的学生ID号为3
    在视图函数中,可以使用request.GET.get('edit_id')获取ID值3,从而方便我们去数据库中修改。


6.HttpRequest.POST

  一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。

  POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。
   因此,不应该使用 if request.POST  来检查使用的是否是POST 方法;应该使用 if request.method == "POST" 
   我们通常使用该属性来获取用户在输入框中的用户名和密码等信息,例如:
   username = request.POST.get('username')
   password = request.POST.get('password')
  另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。

7.HttpRequest.COOKIES
  一个标准的Python 字典,包含所有的cookie。键和值都为字符串,例如获取服务端发送的cookies值:
        request.COOKIES.get("login1") 只要存在该cookie,那么证明用户处于登入状态。

8.HttpRequest.FILES

  一个类似于字典的对象,包含所有的上传文件信息。
   FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。
   
  注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会
   包含数据。否则,FILES 将为一个空的类似于字典的对象。


9.HttpRequest.session

   一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。
    完整的细节参见会话的文档。例如我们一般使用request.session.get('key')获取对应的value值

注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,例如一个用户可能有多个爱好,那么当我们使用

select标签返回数据给后台时是一个列表,此时后台应该使用request.POST.getlist("hobby")

【02】HttpRequest对象的常用方法

1.HttpRequest.get_full_path()

  返回 path,如果可以将加上查询字符串。

  例如:"/music/bands/the_beatles/?print=true"

2.HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

  返回签名过的Cookie 对应的值,如果签名不再合法则返回django.core.signing.BadSignature。

  如果提供 default 参数,将不会引发异常并返回 default 的值。

  可选参数salt 可以用来对安全密钥强力攻击提供额外的保护。max_age 参数用于检查Cookie 对应的时间戳以确保Cookie 的时间不会超过max_age 秒。

        复制代码
        >>> request.get_signed_cookie('name')
        'Tony'
        >>> request.get_signed_cookie('name', salt='name-salt')
        'Tony' # 假设在设置cookie的时候使用的是相同的salt
        >>> request.get_signed_cookie('non-existing-cookie')
        ...
        KeyError: 'non-existing-cookie'    # 没有相应的键时触发异常
        >>> request.get_signed_cookie('non-existing-cookie', False)
        False
        >>> request.get_signed_cookie('cookie-that-was-tampered-with')
        ...
        BadSignature: ...    
        >>> request.get_signed_cookie('name', max_age=60)
        ...
        SignatureExpired: Signature age 1677.3839159 > 60 seconds
        >>> request.get_signed_cookie('name', False, max_age=60)
        False
        复制代码
         
3.HttpRequest.is_secure()

  如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。


4.HttpRequest.is_ajax()

  如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。

  大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。

  如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware, 
   你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。

B:HttpResponse对象

对于HttpRequest对象来说,是由django自动创建的,但是,HttpResponse对象就必须我们自己创建。每个view请求处理方法必须返回一个HttpResponse对象,这就是为什么我们在

视图函数中都会看到return关键字的原因,相当于这是给客户端浏览器的响应,因为前面我们说过HTTP协议是基于请求响应模式的;我们来看看在HttpResponse对象上扩展的常用方法:

01】render(request, template_name[, context])
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
例如:return render(request, "class_list.html", {'class_list': class_list})
这句话的意思是我使用context对象{'class_list': class_list}去渲染模板文件:class_list.html,最终将渲染完成的html文件
返回给客户端浏览器,然后客户端浏览器再为终端用户解释HTML源码,于是我们看到了绚丽的HTML页面,就是这么简单。
参数:
     request: 用于生成响应的请求对象,第一个参数必须是request,表明这是客户端的请求对象

     template_name:要使用的模板的完整名称,可选的参数

     context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。

     content_type:生成的文档要使用的MIME类型。默认为DEFAULT_CONTENT_TYPE 设置的值。

     status:响应的状态码。默认为200。
     我们所说的视图渲染,其实就是使用context对象中的数据去模板文件template_name中替换需要替换的数据,而在Django中,我们需要
     遵循一套替换规则,所以产生了Jinja2模板语言。context对象中的数据就是我们与后台数据库交互获取的数据,这就构成了整个动态网站的基础。
有时我们仅仅是需要将页面呈现给终端用户,并不需要使用数据去渲染模板文件,所以此时可以不需要context对象,如下:
return render(request, "class_list.html"),关于render与Redirect的区别,本节稍后会给出详细解释。

【02】redirect 函数
参数可以是:
一个模型:将调用模型的get_absolute_url() 函数
一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
一个绝对的或相对的URL,将原封不动的作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

此方法可称之为页面跳转,HTTP状态码为302,
表示达达到某种条件时,如再登陆淘宝界面,登陆成功之后会跳转到用户的主界面
默认情况下,为临时重定向;通过 permanent=True 设置永久重定向

下面我们来看看render()方法和redirect方法的区别:

01】render(request, template_name[, context])
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
例如:return render(request, "class_list.html", {'class_list': class_list})
这句话的意思是我使用context对象{'class_list': class_list}去渲染模板文件:class_list.html,最终将渲染完成的html文件
返回给客户端浏览器,然后客户端浏览器再为终端用户解释HTML源码,于是我们看到了绚丽的HTML页面,就是这么简单。
参数:
     request: 用于生成响应的请求对象,第一个参数必须是request,表明这是客户端的请求对象

     template_name:要使用的模板的完整名称,可选的参数

     context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。

     content_type:生成的文档要使用的MIME类型。默认为DEFAULT_CONTENT_TYPE 设置的值。

     status:响应的状态码。默认为200。
     我们所说的视图渲染,其实就是使用context对象中的数据去模板文件template_name中替换需要替换的数据,而在Django中,我们需要
     遵循一套替换规则,所以产生了Jinja2模板语言。context对象中的数据就是我们与后台数据库交互获取的数据,这就构成了整个动态网站的基础。
有时我们仅仅是需要将页面呈现给终端用户,并不需要使用数据去渲染模板文件,所以此时可以不需要context对象,如下:
return render(request, "class_list.html"),关于render与Redirect的区别,本节稍后会给出详细解释。


【02】redirect 函数
参数可以是:
一个模型:将调用模型的get_absolute_url() 函数
一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
一个绝对的或相对的URL,将原封不动的作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

此方法可称之为页面跳转,HTTP状态码为302,
表示达达到某种条件时,如再登陆淘宝界面,登陆成功之后会跳转到用户的主界面
默认情况下,为临时重定向;通过 permanent=True 设置永久重定向

【03】render方法和redirect的区别(重点掌握)
从使用方法的角度上:
render方法表示如果页面需要模板语言渲染,需要将数据库的数据加载到html,那么此时就需要使用render方法;
而redirect方法表示仅仅是重定向到某个网页,并没有数据渲染模板文件的过程;
从客户端与服务端的交互角度上:
使用render()方法时候,客户端浏览器和服务器只需要进行一次HTTP请求-响应链接,而render()方法需要进行二次请求-响应
链接,我们以登入的案例为例来进行分析:
def login(request):
    if request.method == 'POST':
        username = request.POST.get("name")
        password = request.POST.get("pwd")
        if username == 'carson' and password == 'cisco123':
            return redirect('/index.html/')
        else:
            return render(request, "login.html")
    return render(request, "login.html")

第一次:用户发送Get请求,获取登入页面的url:http://127.0.0.1:8000/login.html/;由于是get请求,所以并没有数据,
接着去urls.py中匹配到login视图函数,然后调用执行login函数——login(request);返回结果return render(request, "login.html")
所以当我们在浏览器中输入登入页面的url:http://127.0.0.1:8000/login.html/;会看到登入界面,由于这个过程非常快速,所以
我们压根感觉不到;

第二次,用户接收到登入页面,开始输入用户名和密码;数据通过POST方法发送到后台,后台的处理地址是action中指定的地址:/login/
所以我们依然去urls.py中匹配,同样匹配到login视图函数,此时执行POST请求方法,用户名和密码正确,于是乎,我们就可以访问首页
注意这里使用的是redirect()跳转到index.html,这里隐藏了如下两点含义:
1.当用户输入的用户名和密码正确,服务端给出的响应是:return redirect('/index.html/'),客户端在接受到该条命令后,发现
这是一条让我跳转的命令,因为此时的HTTP状态码是302,所以客户端浏览器会再次向服务端发送一个请求:
http://127.0.0.1:8000/index.html/,此时这里是一个get请求,还是走上面的流程,最终匹配到index视图函数,然后将index.html模板返回给
客户端浏览器,最后客户端浏览器在终端为我们渲染出效果,于是乎,我们的体验就是:输入了正确的用户名和密码后,就跳转到
首页,输入错误后,直接停留在login.html

综上所述:render()方法只是建立了一次完整的HTTP请求-响应链接;而redirect()方法建立了2次完整的HTTP请求-响应链接。






  

---恢复内容结束---

优质内容筛选与推荐>>
1、BZOJ3238:[AHOI2013]差异——题解
2、C++ 连接mysql等数据库(二)
3、批量更新sql
4、推荐10 个短小却超实用的 JavaScript 代码段
5、LOJ-10102(求A到B之间的割点)


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn