Django(二) 路由和视图

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ayhan_huang/article/details/78036501

路由定义

路由是客户端访问的url路径与视图函数间的一一映射关系。Django中的路由关系在urls.py文件中,基本格式如下:

urlpatterns = [
    url(regex,view, kwargs=None, name=None),
]

参数说明:
regex: 匹配url路径的正则表达式,比如r'^login/', 匹配以login/开头的路径,当你在浏览器地址栏输入http://127.0.0.1/login/时,会调用login映射的视图函数来处理请求。几点提醒,
1. 为了避免转义,正则的前面最好加上r
2. 路径前默认都有前导的反斜杠/,我们在正则中就不需要再加了,比如r'login/'就可以,而不用r'^/login/'
3. 如果我们访问127.0.0.1:8000/loginlogin后未加反斜杠/,将会重定向至127.0.0.1:8000/login/,这时因为默认设置APPEND_SLASH = True。另外,任何未在setting.py文件中定义的配置都由django.conf.global_settings 提供
4. 在进行url路径匹配时,只要匹配成功一个,就不在向下匹配。
view: 映射的视图函数
name: 可以为视图函数定义别名,这个稍后介绍。
配置根目录:
我们必须为项目配置根目录,即当用户访问的url不带路径时,必须有相应的视图函数处理并返回响应,而不是错误页面。加入下面的路由:
url(r'^$', view) 当匹配路径为空,调用指定的视图函数处理。

路径分组和视图函数传参

分组是正则中的操作,对正则表达式加括号,就可以实现路径分组。分组的目的是为了给视图函数传参。这里有两种分组方式,通过位置传参的简单分组,和通过关键字传参的命名分组。我们通过下面的栗子来介绍这两种分组方式。
1.新建一个项目learn, 并创建应用calc, 在calc的视图函数中定义一个计算加法的函数add:

from django.http import HttpResponse
# HttpResponse用于直接返回字符串响应
def add(request,a,b):
    # add 函数除了第一个请求对象参数,还从urls.py中的路由接收另外两个参数用于求和
    c = int(a) + int(b)
    return HttpResponse('{a} + {b} = {c}'.format(a=a, b=b, c=str(c)))

2.在项目的urls.py中编辑路由映射:
2.1 简单分组:

from django.conf.urls import url
from django.contrib import admin
from calc import views as calc_views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^add/(\d+)/(\d+)/', calc_views.add),
    # 简单分组:通过圆括号匹配两组数字,作为参数传给视图函数add
]

2.2 命名分组:

from django.conf.urls import url
from django.contrib import admin
from calc import views as calc_views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^add/(?P<c>\d+)/(?P<b>\d+)/', calc_views.add),
    # 通过(?P<...>)命名分组, 将分组名a和b传给视图函数add中的关键字参数a和b,
    # 分组名必须和关键字匹配:如果分组名为x,y 而视图的关键字是a,b,就会报错。
]

3.在浏览器地址栏输入http://127.0.0.1:8000/add/4/5/,你将看到以下结果:
这里写图片描述

4.另外,如果不用分组,可以通过url中的query string查询字符串来提取数字,进行计算。
因此url需要使用查询字符串形式:
http://127.0.0.1:8000/add/?a=4&b=5
urls路由如下:

urlpatterns = [
    url(r'^add/$', calc_views.add, name='add'),
]

视图函数中提取查询字符串:

def add(request):
    a = request.GET.get('a') # get请求中查询字符串以字典键值对的方式封装在request对象中
    b = request.GET.get('b')
    c = int(a) + int(b)
    return HttpResponse('{a} + {b} = {c}'.format(a=a, b=b, c=str(c)))

别名

通过别名还原真实url是很有帮助的,尤其是urls.py路由中的url路径修改后,如果不使用别名,我们就不得不在所有直接使用了url路径的地方一一修改。
比如,在模板的表单提交中,如果action的路径写死了,一旦我们修改了urls.py中的路径,那么客户端的表单提交路径就是无效的,除非我们手动修改action路径。

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

通过使用别名,可以避免这种情况发生。
1.定义别名

urlpatterns = [
    url(r'^login/$', blog_views.login, name='login')
    # name='login', 为这条路由起个别名'login', 通过别名可以得到对应的网址
]

2.使用别名:{% url 'name' %}

<form action="{% url 'name' %}" method="post">

还有以一种情况是,用户收藏了我们的旧网址127.0.0.1/index/,现在网站的网址变了127.0.0.1/index_new/,用户如何通过旧网址找到我们的网站。
解决方案是为旧网址写一个跳转函数:
1.编辑视图函数

