Django中的V——视图

697 阅读11分钟

视图View

视图中对于数据库的操作请参考另一篇博客,Django概述:blog.csdn.net/qq_27114273…

request

request是Django框架根据Http请求报文生成的对象,包含了请求的所有信息,默认是视图函数的第一个参数

组成属性:

  • path:请求页面的完整地址,不包括域名
  • method:请求方法,大写,'GET', 'POST'
  • GET:类字典对象,包含GET参数信息
  • POST:类字典对象,包含POST参数信息,可能为空
  • request:为了方便而创建,类字典对象,先搜索POST,然后GET,在高版本去除
  • cookies:标准Python字典,包含所有cookies
  • FILES:类字典对象,包含所上传的文件。
    • name:字符串上传文件的名
    • content-type:文件的内容类型
    • content:文件的原始内容
  • META:标准Python字典,包含所有HTTP头信息,完整地址,端口,语言,编码等
  • session:可读写的类字典对象,仅当激活session时有效
  • is_ajax:是否是ajax请求

几个方法:

  • __getitem__(key):获取所给键的GET/POST值,先查找POST,然后GET,不存在则跑出keyerror
  • has_key():request.POST.has_key(),返回布尔值,是否包含所给的键
  • get_full_path():返回path,若请求参数有效,则会附带请求参数
  • is_secure():如果请求是HTTPS请求,返回True

QueryDict对象,是一个类似字典的类,被设计成可以处理同一个键有多个值的情况。它的实例是一个不可变对象,也就是说不能改变request.POST或者request.GET的值

  • __getitem__(key):返回给定键的值,有多个就返回最后一个值
  • get(key, default):获取键的值,如果不存在返回默认值,类似于__getitem__()
  • getlist(key):获取键对应值的列表

处理文件

request请求上传的文件包含在FILES里面,键来自<input type="file" name=""/>中的name,只在POST请求中存在,并且提交的

包含entype="multipart/form-data"时才生效,否则FILES只是一个空的类字典对象。

以图像为例,展示一个文件的上传和存储的过程。首先在视图函数中接受这个文件,具体实现的思路是,将文件拆分成小块的可迭代对象,然后将其写入到文件里面。

from DjangoView.settings import MEDIA_ROOT
def upload(request):
    if request.method = "POST":
        username = request.POST.get("username")
        icon = request.FILES.get("icon")
        save_filename = os.path.join(MEDIA_ROOT, icon.name)
        # 用with方法来实现文件存储
        with open(save_filename, "wb") as save_file:
            # chunks 将文件拆分差成块的可迭代对象
            for part in icon.chunks():
                save_file.write(part)
                save_file.flush()
        user = User(username=username, icon=save_filename)
        user.save()
        return HttpResponse("upload file")

会话技术

cookie

通过返回方法生成的实例来设置cookie,分为加盐和不加盐,加盐的cookie更安全

resp = HttpResponse("content")
# 设置cookie
resp.set_cookie("key", "value", max_age="过期时间")
# 删除cookie
del request.COOKIES["key"]  # 删除了服务器的cookie,浏览器还有
resp.delete_cookie("key")  # 删除了对应键的值,键还存在
resp.flush()  # 删除所有cookie
# 获取cookie
request.COOKIES.get("key")

加盐的cookie设置获取和删除

value = request.POST.get("name")
resp = HttpResponse("redirect to login")
# 设置加盐cookie,盐是一个字符串
response.set_signed_cookie("key", "value", salt="String")
# 获取加盐cookie,需要提供加的盐
value = request.get_signed_cookie("key", salt="String")
# 删除加盐cookie
resp.delete_cookie("key")
return resp

cookie的参数:

  • key:键
  • value:值
  • max_age:过期时间,时间为秒
  • expires:过期时间,为具体时间
  • path:生效路径
  • domain:生效的域名
  • secure:HTTPS请求时设置为True
  • httponly:用于http传输,JavaScript无法获取

