Django视图高级

Django视图高级

  • 1.限制请求method装饰器
  • 2.重定向详解
  • 3.HttpRequest对象讲解
  • 4.QueryDict的用法详解
  • 5.HttpResponse用法详解
  • 6.JsonResponse用法详解
  • 7.生成和下载csv文件
  • 8.大型CSV文件处理方式
  • 9.类视图讲解
  • 10.TemplateView讲解
  • 11.ListView视图讲解
  • 12.Paginator和Page类常用属性和方法
  • 13.手动实现普通分页效果
  • 14.手动实现普通分页算法
  • 15.给类视图添加装饰器
  • 16.状态码错误处理

限制请求method装饰器

常用的请求method:

  1. GET请求:GET请求一般用来向服务器索取数据,但不会向服务器提交数据,不会对服务器 的状态进行更改。比如向服务器获取某篇文章的详情。
  2. POST请求:POST请求一般是用来向服务器提交数据,会对服务器的状态进行更改。比如提 交一篇文章给服务器。

限制请求装饰器: Django 内置的视图装饰器可以给视图提供一些限制。比如这个视图只能通过 GET 的 method 访问等。

以下将介绍一些常用的内置视图装饰器

  1. django.http.decorators.http.require_http_methods :这个装饰器需要传递一个允许访问的方法的列表。比如只能通过 GET 的方式访问。那么示例代码如下:
from django.views.decorators.http import require_http_methods 
@require_http_methods(["GET"]) 
def my_view(request): 
    pass
  1. django.views.decorators.http.require_GET :这个装饰器相当于 是 require_http_methods([‘GET’]) 的简写形式,只允许使用 GET 的 method 来访问视图。示例代码如下
from django.views.decorators.http import require_GET 
@require_GET 
def my_view(request): 
    pass
  1. django.views.decorators.http.require_POST :这个装饰器相当于 是 require_http_methods([‘POST’]) 的简写形式,只允许使用 POST 的 method 来访问视图。 示例代码如下
from django.views.decorators.http import require_POST 
@require_POST 
def my_view(request): 
    pass
  1. django.views.decorators.http.require_safe :这个装饰器相当于是 require_http_methods([‘GET’,‘HEAD’]) 的简写形式
    只允许使用相对安全的方式来访问视 图。因为 GET 和 HEAD 不会对服务器产生增删改的行为。因此是一种相对安全的请求方式。 示例代码如下
from django.views.decorators.http import require_safe 
@require_safe 
def my_view(request): 
    pass

views.py

from django.shortcuts import render
from .models import Article
from django.views.decorators.http import require_http_methods,require_GET,require_POST,require_safe
from django.http import HttpResponse

# require_GET = @require_http_methods(['GET'])

# require_safe = @require_http_methods(['GET','HEAD'])

@require_GET
def index(request):
    # 首页返回所有的文章
    # 只能使用GET请求来访问这个视图函数
    articles = Article.objects.all()
    return render(request,'index.html',context={"articles":articles})

@require_http_methods(['POST','GET'])
def add_article(request):
    # 如果使用GET请求来访问这个视图函数,那么就返回一个添加文章的HTML页面
    # 如果使用POST请求来访问这个视图函数,那么就获取提交上来的数据,然后保存
    # 到数据库中
    if request.method == 'GET':
        return render(request,'add_article.html')
    else:
        title = request.POST.get("title")
        content = request.POST.get('content')
        Article.objects.create(title=title,content=content)
        return HttpResponse('success')

add_article.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    <table>
        <tbody>
            <tr>
                <td>标题:</td>
                <td><input type="text" name="title"></td>
            </tr>
            <tr>
                <td>内容:</td>
                <td>
                    <textarea name="content" id="" cols="30" rows="10"></textarea>
                </td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="提交"></td>
            </tr>
        </tbody>
    </table>
</form>
</body>
</html>

重定向详解

重定向分为永久性重定向和暂时性重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转 到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应 该给他重定向到登录页面。

永久性重定向:http的状态码是301,多用于旧网址被废弃了要转到一个新的网址确保用户的
访问,最经典的就是京东网站,你输入www.jingdong.com的时候,会被重定向到
www.jd.com,因为jingdong.com这个网址已经被废弃了,被改成jd.com,所以这种情况下应 该用永久重定向。

暂时性重定向:http的状态码是302,表示页面的暂时性跳转。比如访问一个需要权限的网
址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。

在 Django 中,重定向是使用

redirect(to, *args, permanent=False, **kwargs)

来实 的。
to 是一个 url , permanent 代表的是这个重定向是否是一个永久的重定向,默认 是 False 。关于重定向的使用。请看以下例子:

from django.shortcuts import reverse,redirect 
def profile(request): 
    if request.GET.get("username"):  
        return HttpResponse("%s,欢迎来到个人中心页面!") 
    else:
        return redirect(reverse("user:login"))

views.py

from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse

def index(request):
    # 如果没有登录,那么就重定向到注册页面
    # 如果在url中,传递了usename这个参数,那么就认为是登录了,否则就没有登录
    # /?username=xxx
    username = request.GET.get("username")
    if username:
        return HttpResponse("首页")
    else:
        return redirect(reverse('signup'))
        # return HttpResponse("注册页")