from django.http import HttpResponseRedirect
from django.urls import reverse

def to_new_index(request):
    return HttpResponseRedirect(reverse(index))
    # reverse()函数,可以接收一个视图函数,并返回该视图函数对应的url
    # 返回重定向响应

def index(request): # 
    return render(request, 'index.html')

2.编辑路由

from django.conf.urls import url
from blog import views as blog_views
# 从blog应用中导入视图函数

urlpatterns = [
    url(r'^index_new/$', blog_views.index),
    url(r'^index/$',blog_views.to_new_index),
    # 如果url路径是/index,那么调用视图函数to_new_index, 跳转至新的url
]

3.这样我们在浏览中输入127.0.0.1/index/,将自动跳转到127.0.0.1/index_new/

路由分发

前面我们都是在项目文件夹的urls.py中通过from app import views这种形式导入视图函数,然后定义具体路由。应该知道的是,项目目录中的urls.py是全局路由,我们要避免将具体应用的路由写在全局路由中,这样会造成代码耦合,而且:1. 当应用越来越多,全局路由将越来越大,不利于维护;2.一旦某一条路由崩溃了,将导致整个路由都崩溃。正确的方式是在每个应用自己的文件夹中定义自己的路由,全局路由只负责做分发。下面我们看一下如何实现。
1.在应用文件夹中新建urls.py,编辑路由规则

from django.conf.urls import url
from . import views
# 从当前文件夹导入视图函数

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

2.在全局路由中作分发

from django.conf.urls import url, include   # 导入include函数
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # include创建路由分发
    url(r'^polls/', include('polls.urls')),
    # 客户端在访问时,只要是属于polls应用的路径,就会被分发到polls应用下的路由
]

注意:
1. 路由分发不能加$结尾约束, 否则直接终止匹配了,造成分发失败。
2. 如果要作一个空路径的默认路由分发,可以通过以下方式:

from django.conf.urls import url, include
from api import urls as api_urls
from web import urls as web_urls

urlpatterns = [
    url(r'^api/', include(api_urls)), # api开头的分发至api应用的路由
    url(r'^', include(web_urls))  # 为空,分发至web应用的路由
    # 路由分发不能加$, 否则终止了
]


这样,我们在浏览器中输入url:http://127.0.0.1:8000/polls/index/,全局路由匹配到是访问polls应用的,将这条url分发到polls下的urls.py来处理。
后续创建了新的应用,就可以定义各自的路由,然后在全局路由中新增一条分发就可以了。

反向路由

reverse

reverse函数可以接收路由中定义的别名或视图函数名,反向生成该视图的url;

导入:from django.shortcuts import reverse

  1. 通过别名反向生成:

    urlpatterns = [
        url(r'^index/$',views.index, name='index'),
    ]
    
    url = reverse('index')
  2. 通过视图函数反向生成:

    def index(request): 
        return render(request, 'index.html')
    
    url = reverse(index)
  3. 接收简单分组的参数,args=()

    urlpatterns = [
        url(r'^add/(\d+)/(\d+)/', view.add, name='add'),
    ]
    
    url = reverse(url = reverse('add', args=(2, 9)))
  4. 接收命名分组的参数,kwargs=()

    urlpatterns = [
        url(r'^add/(?P<a>\d+)/(?P<b>\d+)/', view.add, name='add'),
    ]
    
    url = reverse(url = reverse('add', kwargs=(a=2, b=9)))

include路由分发和namespace

在项目的urls.py中经常通过include函数作路由分发,查看源码可以发现include函数的本质返回了一个元组:(urlconf_module, app_name, namespace),urlconf_module是一个个路由组成的列表;还有有一个值是namespace名称空间,它的作用是在别名重复时,作区分用的。因此,如果有名称空间,那么反向路由时需要加名称空间,比如namespace=’xxx’, name=’yyy’,那么通过reverse函数反向路由时,格式为:reverse('xxx:yyy')。如果嵌套了多层名称空间,需要由外到里用冒号:连接起来。

模板中:{% url %}

在模板中,我们可以通过{% url %}配合别名来反向生成url:

  1. 一般形式,通过别名生成:

    {% url 'name' %}
  2. 接收简单分组参数:

    {% url 'name' arg1, arg2 %}
  3. 接收命名分组的参数:

    {% url 'name' arg1=xx, arg2=xx %}

视图函数

视图函数是应用的业务处理逻辑。它接收wsgi封装的客户端请求对象,返回响应。

request请求

