6.1 视图基础
2025-02-17
- 一般视图都会放在view.py文件中
- 简单的例子
from django.http import HttpResponse
def hello_django_bbs(request):
html = '<h1>hello django</h1>'
return HttpResponse(html)- 引用HttpResponse,作为视图的返回类型
- 视图函数声明,当前的函数名为hello_django_bbs,仅仅描述自身的用途
- 视图函数的第一个参数:是HttpRequest类型的对象,通常定义为request,Django没有要求,约定俗成
- 函数内部定义业务处理逻辑
- 视图最后返回一个HttpResponse对象,标记一次Web请求的结束在管理后台进行映射(my_bbs/my_bbs/urls.py)文件中配置URL到视图的映射
from django.contrib import admin
from django.urls import path
from post import views # 导入post的views视图函数
urlpatterns = [
path('admin/', admin.site.urls),
path('post/hello/',views.hello_django_bbs) # 注册post应用的视图函数
]
6.1.1 理解url.py文件的注释
my_bbs URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views # 针对基于函数的视图
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views # 针对基于类的视图
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf # 针对项目中存在多APP的场景
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))针对基于函数的视图
- 需要先将视图定义文件引入:import
- 利用path定义URL和视图的关系
- path函数传递两个参数,第一个参数定义URL的匹配规则,第二个参数定义映射的视图函数,都是必填项
针对基于类的视图
- 需要现将类对象引入
- 使用类似方法配置URL和视图类的关系
from other_app.views import Home path('',Home.as_view(), name='home')针对项目中存在多App的场景
- 利用include实现App与项目的解耦
- include将App的URL配置文件导入,就可以实现根据不同的App来分发请求,以实现每个App自己管理自己的视图与URL映射
from django.urls import include, path path('blog/', include('blog.urls'))上面的URL映射就可以改为
# my_bbs/post/urls.py from django.urls import path from post import views urlpatterns = [ path('hello/',views.hello_django_bbs) # 注册post应用的视图函数 ]# my_bbs/my_bbs/urls.py """my_bbs URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import include, path urlpatterns = [ path('post/',include('post.urls')) ]
得到的结果 
总结
在多应用App的项目中,在每个App下面创建urls.py文件,然后通过include注册到整个项目中,方便管理,而且结构清晰,便于维护
6.1.2 视图的请求与相应对象
- 请求与返回涉及两个对象
- HttpRequest:请求对象
- HttpResponse:相应对象
HttpRequest
- 此对象定义域:django/http/request.py文件中
- 请求的时候,Django会创建一个携带有请求元数据的HttpRequest对象,传递给视图函数的第一个参数
- 视图函数的处理逻辑:根据携带的元数据作出相应的动作
- 定义的属性
- method:
字符串类型的值,标识请求所使用的HTTP方法,如GET,POST,PUT等- 对于同一个URL,不管使用的是GET还是POST都会路由到同一个视图函数去处理
- 装饰器:
@csrf_exempt:POST方法和GET方法会获取相同的响应 - 指定请求方法需要使用:
@require_http_methods装饰器指定视图可以接受的方法 - 简化:提供了几个简单的装饰器指定可以接受的请求方法
- require_GET或require_POST等
- scheme:被
@property装饰的方法,返回字符串类型的值- 可以被当做属性直接调用:
request.scheme。标识请求的协议类型(http/https)
- 可以被当做属性直接调用:
- path:
字符串类型,返回当前请求页面的路径,但不包括协议类型或域名 - GET:
类字典对象,包括GET请求的所有参数,GET属性中的键和值都是字符串类型- 获取参数的方法有两种
request.GET['a']request.GET.get('b',0)
- GET的属性不是Python的字典类型,实际上是一个QuerySet(django.http.QueryDict)类型的实例,且是只读的,修改此数据,需要通过
copy方法获取副本并在副本上进行修改
- 获取参数的方法有两种
- POST:与GET类型
在POST方法上传文件时,文件相关的信息不会保存在POST中,而是保存在FILES属性中
- FILES
- META
- user
- method:
具体介绍查看文档:https://weread.qq.com/web/reader/6e4329007193f1e66e43129k341323f021e34173cb3824c
HttpResponse
具体介绍查看文档:https://weread.qq.com/web/reader/6e4329007193f1e66e43129k341323f021e34173cb3824c
- 常用的HttpResponse子类对象
- JsonResponse
- HttpResponseRedirect
- HttpResponseNotFound
6.1.3 基于类的视图
- 类视图的特点:可以利用不同的实例方法相应不同的HTTP请求方法(GET,POST)
- 可以利用面向对象的技术将代码分解为可重用的组件
简单的类视图定义
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
class FirstView(View):
html = '(%s) Hello Django BBS'
def get(self, request):
return HttpResponse(self.html % 'GET')
def post(self, request):
return HttpResponse(self.html % 'POST')
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(FirstView, self).dispatch(request, *args, **kwargs)FirstView 继承自 View,它是所有基于类视图的基类(View)
FirstView 定义了get和post方法
并且重写了父类的dispatch方法,dispatch根据HTTP类型实现请求分发:
- 如果是
GET请求,分发给get - 如果是
POST请求,分发给post
- 如果是
如果View中没有实现对应请求类型的方法,则会返回
HttpResponseNotAllowed类对象中定义的方法与普通的函数并不相同,所以,应用于函数的装饰器不能直接应用到类方法上,需要将它转换为类方法的装饰器。
- 在dispatch方法上添加了
@method_decorator - 此装饰器可以将函数装饰器转换为类方法装饰器,
csrf_exempt装饰器可以被用在类方法上
- 在dispatch方法上添加了
基于类的视图,在
urls.py中定义路由和视图的时候,需要用到View提供的as_view()方法完成URL的定义
具体代码
# my_bbs/post/views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
class FirstView(View):
html = '(%s) Hello Django BBS'
def get(self, request):
return HttpResponse(self.html % 'GET')
def post(self, request):
return HttpResponse(self.html % 'POST')
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(FirstView, self).dispatch(request, *args, **kwargs)# my_bbs/post/urls.py
from django.urls import path
from post.views import FirstView
urlpatterns = [
path('hello/',FirstView.as_view()) # 注册post应用的视图函数
]# my_bbs/my_bbs/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/',admin.site.urls),
path('post/',include('post.urls')) # 基于不同APP的urls
]结果
设置类属性
- FirstView中定义了一个类属性:html
- 常用的设计:将公用的部分放在类属性中,所有的方法都能看到
要修改类的属性由两种办法
- 使用Python语言的特性,实现子类并覆盖父类中的属性
class SecondsView(FirstView):
html = "Seconds ...."- 直接在配置URL的时候在
as_view方法中指定类属性,简单直接as_view中设置的类属性只在URL第一次导入时设置
path('second_hello_class/', FirstView.as_view(html="Seconds: (%s) Hello Django Seconds"))利用Mixin实现代码复用
- Mixin就是一个Python对象,但这个类不一定需要明确的语义,主要目的:实现代码的复用
- 一个视图类可以继承多个Mixin,但只能继承一个View,写法上通常会把Mixin写在View的前面
- 例子:将FirstView中重写的
dispatch方法放到Mixin里,并加入计时装饰器
# post/views.py
import time
def exec_time(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('%ss elapsed for %s' %(time.time() - start, func.__name__))
return res
return wrapper
class ExecTimeMixin(object):
@method_decorator(csrf_exempt)
@method_decorator(exec_time)
def dispatch(self, request, *args, **kwargs):
return super(ExecTimeMixin, self).dispatch(request, *args, **kwargs)
class FirstView(ExecTimeMixin, View):
html = '(%s) Hello Django BBS'
def get(self, request):
return HttpResponse(self.html % 'GET')
def post(self, request):
return HttpResponse(self.html % 'POST')- 使用了ExecTimeMixin重写dispatch之后,在FirstView中不需要在进行重写dispatch
- Django默认定义了一些Mixin简化开发:
- LoginRequiredMixin验证当前请求必须是登录用户,否则禁止访问或重定向到登录页

总结
- 基于函数的视图:FBV
- 基于类的视图:CBV
6.1.4 动态路由
- 视图函数也是普通的Python函数,所以可以定义额外的参数
- 对于视图函数中的额其他参数,如何传值
- 需要引入动态路由的概念(URL不是固定的,URL中包含了传递给视图的参数变量)
- 之前定义的视图映射的URL都可以被称作静态路由,即URL是固定的。
- 配置动态路由需要用到
path函数,区别在于:URL配置的语法上
使用path配置动态路由
- 简单接受其他参数的视图示例
def dynamic_hello(request, year, month, day):
html = '<h1>(%s) Hello</h1>'
return HttpResponse(html % ('%s-%s-%s' % (year, month, day)))- 为这些参数有具体的含义,所以,它们也应该有具体的类型
# urls.py
path('dynamic/<int:year>/<int:month>/<int:day>/', views.dynamic_hello)
之所以需要定义转换器,有两个原因:
- 第一是可以将捕获到的字符值转换为对应的类型
- 第二是对URL中传值的一种限制,避免视图处理出错
Django还提供了其他的转换器:str, slug, uuid, path 具体文档信息:https://weread.qq.com/web/reader/6e4329007193f1e66e43129k341323f021e34173cb3824c
自定义转换器
内置的转换器都定义于:
django/urls/converters.py它包含三个部分,也是每一个转换器都需要实现的三要素。
- (1)regex:字符串类型的类属性,根据属性名可以猜测,这是一个正则表达式,用于匹配URL对应位置的参数值。
- (2)to_python:参数value是从URL中匹配到的参数值,通过强转成对应的类型传递给视图函数。需要注意,这里可能因为强转抛出ValueError。
- (3)to_url:将一个Python类型的对象转换为字符串,to_python的反向操作
之前的例子:dynamic_hello存在的不足,月份的区间参数
自定义一个转换器捕获一个正确的月份
from django.urls.converters import IntConverter
class MonthConverter(InConverter):
regex = '0?[1-9]|1[0-2]'- 定义好转换器之后还需要注册才能使用, 在post应用的urls.py文件中注册
# urls.py
from django.urls import register_converter
from post.views import MonthConverter
register_converter(MonthConverter,'mth')
# 将转换器的类型名设定为mth,之后需要重新定义dynamic_hello视图的urls
path('dymmmic/<int:year>/<mth:month>/<int:day>/', views.dynamic_hello)

带默认参数的视图
# views.py
def dynamic_hello(request, year, month, day=15):
html = '<h1>(%s) Hello</h1>'
return HttpResponse(html % ('%s-%s-%s' % (year, month, day)))# urls.py
path('dymmmic/<int:year>/<mth:month>/', views.dynamic_hello),
path('dymmmic/<int:year>/<mth:month>/<int:day>/', views.dynamic_hello)
正则表达式
- 正则表达式命名分组的语法:
(?P<name>pattern)- name:分组名
- pattern:匹配模式
- 引用分组可以使用分组名,也可以使用分组编号
import re
r = re.compile('(?P<year>[0-9]{4})')
s = r.search('2022')
print(s.group('year'))
print(s.group(1))
# 2022
# 2022- path:定义于 django/urls/conf.py文件中
- 基于此,就可以通过re_path,使用命名分组来定义URL
- 使用re_path的理由是path方法和转换器都不能满足需求。其使用方法与path类似
- 例子: 将dynamic_hello的URL模式使用re_path重新定义
# urls.py
from django.urls import re_path
re_path('re_dynamic/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/(?P<day>[0-9]{2})', views.dynamic_hello)注意:当使用re_path的时候,在实际应用中,path自定义选择器无法使用的问题
6.1.5 给post应用添加视图
- 查看当前站点中所有的Topic列表信息
- 查看单个Topic的详细信息
- 给某一个Topic添加评论等
实现Topic列表视图
- 这里不涉及渲染,而是直接返回json格式的数据(JsonResponse)
创建存储业务处理逻辑的文件post/post_service.py,并实现构造Topic实例对象基本信息的业务逻辑
# post/post_service.py def build_topic_base_info(topic): """ 构造Topic基本信息 :param topic :return """ return { 'id':topic.id, 'title': topic.title, 'user': topic.user.username, 'created_time': topic.created_time.strftime('%y-%m-%d %H:%M:%S') }在post/views.py文件中定义Topic列表信息的视图函数
# post/views.py def topic_list_view(request): """ 话题列表 :param: request: :return: """ topic_qs = Topic.objects.all() result = { "count": topic_qs.count(), "info": [build_topic_base_info(topic) for topic in topic_qs] } return JsonResponse(result)添加视图路由
# post/urls.py from django.urls import path, register_converter from post.views import FirstView, MonthConverter from post import views from django.urls import re_path urlpatterns = [ # path('hello/',FirstView.as_view()), # 注册post应用的视图函数 # path('dynamic/<int:year>/<int:month>/<int:day>/', views.dynamic_hello), # path('dymmmic/<int:year>/<mth:month>/', views.dynamic_hello), # path('dymmmic/<int:year>/<mth:month>/<int:day>/', views.dynamic_hello), # re_path('re_dynamic/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/(?P<day>[0-9]{2})/', views.dynamic_hello), path('topiclist/', views.topic_list_view) ]
实现Topic实例对象信息视图
- 有了Topic的列表信息,根据列表信息中提供的id(Topic主键字段)查看每一个Topic的详细信息
- 思考思路
- 每一个Topic都有可能会有多个Comment,展示一个Topic的详细信息,应该吧Comment也展示出来,所需需要构造一个Comment信息的方法
# post/post_service.py
def build_comment_info(comment):
"""
构造Comment信息
:param comment:
:return:
"""
return {
'id': comment.id,
'content': comment.content,
'up': comment.up,
'down': comment.down,
'created_time': comment.created_time.strftime('%y-%m-%d %H:%M:%S'),
'last_modified': comment.last_modified.strftime('%y-%m-%d %H:%M:%S'),
}
from post.models import Comment
def build_topic_detail_info(topic):
"""
构造Topic详细信息
:oaram topic:
:return:
"""
comment_qs = Comment.objects.filter(topic=topic)
return {
"id": topic.id,
"title": topic.title,
"content": topic.content,
"user": topic.user.username,
"create_time": topic.created_time.strftime('%y-%m-%d %H:%M:%S'),
'last_modified': topic.last_modified.strftime('%y-%m-%d %H:%M:%S'),
'comments': [build_comment_info(comment) for comment in comment_qs]
}# post/views.py
# comment详细信息
def topic_detail_view(reqeust, topic_id):
"""
话题详细信息
:param request:
:param topic_id:
:return:
"""
result = {}
try:
result = build_topic_detail_info(Topic.objects.get(pk=topic_id))
except Topic.DoesNotExist:
pass
return JsonResponse(result)# post/urls.py
urlpatterns = [
path('topic/<int:topic_id>/',views.topic_detail_view)
]给Topic实例对象添加评论的视图
- 先写添加评论的业务逻辑
# post/post_service.py
def add_comment_to_topic(topic,content):
"""
给话题添加评论
:param topic:
:param content:
:return:
"""
# 这里简单地根据传递的Topic实例对象和评论内容(content)创建了Comment实例对象。注意,up和down可以使用默认值,不需要指定
return Comment.objects.create(topic=topic, content=content)- 视图函数
from post.post_service import add_comment_to_topic
def add_comment_to_topic_view(request):
"""
给话题添加评论
:param request:
:return:
"""
topic_id = int(request.POST.get('id',0))
content = request.POST.get('content','')
topic = None
try:
topic = Topic.objects.get(pk=topic_id)
except Topic.DoesNotExist:
pass
if topic and content:
return JsonResponse({'id':add_comment_to_topic(topic,content)})
return JsonResponse({})- 路由函数
urlpatterns = [
path('topic_comment/', views.add_comment_to_topic_view)
]