def signup(request):
    return HttpResponse("注册页")

HttpRequest对象讲解

Django在接收到http请求之后,会根据http请求携带的参数以及报文信息创建一个 WSGIRequest 对 象,并且作为视图函数第一个参数传给视图函数。也就是我们经常看到的 request 参数。在这个对象上我们可以找到客户端上传上来的所有信息。这个对象的完整路径是django.core.handlers.wsgi.WSGIRequest 。

WSGIRequest对象常用属性: WSGIRequest 对象上大部分的属性都是只读的。因为这些属性是从客户端上传上来的,没必要做任何的修改。以下将对一些常用的属性进行讲解:

  1. path :请求服务器的完整“路径”,但不包含域名和参数。比 如
    http://www.baidu.com/xxx/yyy/ ,那么 path 就是 /xxx/yyy/ 。
  2. method:代表当前请求的 http 方法。比如是 GET 还是 POST 。
  3. GET :一个django.http.request.QueryDict 对象。操作起来类似于字典。这个属性中包含了 所有以 ?xxx=xxx的方式上传上来的参数。
  4. POST :也是一个 django.http.request.QueryDict 对象。这个属性中包含了所有以
    POST 方 式上传上来的参数。
  5. FILES :也是一个 django.http.request.QueryDict对象。这个属性中包含了所有上传的文 件。
  6. COOKIES :一个标准的Python字典,包含所有的 cookie,键值对都是字符串类型。
  7. session :一个类似于字典的对象。用来操作服务器的 session 。
  8. META:存储的客户端发送上来的所有 header 信息。
  9. CONTENT_LENGTH :请求的正文的长度(是一个字符串)。
  10. CONTENT_TYPE :请求的正文的MIME类型。
  11. HTTP_ACCEPT :响应可接收的Content-Type。
  12. HTTP_ACCEPT_ENCODING :响应可接收的编码。
  13. HTTP_ACCEPT_LANGUAGE : 响应可接收的语言。
  14. HTTP_HOST :客户端发送的HOST值。
  15. HTTP_REFERER :在访问这个页面上一个页面的url。
  16. QUERY_STRING :单个字符串形式的查询字符串(未解析过的形式)。
  17. REMOTE_ADDR:客户端的IP地址。如果服务器使用了 nginx 做反向代理或者负载均衡,那么 这个值返回的是 127.0.0.1 ,这时候可以使用
    HTTP_X_FORWARDED_FOR 来获取,所以获 取 ip 地址的代码片段如下: if
    request.META.has_key(‘HTTP_X_FORWARDED_FOR’): ip =
    request.META[‘HTTP_X_FORWARDED_FOR’] 94 else:ip =
    request.META[‘REMOTE_ADDR’]
  18. REMOTE_HOST :客户端的主机名。
  19. REQUEST_METHOD :请求方法。一个字符串类似于 GET 或者 POST 。
  20. SERVER_NAME :服务器域名。
  21. SERVER_PORT :服务器端口号,是一个字符串类型。

WSGIRequest对象常用方法:

  1. is_secure() :是否是采用 https 协议。
  2. is_ajax() :是否采用ajax 发送的请求。原理就是判断请求头中是否存在 X-Requested- With:XMLHttpRequest 。
  3. get_host() :服务器的域名。如果在访问的时候还有端口号,那么会加上端口号。比 如 www.baidu.com:9000 。
  4. get_full_path() :返回完整的path。如果有查询字符串,还会加上查询字符串。比 如
    /music/bands/?print=True 。
  5. get_raw_uri() :获取请求的完整 url 。
from django.shortcuts import render
from django.http import HttpResponse
from django.core.handlers.wsgi import WSGIRequest

def index(request):
    # print(type(request))
    print(request.path)
    return HttpResponse('index')

def login(request):
    # print(request.path)
    # print(request.get_full_path())
    # /login/?username=xxx&password=111111
    # print(request.get_raw_uri())
    # for key,value in request.META.items():
    #     print("%s:%s"%(key,value))
    # print(request.get_host())
    # print(request.is_secure())
    print(request.is_ajax())
    return HttpResponse("login")

QueryDict的用法详解

我们平时用的 request.GET 和 request.POST 都是 QueryDict 对象,这个对象继承自 dict ,因此 用法跟dict 相差无几。其中用得比较多的是 get 方法和 getlist 方法。

  1. get 方法:用来获取指定 key 的值,如果没有这个 key ,那么会返回 None 。
  2. getlist 方法:如果浏览器上传上来的 key 对应的值有多个,那么就需要通过这个方法获 取
from django.shortcuts import render
from django.http import HttpResponse
from django.http.request import QueryDict
from django.views.decorators.http import require_http_methods

def index(request):
    # username = request.GET['username']
    username = request.GET.get('p',default=1)
    print(username)
    return HttpResponse('success')