request请求对象是视图函数必须接收的第一个参数。通过调用request的属性/方法,可以获取请求信息:
- request.method 获取请求方法(GET/POST)
- request.GET 字典的形式存放GET请求的数据,通过val = request.GET.get(key)可以获取具体的值
- request.POST 字典形式存放POST请求的数据,通过val = request.GET.get(key)可以获取具体的值
- request.META 获取请求的所有元信息,比如获取访问前的地址(执行完某些操作,比如登录后,可利用该地址跳转回去):

next_url = request.META.get('HTTP_REFERER', '/')
  • request.FILE 获取上传的文件

响应方法

  • render(request, 'template', {key: value, ... }) 渲染模板,返回HttpResponse响应。{key:value}称为context上下文对象,key对应模板中的变量名,value对应视图中的对象,渲染时,将视图函数中的对象嵌入模板中。
  • HttpResponse(' ') 字符串对象
  • redirect('path') 重定向,接收的参数是路径。会在响应头中加一个location键,客户端拿到这个键对应的值,即url, 对url发起get请求。

注意redirect和render的区别:
1. 客户端收到redirect重定向响应后,会对重定向的url发起get请求,这时会重走路由映射,调用视图函数。
2. 而render是直接渲染html模板,生成html页面,返回给客户端。

FBV $ CBV

简介

路由分为两种:FBV & CBV

一般我们写的视图函数属于FBV, 请求过来执行视图函数,函数式编程;

而CBV,请求过来,执行的是views.py 中定义的类,面型对象的编程方式;

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index.html$', views.IndexView.as_view()), # CBV
    # 早先的web服务就是提供一个静态HTML页面,不涉及ORM和模板渲染,这里写成这种形式,'^index.html$' 是url伪静态
]

在CBV路由中,要特别注意类名后加as_view()

自定义CBV:

from django.views import View
class IndexView(View): # 自定义的类继承View
    def get(self,request,*args,**kwargs):
        return render(request, 'index.html') 
    def post(self,request,*args,**kwargs):
        return HttpResponse('ok')

请求经过路由到达自定义类后,根据请求方法,来执行get或post函数,这一点是通过反射完成的。查看源码可以看到所有可以反射的方法:

http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 如果是通过Ajax发送请求,那么支持以上所以方法

反射操作是在View类中的dispatch方法中完成的:

def dispatch(self, request, *args, **kwargs):
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

从以上源码可以看到,请求方法执行结果最终是作为dispatch方法的返回值,因此上面我们自定义的CBA可以写成如下形式:

from django.views import View
class IndexView(View): # 自定义的类继承View
    # 重用父类中的dispatch方法,并加入自定义逻辑,比如用户验证
    def dispatch(self, request, *args, **kwargs):
        if not request.session.get('user-info'):
            return redirect('/login.html')
        return super(IndexView, self).dispatch(request,*args,**kwargs)

    def get(self,request,*args,**kwargs):
        return render(request, 'index.html')

    def post(self,request,*args,**kwargs):
        return HttpResponse('ok')

CBV中使用装饰器

导入:

from django.utils.decorators import method_decorator

三种加装饰的方式:

假设定义了一个装饰器deco:

def deco(func):
    def wrapper(*args,**kwargs):
        print('do something')
        return func(*args,**kwargs)
    return wrapper
  1. 加到请求方法上:

    class LoginView(View):
       @method_decorator(deco)
       def get(self,request):
           return render(request,'login.html')
  2. 加到dispatch上:

    class LoginView(View):
       @method_decorator(deco)
       def dispatch(self, request, *args, **kwargs):
           return super(LoginView,self).dispatch(request, *args, **kwargs)
  3. 加到类上:

    @method_decorator(deco, name='get') # 指定给get方法加
    
    # @method_decorator(deco, name='put')
    
    
    # 必须指定name; 如果有多个方法要装饰,并列写
    
    class LoginView(View):
    
       def get(self,request):
           return render(request,'login.html')

我们知道Django的中间件中做了一个全局的csrf保护,但是有时我们希望具体对待,那么可以导入csrf装饰器来实现这个需求:

from django.views.decorators.csrf import csrf_exempt,csrf_protect

其中csrf_exempt是不应用csrf保护;csrf_protect是应用csrf保护;我们就可以根据需要加到具体的视图上了:

@csrf_protect
def demo(request):
    print('DEMO')
    return HttpResponse('ok')

但是,在目前的Django版本中要注意,如果是CBV,那么csrf装饰器只能加在类中的dispatch方法上:

class LoginView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super(LoginView,self).dispatch(request, *args, **kwargs)
展开阅读全文

没有更多推荐了,返回首页