session

默认

session是数据保存在服务器的回话技术,flask默认将session存在了cookie中,django默认存在了ORM中,在迁移时默认生成一张django-session的表,django将session持久化到了内存中。

主要有三个字段:

  • session_key:唯一
  • session_data:数据拼接混淆串,跟base64编码的串结合在一起
  • session_expire:默认过期时间14天
def login(request):
    username = request.POST.get("username")
    # 设置session
    request.session["username"] = username
    # 获取session的值
    username = request.session.get("username")
    # cookie  session 一起干掉
    request.session.flush()
    return HttpResponse("you are in")

你也可以在模板里面,用模板语法获取到session

<h3>{{ request.session.username }}</h3>

redis

session实现在redis中存取借助了一个模块pip install django-redis-sessions,或者在下载页面下载然后python setup.py install,github地址:github.com/martinrusev… ,安装之后需要在settings.py里面设置如下

# 引擎设置
SESSION_ENGINE = 'redis_session.session'
# 链接设置
SESSION_REDIS = {
    'host': 'localhost',
    'port': 6379,
    'db': 0,
    'password': 'password',
    'prefix': 'session',
    'socket_timeout': 1,
    'retry_on_timeout': False
    }
# 如果使用远程服务器的redis
SESSION_REDIS = {
    'unix_domain_socket_path': '/var/run/redis/redis.sock',
    'db': 0,
    'password': 'password',
    'prefix': 'session',
    'socket_timeout': 1,
    'retry_on_timeout': False
}

集群的redis需要配置redis哨兵(Redis Sentinel),即从服务器,也可以设置Redis Pool

# 配置哨兵信息
SESSION_REDIS_SENTINEL_LIST = [(host, port), (host, port), (host, port)]
SESSION_REDIS_SENTINEL_MASTER_ALIAS = 'sentinel-master'
# 配置redis池
SESSION_REDIS = {
    'prefix': 'session',
    'socket_timeout': 1
    'retry_on_timeout': False,
    'pool': [{
        'host': 'localhost3',
        'port': 6379,
        'db': 0,
        'password': None,
        'unix_domain_socket_path': None,
        'url': None,
        'weight': 1
    },
    {
        'host': 'localhost2',
        'port': 6379,
        'db': 0,
        'password': None,
        'unix_domain_socket_path': None,
        'url': None,
        'weight': 1
    },
    {
        'host': 'localhost1',
        'port': 6379,
        'db': 0,
        'password': None,
        'unix_domain_socket_path': None,
        'url': None,
        'weight': 1
    }]
}

配置好redis的连接,就可以使用redis来存session了,存取的命令没有任何的改变,Django会自动帮我们完成。

cache

默认

Django是自带缓存系统的,默认将缓存放在的了配置的数据库中,在终端命令行里面可以使用默认命令创建缓存表,python manager.py createcachetable TableName,会在数据库中生成一张自定义名称TableName的表,用来存储缓存,包含三个参数,都不允许为空

  • cache_key
  • value
  • expires

需要在settings.py中配置缓存数据库:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'TableName',
        'TIMEOUT': '60',
        'KEY_PREFIX': 'Prefix',
        'VERSION': '1',
    }
}

缓存的存取:

from django.core.cache import cache
resp = render(request, "person_list.html", locals())
# 设置缓存
cache.set("persons", resp, timeout=60*5)
return resp
# 获取缓存
result = cache.get("persons")

redis

用redis来实现缓存是非常理想的方式,Django中配置redis作为缓存数据库,需要用到django-redis,或者django-redis-cache模块,配置基本一直,以django-redis为例

虚拟环境输入pip install django-redis,然后在settings.py里面配置缓存:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
             # "PASSWORD": "密码",
        }
    }
}

缓存的存取写法不变。

我们也可以使用redis实现全站缓存,来提高服务器的运行效率。 使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户, 当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