@require_http_methods(['GET','POST'])
def add_article(request):
    if request.method == 'GET':
        return render(request,'add_article.html')
    else:
        title = request.POST.get("title")
        content = request.POST.get('content')
        tags = request.POST.getlist('tags')  # getlist获取所有的值
        print('title:',title)
        print('content:',content)
        print('tags:',tags)
        return HttpResponse("success")
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    <table>
        <tbody>
            <tr>
                <td>标题:</td>
                <td>
                    <input type="text" name="title">
                </td>
            </tr>
            <tr>
                <td>内容:</td>
                <td>
                    <input type="text" name="content">
                </td>
            </tr>
            <tr>
                <td>标签:</td>
                <td>
                    <label>
                        Python
                        <input type="checkbox" name="tags" value="python">
                    </label>
                    <label>
                        Django
                        <input type="checkbox" name="tags" value="django">
                    </label>
                </td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <input type="submit" value="提交">
                </td>
            </tr>
        </tbody>
    </table>
</form>
</body>
</html>

HttpResponse用法详解

HttpResponse对象 Django服务器接收到客户端发送过来的请求后,会将提交上来的这些数据封装成一 个 HttpRequest对象传给视图函数。那么视图函数在处理完相关的逻辑后,也需要返回一个响应 给浏览器。而这个响应,我们必须返回 HttpResponseBase或者他的子类的对象。 而 HttpResponse 则是 HttpResponseBase 用得最多的子类。那么接下来就来介绍一 下HttpResponse 及其子类。

常用属性:

  1. content:返回的内容。
  2. status_code:返回的HTTP响应状态码。
  3. content_type:返回的数据的MIME类型,默认为 text/html 。浏览器会根据这个属性,来显 示数据。如果是 text/html ,那么就会解析这个字符串,如果 text/plain ,那么就会显示一 个纯文本。常用的 Content-Type
    如下:
    text/html(默认的,html文件)
    text/plain(纯文本)
    text/css(css文件)
    text/javascript(js文件)
    multipart/form-data(文件提交)
    application/json(json传输)
    application/xml(xml文件)
  4. 设置请求头: response[‘X-Access-Token’] = ‘xxxx’ 。

常用方法:

  1. set_cookie:用来设置 cookie 信息。后面讲到授权的时候会着重讲到。
  2. delete_cookie:用来删除 cookie 信息。
  3. write: HttpResponse 是一个类似于文件的对象,可以用来写入数据到数据体(content)中。
#encoding: utf-8

from django.http import HttpResponse,JsonResponse
import json

def index(request):
    response = HttpResponse('

Smj

'
,content_type='text/plain;charset=utf-8') # response.status_code = 400 response['X-Token'] = 'z' # response.content = 'Smj' response.write('z') return response def jsonresponse_view(request): persons = [ { 'username': 'z', 'age': 18, 'height': 180 }, { 'username': 'z1', 'age': 20, 'height': 180 } ] # person_str = json.dumps(person) # response = HttpResponse(person_str,content_type='application/json') # return response response = JsonResponse(persons,safe=False) return response

JsonResponse用法详解

用来对象 dump 成 json 字符串,然后返回将 json 字符串封装成 Response 对象返回给浏览器。 并且他的Content-Type 是 application/json 。示例代码如下

from django.http import JsonResponse 
def index(request): 
     return JsonResponse({"username":"zhiliao","age":18})

默认情况下 JsonResponse 只能对字典进行 dump ,如果想要对非字典的数据进行 dump ,那么需 要给 JsonResponse 传递一个 safe=False 参数。示例代码如下:

from django.http import JsonResponse 
def index(request): persons = ['张三','李四','王五'] 
     return HttpResponse(persons)

以上代码会报错,应该在使用 HttpResponse 的时候,传入一个 safe=False 参数,示例代码如 下:

return HttpResponse(persons,safe=False)
#encoding: utf-8

from django.http import HttpResponse,JsonResponse
import json

def index(request):
    response = HttpResponse('

Smj

'
,content_type='text/plain;charset=utf-8') # response.status_code = 400 response['X-Token'] = 'z' # response.content = 'Smj' response.write('z') return response def jsonresponse_view(request): persons = [ { 'username': 'z', 'age': 18, 'height': 180 }, { 'username': 'z1', 'age': 20, 'height': 180 } ] # person_str = json.dumps(person) # response = HttpResponse(person_str,content_type='application/json') # return response response = JsonResponse(persons,safe=False) return response

生成和下载csv文件

生成CSV文件: 有时候我们做的网站,需要将一些数据,生成有一个 CSV 文件给浏览器,并且是作为附件的形式 下载下来。以下将讲解如何生成CSV 文件。

生成小的CSV文件: 这里将用一个生成小的 CSV 文件为例,来把生成 CSV 文件的技术要点讲到位。我们用 Python 内置的csv模块来处理 csv 文件,并且使用 HttpResponse 来将 csv 文件返回回去。示例代码如 下:

import csv
from django.http import HttpResponse 
def csv_view(request): 
   response = HttpResponse(content_type='text/csv') 
   response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'    
   writer = csv.writer(response) 
   writer.writerow(['username', 'age', 'height', 'weight'])   
   writer.writerow(['zhiliao', '18', '180', '110']) 
   return response