# 中间件
MIDDLEWARE = [
    'django.middleware.cache.UpdataCacheMiddleware',
    # 其他中间件
    'django.middleware.cache.FetchFromCacheMeddileware',
]

缓存可以在单独的视图中使用

方法一:通过装饰器

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def login(request):
    username = cache.get("username")

方法二:通过url

from django.views.decorators.cache import cache_page

urlpatterns = [
    url(r'^login/', cache_page(60 * 15)(login)),
]

分页器

Django自带了一个分页器Paginator,帮助我们实现多条数据的展示,当我们实例化一个Paginator类的实例时,需要给Paginator传入两个参数,第一个是一个列表、元组或者查询结果集QuerySet,第二个是每页显示的数据,是一个整数

Paginator类中有三个常用属性:

  • count:表示所有页面对象的总数
  • num_page:表示页面总数
  • page_range:下表从1开始的页面范围迭代器

Page对象:Paginator类提供一个**page(number)**函数,该函数返回的就是一个page对象,number表示第几个分页,在前端显示数据时,主要的操作都是基于Page()对象的。

Page对象有三个常用的属性:

  • object_list:表示当前页面上所有对象的列表
  • numberv:表示当前也的序号,从1开始计数
  • paginator:当前Page对象所属的Paginator对象

Page对象还拥有几个常用的函数:

  • has_next():判断是否有下一页,有就返回True
  • has_previous():判断是否有上一页,有就返回True
  • has_other_pages():判断是否有上一页或下一页,有就返回True
  • next_page_number():返回下一页的页码,如果下一页不存在抛出InvalidPage 异常
  • previous_page_number():返回上一页页码,如果上一页不存在抛出InvalidPage 异常

在view视图中,获取前端传过来的页面数据,包括页码数,每页条数,从数据库中查询数据,构建分页器,生成响应

def person_list(request):
    page = int(request.GET.get("page, 10"))
    per_page = int(request.GET.get("per_page"))
    persons = Person.objects.all()
    # 构建分页器
    paginator = Paginator(persons, per_page)
    # 前一步已经生成了全部的页面,我们直接获取具体的某一页
    page_object = paginator.page(page)
    # 生成响应
    response = render(request, "person_list.html", locals())
    return response

在html里面接受传入的页面数据

<!--用传过来的页面数据生成无序列表-->
<ul>
    {% for person in page_object.object_list %}
</ul>

下面展示的是页码的生成,通过判断是否有前页后页,在第一和最后页时,将按钮变为不可点击状态。用到了bootstrap和后面要讲的反向解析

<nav aria-label="Page navigation">
    <ul class="pagination">
        {% if page_object.has_previous %}
            <li>
                <a href="{% url 'two:persons' %}?page={{ page_object.previous_page_number }}&per_page={{ per_page }}"
                   aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
        {% else %}
            <li class="disabled">
                <a href="#" aria-label="Previous">
                    <span aria-hidden="true">&laquo;</span>
                </a>
            </li>
        {% endif %}
        {% for page_num in paginator.page_range %}
            <li><a href="{% url 'two:persons' %}?page={{ page_num }}&per_page={{ per_page }}">{{ page_num }}</a></li>
        {% endfor %}
        {% if page_object.has_next %}
            <li>
                <a href="{% url 'two:persons' %}?page={{ page_object.next_page_number }}&per_page={{ per_page }}"
                   aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        {% else %}
            <li class="disabled">
                <a href="#" aria-label="Next">
                    <span aria-hidden="true">&raquo;</span>
                </a>
            </li>
        {% endif %}
    </ul>
</nav>

中间件

Django中的中间件也是面向切面编程的一种,注册在settings.py中的中间件会按照顺序加载执行,是django内置的一个底层插件,本质上是MiddlewareMixin的子类,是一个类装饰器,调用__call__方法。

我们可以用中间件来实现类似于记录、日志、用户认证、黑白名单、优先级、拦截器、反爬虫等功能