这里再来对每个部分的代码进行解释:

  1. 我们在初始化 HttpResponse 的时候,指定了 Content-Type 为 text/csv ,这将告诉浏览器, 这是一个 csv 格式的文件而不是一个 HTML 格式的文件,如果用默认值,默认值就是 html , 那么浏览器将把 csv 格式的文件按照 html格式输出,这肯定不是我们想要的。
  1. 第二个我们还在 response 中添加一个 Content-Disposition 头,这个东西是用来告诉浏览器 该如何处理这个文件,我们给这个头的值设置为 attachment; ,那么浏览器将不会对这个文 件进行显示,而是作为附件的形式下载,第二个 filename=“somefilename.csv” 是用来指定这 个 csv 文件的名字。
  1. 我们使用 csv 模块的 writer 方法,将相应的数据写入到 response 中。
#encoding: utf-8
from django.http import HttpResponse,StreamingHttpResponse
from django.template import loader
import csv

def index(request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = "attachment;filename='abc.csv'"
    # with open('xx.csv','w') as fp:
    #     csv.writer(fp)
    writer = csv.writer(response)
    writer.writerow(['username','age'])
    writer.writerow(['z',18])
    return response

def template_csv_view(request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = "attachment;filename='abc.csv'"
    context = {
        'rows': [
            ['username','age'],
            ['z',18],
        ]
    }
    template = loader.get_template('abc.txt')
    csv_template = template.render(context)
    response.content = csv_template
    return response

class Echo:
    def write(self,value):
        return value

def large_csv_view(request):
    response = StreamingHttpResponse(content_type='text/csv')
    response['Content-Disposition'] = "attachment;filename='large.csv'"
    rows = ("Row {},{}\n".format(row,row) for row in range(0,1000000))
    response.streaming_content = rows
    return response

    # response = HttpResponse(content_type='text/csv')
    # response['Content-Disposition'] = "attachment;filename='large.csv'"
    # writer = csv.writer(response)
    # for row in range(0,1000000):
    #     writer.writerow(['Row {}'.format(row),'{}'.format(row)])
    # return response

大型CSV文件处理方式

以上的例子是生成的一个小的 csv 文件,如果想要生成大型的 csv 文件,那么以上方式将有可能 会发生超时的情况(服务器要生成一个大型csv文件,需要的时间可能会超过浏览器默认的超时时 间)。这时候我们可以借助另外一个类,叫做
StreamingHttpResponse 对象,这个对象是将响应的 数据作为一个流返回给客户端,而不是作为一个整体返回。示例代码如下:

class Echo: 
""" 定义一个可以执行写操作的类,以后调用csv.writer的时候,就会执行这个方法 """ 
def write(self, value): 
    return value 
def large_csv(request):  
    rows = (["Row {}".format(idx), str(idx)] for idx in range(655360))   
    pseudo_buffer = Echo() 
    writer = csv.writer(pseudo_buffer) 
    response = StreamingHttpResponse((writer.writerow(row) for row in rows),content_typ e="text/csv") 
    response['Content-Disposition'] = 'attachment; filename="somefilename.csv"' 
    return response

这里我们构建了一个非常大的数据集 rows ,并且将其变成一个迭代器
然后因为 StreamingHttpResponse 的第一个参数只能是一个生成器,因此我们使用圆括 号 (writer.writerow(row) for row in rows)
并且因为我们要写的文件是 csv 格式的文件
因此需要调用 writer.writerow 将 row 变成一个 csv 格式的字符串
而调用 writer.writerow 又需要一个中间的容器
因此这里我们定义了一个非常简单的类 Echo
这个类只实现一个 write 方法
以后在执行 csv.writer(pseudo_buffer) 的时候
就会调用 Echo.writer 方法。
注意: StreamingHttpResponse 会启动一个进程来和客户端保持长连接,所以会很消耗资源。所以 如果不是特殊要求,尽量少用这种方法。

关于StreamingHttpResponse: 这个类是专门用来处理流数据的。使得在处理一些大型文件的时候,不会因为服务器处理时间过长而到时连接超时。这个类不是继承自 HttpResponse ,并且跟 HttpResponse 对比有以下几点区别:

  1. 这个类没有属性 content ,相反是 streaming_content 。
  2. 这个类的 streaming_content 必须是一个可以迭代的对象。
  3. 这个类没有 write 方法,如果给这个类的对象写入数据将会报错。 注意: StreamingHttpResponse 会启动一个进程来和客户端保持长连接,所以会很消耗资源。所以 如果不是特殊要求,尽量少用这种方法。

类视图讲解

在写视图的时候, Django 除了使用函数作为视图,也可以使用类作为视图。使用类视图可以使用 类的一些特性,比如继承等。
View:
django.views.generic.base.View是主要的类视图,所有的类视图都是继承自他。如果我们写自 己的类视图,也可以继承自他。然后再根据当前请求的 method 来实现不同的方法。比如这个视图只能使用 get 的方式来请求,那么就可以在这个类中定义 get(self,request,*args,**kwargs) 方法。以此类推,如果只需要实现 post 方法,那么就只需 要在类中实现 post(self,request,*args,**kwargs) 。示例代码如下

from django.views import View 
class BookDetailView(View): 
   def get(self,request,*args,**kwargs): 
       return render(request,'detail.html')

类视图写完后,还应该在 urls.py 中进行映射,映射的时候就需要调用 View 的类方 法 as_view() 来进行转换。示例代码如下:

urlpatterns = [ path("detail//",views.BookDetailView.as_view(),name='detail') ]

除了 get 方法, View 还支持以下方法

[‘get’,‘post’,‘put’,‘patch’,‘delete’,‘head’,‘options’,‘trace’]

如果用户访问了 View 中没有定义的方法。比如你的类视图只支持 get 方法,而出现了 post 方 法,那么就会把这个请求转发给 http_method_not_allowed(request,*args,**kwargs) 。示例代码如下

class AddBookView(View): 
def post(self,request,*args,**kwargs): 
    return HttpResponse("书籍添加成功!") 
def http_method_not_allowed(self, request, *args, **kwargs): 
     return HttpResponse("您当前采用的method是:%s,本视图只支持使用post请求!" % reque st.method)

urls.py 中的映射如下:

path("addbook/",views.AddBookView.as_view(),name='add_book')

如果你在浏览器中访问 addbook/ ,因为浏览器访问采用的是 get 方法,而 addbook 只支 持 post 方法,因此以上视图会返回您当前采用的 method 是: GET ,本视图只支持使用 post 请 求!。 其实不管是 get 请求还是 post 请求,都会dispatch(request,*args,**kwargs) 方法,所以如 果实现这个方法,将能够对所有请求都处理到。

from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
from .models import Article
from django.views.generic import ListView,View
from django.core.paginator import Paginator,Page
from django.utils.decorators import method_decorator

def add_article(request):
    articles = []
    for x in range(0,102):
        article = Article(title='标题:%s'%x,content='内容:%s'%x)
        articles.append(article)
    Article.objects.bulk_create(articles)
    return HttpResponse('article added successfully')

class ArticleListView(ListView):
    model = Article
    template_name = 'article_list1.html'
    context_object_name = 'articles'
    paginate_by = 10
    ordering = 'create_time'
    page_kwarg = 'p'

    def get_context_data(self, **kwargs):
        context = super(ArticleListView, self).get_context_data(*kwargs)
        context['username'] = 'zhiliao'
        paginator = context.get('paginator')
        page_obj = context.get('page_obj')
        pagination_data = self.get_pagination_data(paginator,page_obj,3)
        context.update(pagination_data)
        return context

    def get_pagination_data(self,paginator,page_obj,around_count=2):
        current_page = page_obj.number
        num_pages = paginator.num_pages

        left_has_more = False
        right_has_more = False

        if current_page <= around_count + 2:
            left_pages = range(1,current_page)
        else:
            left_has_more = True
            left_pages = range(current_page-around_count,current_page)

        if current_page >= num_pages - around_count - 1:
            right_pages = range(current_page+1,num_pages+1)
        else:
            right_has_more = True
            right_pages = range(current_page+1,current_page+around_count+1)

        return {
            'left_pages': left_pages,
            'right_pages': right_pages,
            'current_page': current_page,
            'left_has_more': left_has_more,
            'right_has_more': right_has_more,
            'num_pages': num_pages
        }

    # def get_queryset(self):
    #     return Article.objects.filter(id__lte=9)

def login_required(func):
    def wrapper(request,*args,**kwargs):
        # ?username=zhiliao
        username = request.GET.get('username')
        if username:
            return func(request,*args,**kwargs)
        else:
            return redirect(reverse('front:login'))
    return wrapper


@method_decorator([login_required],name='dispatch')
class ProfileView(View):
    def get(self,request):
        return HttpResponse("个人中心界面")

    # @method_decorator(login_required)
    # def dispatch(self, request, *args, **kwargs):
    #     return super(ProfileView, self).dispatch(request,*args,**kwargs)


def login(request):
    return HttpResponse('login')

TemplateView讲解

TemplateView: django.views.generic.base.TemplateView,这个类视图是专门用来返回模版的。在这个类中, 有两个属性是经常需要用到的
一个是 template_name ,这个属性是用来存储模版的路 径, TemplateView 会自动的渲染这个变量指向的模版。
另外一个是 get_context_data ,这个方 法是用来返回上下文数据的,也就是在给模版传的参数的。示例代码如下:

 from django.views.generic.base 
 import TemplateView 
 class HomePageView(TemplateView): 
       template_name = "home.html" 
       def get_context_data(self, **kwargs): 
           context = super().get_context_data(**kwargs) 
           context['username'] = "黄" 
           return context 

在 urls.py 中的映射代码如下:

 from django.urls import path 
 from myapp.views import HomePageView 
 urlpatterns = [ path('', HomePageView.as_view(), name='home'), ]

如果在模版中不需要传递任何参数,那么可以直接只在 urls.py 中使用 TemplateView 来渲染模 版。示例代码如下:

from django.urls import path 
from django.views.generic import 
TemplateView urlpatterns = 
[
   path('about/', TemplateView.as_view(template_name="about.html")), 
]
from django.urls import path,include
from . import views
from django.views.generic import TemplateView

urlpatterns = [
    path('', views.index,name='index'),
    path('book/', views.BookListView.as_view(),name='book_list'),
    path('add_book/', views.AddBookView.as_view(),name='add_book'),
    path('detail//',views.BookDetailView.as_view(),name='detail'),
    # 以后如果渲染的这个模板不需要传递任何的参数,那么建议在urls中使用TemplateView
    # path('about/',TemplateView.as_view(template_name='about.html'))
    path('about/',views.AboutView.as_view()),
    path('article/',include('front.urls'))
]

#encoding: utf-8
from django.http import HttpResponse
from django.shortcuts import render
from django.views.generic import View,TemplateView

def index(request):
    return HttpResponse('index')

class BookListView(View):
    def get(self,request,*args,**kwargs):
        return HttpResponse("book list view")

class AddBookView(View):
    def get(self,request,*args,**kwargs):
        return render(request,'add_book.html')

    def post(self,request,*args,**kwargs):
        book_name = request.POST.get("name")
        book_author = request.POST.get("author")
        print("name:{},author:{}".format(book_name,book_author))
        return HttpResponse("success")

class BookDetailView(View):
    def get(self,request,book_id):
        print('图书的id是:%s'%book_id)
        return HttpResponse("success")
    
    def dispatch(self, request, *args, **kwargs):
        print('dispatch')
        return super(BookDetailView, self).dispatch(request,*args,**kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        return HttpResponse("不支持GET以外的其他请求!")

class AboutView(TemplateView):
    template_name = 'about.html'

    def get_context_data(self, **kwargs):
        context = {"phone":'0731-888888'}
        return context
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    这是关于我们的页面{{ phone }}
</body>
</html>

ListView视图讲解

在网站开发中,经常会出现需要列出某个表中的一些数据作为列表展示出来。比如文章列表,图书 列表等等。在 Django 中可以使用 ListView 来帮我们快速实现这种需求。示例代码如下:

 class ArticleListView(ListView): 
     model = Article 
     template_name = 'article_list.html' 
     paginate_by = 10 
     context_object_name = 'articles' 
     ordering = 'create_time' 
     page_kwarg = 'page' 
     def get_context_data(self, **kwargs): 
         context = super(ArticleListView, self).get_context_data(**kwargs)    
         print(context) 
         return context 
     def get_queryset(self): 
         return Article.objects.filter(id__lte=89)

对以上代码进行解释:

  1. 首先 ArticleListView 是继承自 ListView 。
  2. model :重写 model 类属性,指定这个列表是给哪个模型的。
  3. template_name :指定这个列表的模板。
  4. paginate_by :指定这个列表一页中展示多少条数据。
  5. context_object_name :指定这个列表模型在模板中的参数名称。
  6. ordering :指定这个列表的排序方式。
  7. page_kwarg :获取第几页的数据的参数名称。默认是 page 。
  8. get_context_data :获取上下文的数据。
  9. get_queryset :如果你提取数据的时候,并不是要把所有数据都返回,那么你可以重写这个方法。将一些不需要展示的数据给过滤掉。
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
from .models import Article
from django.views.generic import ListView,View
from django.core.paginator import Paginator,Page
from django.utils.decorators import method_decorator

def add_article(request):
    articles = []
    for x in range(0,102):
        article = Article(title='标题:%s'%x,content='内容:%s'%x)
        articles.append(article)
    Article.objects.bulk_create(articles)
    return HttpResponse('article added successfully')

class ArticleListView(ListView):
    model = Article
    template_name = 'article_list1.html'
    context_object_name = 'articles'
    paginate_by = 10
    ordering = 'create_time'
    page_kwarg = 'p'

    def get_context_data(self, **kwargs):
        context = super(ArticleListView, self).get_context_data(*kwargs)
        # 继承父类的参数,否则可能导致某些参数无法使用
        context['username'] = 'zhiliao'
        paginator = context.get('paginator')
        page_obj = context.get('page_obj')
        pagination_data = self.get_pagination_data(paginator,page_obj,3)
        context.update(pagination_data)
        return context

    def get_pagination_data(self,paginator,page_obj,around_count=2):
        current_page = page_obj.number
        num_pages = paginator.num_pages

        left_has_more = False
        right_has_more = False

        if current_page <= around_count + 2:
            left_pages = range(1,current_page)
        else:
            left_has_more = True
            left_pages = range(current_page-around_count,current_page)

        if current_page >= num_pages - around_count - 1:
            right_pages = range(current_page+1,num_pages+1)
        else:
            right_has_more = True
            right_pages = range(current_page+1,current_page+around_count+1)

        return {
            'left_pages': left_pages,
            'right_pages': right_pages,
            'current_page': current_page,
            'left_has_more': left_has_more,
            'right_has_more': right_has_more,
            'num_pages': num_pages
        }

    # def get_queryset(self):
    #     return Article.objects.filter(id__lte=9)

def login_required(func):
    def wrapper(request,*args,**kwargs):
        # ?username=zhiliao
        username = request.GET.get('username')
        if username:
            return func(request,*args,**kwargs)
        else:
            return redirect(reverse('front:login'))
    return wrapper


@method_decorator([login_required],name='dispatch')
class ProfileView(View):
    def get(self,request):
        return HttpResponse("个人中心界面")

    # @method_decorator(login_required)
    # def dispatch(self, request, *args, **kwargs):
    #     return super(ProfileView, self).dispatch(request,*args,**kwargs)


def login(request):
    return HttpResponse('login')
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
    <ul>
        {% for article in articles %}
            <li>{{ article.title }}</li>
        {% endfor %}
        <ul class="pagination">
{#             上一页#}
            {% if page_obj.has_previous %}
                <li><a href="{% url 'front:article_list' %}?p={{ page_obj.previous_page_number }}">上一页</a></li>
            {% else %}
                <li class="disabled"><a href="javascript:void(0);">上一页</a></li>
            {% endif %}

{#             中间的页码#}
            {% for page in paginator.page_range %}
                {% if page == page_obj.number %}
                    <li class="active"><a href="javascript:void(0);">{{ page }}</a></li>
                {% else %}
                    <li><a href="{% url 'front:article_list' %}?p={{ page }}">{{ page }}</a></li>
                {% endif %}
            {% endfor %}

{#             下一页#}
            {% if page_obj.has_next %}
                <li><a href="{% url 'front:article_list' %}?p={{ page_obj.next_page_number }}">下一页</a></li>
            {% else %}
                <li class="disabled"><a href="javascript:void(0);">下一页</a></li>
            {% endif %}

        </ul>
    </ul>
</body>
</html>

Paginator和Page类常用属性和方法

Paginator和Page类: Paginator 和 Page 类都是用来做分页的。
他们在 Django 中的路径为
django.core.paginator.Paginator 和 django.core.paginator.Page
以下对这两个类的常用属性和方法做解释:

Paginator常用属性和方法:

  1. count :总共有多少条数据。
  2. num_pages :总共有多少页。
  3. page_range :页面的区间。比如有三页,那么就 range(1,4) 。

Page常用属性和方法:

  1. has_next :是否还有下一页。
  2. has_previous :是否还有上一页。
  3. next_page_number :下一页的页码。
  4. previous_page_number :上一页的页码。
  5. number :当前页。
  6. start_index :当前这一页的第一条数据的索引值。
  7. end_index :当前这一页的最后一条数据的索引值。

手动实现普通分页效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
    <ul>
        {% for article in articles %}
            <li>{{ article.title }}</li>
        {% endfor %}
        <ul class="pagination">
{#             上一页#}
            {% if page_obj.has_previous %}
                <li><a href="{% url 'front:article_list' %}?p={{ page_obj.previous_page_number }}">上一页</a></li>
            {% else %}
                <li class="disabled"><a href="javascript:void(0);">上一页</a></li>
            {% endif %}

{#             中间的页码#}
            {% for page in paginator.page_range %}
                {% if page == page_obj.number %}
                    <li class="active"><a href="javascript:void(0);">{{ page }}</a></li>
                {% else %}
                    <li><a href="{% url 'front:article_list' %}?p={{ page }}">{{ page }}</a></li>
                {% endif %}
            {% endfor %}

{#             下一页#}
            {% if page_obj.has_next %}
                <li><a href="{% url 'front:article_list' %}?p={{ page_obj.next_page_number }}">下一页</a></li>
            {% else %}
                <li class="disabled"><a href="javascript:void(0);">下一页</a></li>
            {% endif %}

        </ul>
    </ul>
</body>
</html>
 def get_pagination_data(self,paginator,page_obj,around_count=2):
        current_page = page_obj.number
        num_pages = paginator.num_pages

        left_has_more = False
        right_has_more = False

        if current_page <= around_count + 2:
            left_pages = range(1,current_page)
        else:
            left_has_more = True
            left_pages = range(current_page-around_count,current_page)

        if current_page >= num_pages - around_count - 1:
            right_pages = range(current_page+1,num_pages+1)
        else:
            right_has_more = True
            right_pages = range(current_page+1,current_page+around_count+1)

        return {
            'left_pages': left_pages,
            'right_pages': right_pages,
            'current_page': current_page,
            'left_has_more': left_has_more,
            'right_has_more': right_has_more,
            'num_pages': num_pages
        }

手动实现普通分页算法

 def get_pagination_data(self,paginator,page_obj,around_count=2):
        current_page = page_obj.number
        num_pages = paginator.num_pages

        left_has_more = False
        right_has_more = False

        if current_page <= around_count + 2:
            left_pages = range(1,current_page)
        else:
            left_has_more = True
            left_pages = range(current_page-around_count,current_page)

        if current_page >= num_pages - around_count - 1:
            right_pages = range(current_page+1,num_pages+1)
        else:
            right_has_more = True
            right_pages = range(current_page+1,current_page+around_count+1)

        return {
            'left_pages': left_pages,
            'right_pages': right_pages,
            'current_page': current_page,
            'left_has_more': left_has_more,
            'right_has_more': right_has_more,
            'num_pages': num_pages
        }
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
    <ul>
        {% for article in articles %}
            <li>{{ article.title }}</li>
        {% endfor %}
        <ul class="pagination">
{#             上一页#}
            {% if page_obj.has_previous %}
                <li><a href="{% url 'front:article_list' %}?p={{ page_obj.previous_page_number }}">上一页</a></li>
            {% else %}
                <li class="disabled"><a href="javascript:void(0);">上一页</a></li>
            {% endif %}

            {% if left_has_more %}
                <li><a href="{% url 'front:article_list' %}?p=1">1</a></li>
                <li><a href="javascript:void(0);">...</a></li>
            {% endif %}

            {# 左边的页码 #}
            {% for left_page in left_pages %}
                <li><a href="{% url 'front:article_list' %}?p={{ left_page }}">{{ left_page }}</a></li>
            {% endfor %}

            {# 当前的页面 #}
            <li class="active"><a href="{% url 'front:article_list' %}?p={{ current_page }}">{{ current_page }}</a></li>

            {# 右边的页码 #}
            {% for right_page in right_pages %}
                <li><a href="{% url 'front:article_list' %}?p={{ right_page }}">{{ right_page }}</a></li>
            {% endfor %}

            {% if right_has_more %}
                <li><a href="javascript:void(0);">...</a></li>
                <li><a href="{% url 'front:article_list' %}?p={{ num_pages }}">{{ num_pages }}</a></li>
            {% endif %}

{#             下一页#}
            {% if page_obj.has_next %}
                <li><a href="{% url 'front:article_list' %}?p={{ page_obj.next_page_number }}">下一页</a></li>
            {% else %}
                <li class="disabled"><a href="javascript:void(0);">下一页</a></li>
            {% endif %}

        </ul>
    </ul>
</body>
</html>

给类视图添加装饰器

在开发中,有时候需要给一些视图添加装饰器。如果用函数视图那么非常简单,只要在函数的上面 写上装饰器就可以了。但是如果想要给类添加装饰器,那么可以通过以下两种方式来实现:
装饰dispatch方法:

 from django.utils.decorators import method_decorator 
 def login_required(func): 
     def wrapper(request,*args,**kwargs): 
         if request.GET.get("username"): 
             return func(request,*args,**kwargs) 
         else:
             return redirect(reverse('index')) 
     return wrapper 
     
class IndexView(View): 
         def get(self,request,*args,**kwargs): 
             return HttpResponse("index") 
         @method_decorator(login_required) 
         def dispatch(self, request, *args, **kwargs): 
             super(IndexView, self).dispatch(request,*args,**kwargs) 

直接装饰在整个类上:

from django.utils.decorators import method_decorator 
def login_required(func): 
    def wrapper(request,*args,**kwargs): 
        if request.GET.get("username"): 
            return func(request,*args,**kwargs) 
        else:
            return redirect(reverse('login')) 
    return wrapper 
    
@method_decorator(login_required,name='dispatch')  # name就指定了是下面的dispatch方法
class IndexView(View): 
      def get(self,request,*args,**kwargs): 
          return HttpResponse("index") 
      def dispatch(self, request, *args, **kwargs): 
          super(IndexView, self).dispatch(request,*args,**kwargs)

状态码错误处理

错误处理 在一些网站开发中。经常会需要捕获一些错误,然后将这些错误返回比较优美的界面,或者是将这个错误的请求做一些日志保存。。

常用的错误码:
404 :服务器没有指定的url。
403 :没有权限访问相关的数据。
405 :请求的 method 错误。
400 : bad request ,请求的参数错误。
500 :服务器内部错误,一般是代码出bug了。
502 :一般部署的时候见得比较多,一般是 nginx 启动了,然后 uwsgi 有问题。

自定义错误模板: 在碰到比如 404 , 500 错误的时候,想要返回自己定义的模板。那么可以直接在 templates 文件 夹下创建相应错误代码的 html 模板文件。那么以后在发生相应错误后,会将指定的模板返回回去。

错误处理的解决方案:

对于 404 和 500 这种自动抛出的错误。我们可以直接在 templates 文件夹下新建相应错误代码的 模板文件。而对于其他的错误,我们可以专门定义一个 app ,用来处理这些错误

#encoding: utf-8
from django.http import HttpResponse
from django.shortcuts import render

def view_405(request):
    return render(request,'errors/405.html',status=405)

def view_403(request):
    return render(request,'errors/403.html',status=403)
from django.urls import path
from . import views

app_name = 'errors'

urlpatterns = [
    path('405.html',views.view_405,name='405'),
    path('403.html',views.view_403,name='403'),
]
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    您访问的页面已经到火星上去了~~
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    服务器出现了一个小虫子,工程师们正在紧急捕捉...
</body>
</html>

你可能感兴趣的:(Django视图高级)