内置的切点:

  • process_request
  • process_view
  • process_template_response
  • process_response
  • process_exception
    • 界面友好化
    • 错误记录

首先创建一个middleware的package,在理编写我们的功能代码

class CustomMiddleware(MiddlewareMixin):
    # 重写process_request方法
    def process_request(self, request):
        print(request.path)
    # 重写process_exception方法,出异常时重定向至首页
    def process_exception(self, request, excception):
        print(exception)
        return redirect(reverse("app:index"))

然后在settings.py里面注册中间件

MIDDLEWARE = [
    'middleware.LearnMiddlw.CustomMiddleware',
    ...
]

返回Response

HttpResponse

相对与HttpRequest来说,HttpRequest是Django根据request自动创建的,而HttpResponse是开发者自己创建的,我们编写的每个视图都要实例化、填充和返回一个HttpResponse对象。也就是函数的return值。

可传递的数据:

  • 字符串
  • 可迭代对象
  • 设置头部字段

1.字符串:最简单的是传递一个定义的字符串返回

response = HttpResponse("This is a string to return", content_type("text/plain"))

也可以将它的实例化对象看做类文件写入:

response = HttpResponse()
response.write("<p>Here is a title for a web page</p>")

2.可迭代对象:HttpResponse会立即处理这个迭代器,并把它的内容存成字符串,最后废弃这个迭代器。比如文件在读取后,会立刻调用close()方法,关闭文件。

3.设置头部字段:可以把HttpResponse对象当作一个字典一样,在其中增加和删除头部字段。

response = HttpResponse()
response["age"] = 18
del response["age"]

与字典不同的是,如果删除的头部字段不存在的话,会抛出KeyError,且不包含换行(CR、LF),会出现BadHeaderError异常

返回制定的数据类型content_type,是可选的,用于填充HTTP的Content-Type头部。如果未指定,默认情况下由DEFAULT_CONTENT_TYPEDEFAULT_CHARSET设置组成:text/html; charset=utf-8

content_type可以在MIME(多用途互联网邮件扩展)的概念中找到,他指定一个数据的类型和打开此数据的插件。

JsonResponse

是HttpResponse的一个子类,默认content_type = "application/json",传入的参数是一个字典类型

# view视图中
data = {
    "msg": "ok",
    "status": 200
}
return JsonResponse(data)

redirect

重定向,可以根据url、第三方路径、命名空间、对象、视图view重定向

# 根据url路径
def my_view(request):
    return redirect("/index/")
# 根据第三方路径
def my_view(request):
    return redirect("heeps://www.cn.bing.com")
# 根据命名空间
def my_view(request):
    return redirect(reverse("blog:article_list"))

根据对象重定向,前提是在模型中定义了get_absolute_url()方法,是定义Model的对象的查看地址,主要是用在RSS与后台查看:

在模型models.py中:

class Post(models.Model):
	title = models.CharField('标题',max_length=200)
	slug = models.CharField('slug',max_length=255,blank=True)
	summary = models.TextField('摘要',blank=True)
    body = models.TextField('正文')

	def get_absolute_url(self):
        return reverse('post_view',args=[self.slug])

视图views.py中:

def my_view(request):
    obj = MyModel.objects.get(...)
    return redirect(obj)

扩展:在模板中使用get_absolute_url()方法,在模板中生成标签时,使用这个方法而不用指明url路由的信息

<a href="{{ object.get_absolute_url }}">{{ object.name }}</a>

正向解析,反向解析

正向解析就是根据url地址访问页面

反向解析就是根据命名空间定向到url

根路由Project/urls.py里面

urlpatterns = [
    url(r'app/', include("app.urls"), namespace='app')
]

应用路由app/urls.py里面

urlpatterns = [
    url(r'^index/', views.my_view, name='index')
]

在模板里面使用反向解析:

<!--反向解析到应用app的index路由中-->
<a href="{% url "app:index" %}">