谈谈Python之Django搭建企业级官网(第三篇下)

转载请注明来源地址和原作者(CFishHome)

前沿

上一篇文章我们学习了URL与视图函数的映射、传递参数的三种方式、转换器的简单使用和include函数分层映射管理。接下来这一篇文章着重介绍Path、re_path、include、reverse、redirect函数的使用和自定义URL转换器。学完这些内容,相信我们对URL和视图都会有了一定的了解和认识。为了让每篇文章具有一定的独立性,我决定每篇文章都重新新建一个项目,便于测试和调试。

预备

首先,我们在Pycharm重新新建一个名为book_project的Django项目,然后打开CMD窗口进入虚拟环境,接着跳转到该项目的目录下(含有manage.py),再依次执行创建app命令,如下图:
谈谈Python之Django搭建企业级官网(第三篇下)
创建完成后,该项目的样子大概是酱紫的:
谈谈Python之Django搭建企业级官网(第三篇下)

模拟前端和后端

我们想实现:front前端拥有前端首页和前端登陆页面,而cms后端拥有后端首页和后端登陆页面,然后分别输入配置好的URL对这四个页面进行访问。
首先,为cms和front两个app手动添加urls.py文件。然后分别按以下步骤添加或修改项目文件代码:
(1)为cms的urls.py文件添加以下代码:

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index),
    path("login/", views.login)
]

(2)为front的urls.py文件添加以下代码:

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index),
    path("login/", views.login)
]

(3)为cms的views.py文件添加以下代码:

from django.http import HttpResponse

def index(request):
    return HttpResponse("后端首页")

def login(request):
    return HttpResponse("后端登陆页面")

(4)为front的views.py文件添加以下代码:

from django.http import HttpResponse

def index(request):
    return HttpResponse("前端首页")

def login(request):
    return HttpResponse("前端登陆页面")

(5)为book_project这个主包的urls.py文件添加以下代码:

from django.urls import path,include

urlpatterns = [
    path('', include("front.urls")),
    path('cms/', include("cms.urls"))
]

如果学习了上一篇文章的内容,我相信这些代码的编写是完全OK的。好,我们运行一下项目(注意,我们是新建的项目,记得在勾选单一实例运行。)。
运行结果如下(完美,成功运行~):
谈谈Python之Django搭建企业级官网(第三篇下)

实际需求

模拟自动跳转新用户登陆页面

实际需求:要想实现一个类似于知乎主页一样,若是新用户第一次登陆知乎,不管哪个页面都会跳转到登陆账号的页面,对于这一需求,我们可以通过以下模拟实现(为了更好观察代码,一样的代码部分我直接提示省略):
(1)修改front前台的index视图函数

#省略上面代码
from django.shortcuts import redirect  # 导入重定向redirect函数,最好记住redirect函数源于哪个模块,因为要经常重定向。

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,当输入http://127.0.0.1:8000/?username=1正常显示前台首页。
    if username:
        return HttpResponse('前台首页')
    else:
        return redirect('/login/')  # 跳转通过redirect函数来进行页面重定向,重新访问指定的url页面
#省略下面代码

修改完代码后,按下Ctrl+S保存(自动重新编译项目)。注意观察,输入127.0.0.1:8000会发现自动跳转到127.0.0.1:8000/login/。

重定向代码的缺陷

其实还存在这样一种情况,假如老板或项目经理突然脑袋秀逗了,要求你更改网页的URL,将127.0.0.1:8000/login/更改为127.0.0.1:8000/signin/,假如这时还采用上面的代码进行网页重定向,那你会非常抓狂,举我们这个项目栗子来说,不单只要修改为下面这样:

urlpatterns = [
    path("", views.index),
    path("signin/", views.login)
]

还要修改重定向位置的代码这样:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,否则正常显示前台首页。
    if username:
        return HttpResponse('前台首页')
    else:
        return redirect('/singin/')  # 跳转通过redirect函数来进行页面重定向,重新访问指定的url页面

如果公司网站项目十分庞大的话,那可能还有很多地方都要修改,这十分耗时也费力。所以,我们推荐为URL命名来解决这一问题,给URL取个名字,只要调用reverse反转URL的名字而不是直接重定向写死的URL,那么无论URL怎么修改也影响不到其他地方。

Path函数

在学习URL命名之前,先详细学习下Path函数的使用。
path 函数的定义为:

path(route,view,name=None,kwargs=None)  。

以下对这几个参数进行讲解。

  1. route 参数: url 的匹配规则。这个参数中可以指定 url 中需要传递的参数,上一篇文章已讲解,这个参数不再赘述。请移步到谈谈Python之Django搭建企业级官网(第三篇上部)
  2. view 参数:可以为一个视图函数或者是 类视图.as_view() 或者是 django.urls.include() 函数的返回值。
  3. name 参数:这个参数是给这个 url 取个名字的,这在项目比较大, url 比较多的时候用处很大。这个参数就是用于URL命名。
  4. kwargs 参数:有时候想给视图函数传递一些额外的参数,就可以通过 kwargs 参数进行传递。这个参数接收一个字典。会将foo=bar作为关键字实参传入视图函数,所以视图函数要有形参来接收实参。
    比如以下的 url 规则:
    from django.urls import path
    from . import views
    urlpatterns = [
        path('blog/<int:year>/', views.year_archive, kwargs={'foo':'bar'}),  
    ]

    那么以后在访问 blog/1991/ 这个url的时候,会将{'foo':'bar'}作为关键字参数传给 year_archive函数。year_archive视图函数的形参中最后添加一个kwargs参数来接收这个额外的参数。

URL命名

学习完path函数各参数,相信都知道该函数的name参数就是用于URL命名的了。
接下来修改front包的urls.py文件代码如下:

urlpatterns = [
    path("", views.index),
    path("login/", views.login, name='login')
]

再次修改front包的views.py文件代码如下:

#省略上面代码
from django.shortcuts import redirect, reverse  # 注意这里添加了reverse函数,reverse函数用于将指定URL名字反转成URL。

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,否则正常显示前台首页。
    if username:
        return HttpResponse('前台首页')
    else:
        # reverse('login')函数返回‘/login/’
        return redirect(reverse('login'))  # 跳转通过redirect函数来进行页面重定向,重新访问指定的url页面(相当于重新访问127.0.0.1:8000/login/)
#省略下面代码

按下Ctrl+S保存,输入127.0.0.1:8000成功跳转到127.0.0.1:8000/login登陆页面。

应用命名空间

在公司实际开发中,如果公司里的多个人同时负责网站的开发,而且A同事负责开发front的app,B同事负责开发cms的app,那么由于两个app都有首页和登陆页面,那么有可能url的name属性可能会相同冲突。在多个app之间,有可能产生同名的url,这时候为了避免反转url的时候产生混淆,可以使用应用命名空间来做区分。定义应用命名空间非常简单,只要在“app”的“urls.py”中定义一个叫做“app_name”的变量,来指定这个应用的命名空间即可。
就好比我们的项目,将front包里的urls.py和views.py文件和cms包里的urs.py和views.py文件分别为URL映射命名为index和login,如下图所示:
谈谈Python之Django搭建企业级官网(第三篇下)
运行项目,输入127.0.0.1:8000自动跳转到127.0.0.1:8000/cms/login网页,而不是之前的127.0.0.1:8000/login网页,由于多个app的URL拥有相同的名字,所以Django在执行reverse函数反转URL时懵逼了。为了解决这个问题,我们将采用应用命名空间。
修改front包的urls.py文件的代码如下(django在执行到app时,会自动读取这个应用命名空间并将这个名字作为app的名字):

from django.urls import path
from . import views

app_name = "front"  # 添加了应用命名空间

urlpatterns = [
    path("", views.index, name="index"),
    path("login/", views.login, name='login')
]

以后在做反转的时候就可以使用“应用命名空间:url名称”的方式进行反转。示例代码如下修改front包的views.py文件的代码如下:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,否则正常显示前台首页。
    if username:
        return HttpResponse('前台首页')
    else:
        return redirect(reverse('front:login'))  # 这里为URL名字前面添加front应用命名空间名

按下Ctrl+S保存,输入127.0.0.1:8000自动跳转到127.0.0.1:8000/login网页,成功了,Django不会再懵逼了。

include函数

在上一篇文章我们知道,在项目不断庞大以后,经常不会把所有的 url 匹配规则都放在项目的 urls.py 文件中,而是每个 app 都有自己的 urls.py 文件,在这个文件中存储的都是当前这个 app 的所有url 匹配规则。然后再统一include到项目的 urls.py 文件中。 include 函数有多种用法,这里讲下几种常用的用法:
(1)include(pattern,namespace=None) :直接把其他 app 的 urls 包含进来。
之前的include用法,举个栗子(下面的代码仅用于解释用法,不是将代码添加到项目):

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
        path('admin/', admin.site.urls),
        path('book/',include("book.urls"))
]

我们可以发现这一种用法其实该函数还有参数2指定实例命名空间,默认是None。但是在使用实例命名空间之前,必须先指定一个应用命名空间,不先指定应用命名空间会报错。一个app可以创建多个实例,也就是可以使用多个url映射同一个app,所以这就产生一个问题。以后在做反转的时候,如果使用应用命名空间,那么就会发生混淆。
将book_project主包的urls.py文件代码修改如下:

from django.urls import path,include

urlpatterns = [
    path('', include("front.urls")),
    path('cms1/', include("cms.urls")),  # 这两行代码代表输入127.0.0.1:8000/cms1或127.0.0.1:8000/cms2能映射到cms.urls内部的url路径。
    path('cms2/', include("cms.urls"))
]

为cms包的urls.py文件添加应用命名空间:

app_name = "cms"

为cms包的views.py文件修改代码如下:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,否则正常显示前台首页。
    if username:
        return HttpResponse('后台首页')
    else:
        return redirect(reverse('cms:login'))  # 这里使用了应用命名空间反转URL

经过上面代码的修改,这时候按下Ctrl+S保存,输入127.0.0.1:8000/cms1成功跳转到127.0.0.1:8000/cms1/login,但是当输入127.0.0.1:8000/cms2却自动跳转到了127.0.0.1:8000/cms1/login,what?我的127.0.0.1:8000/cms2/login哪里去了?导致上面的原因就是前面介绍的一个app可以创建多个实例,也就是可以使用多个url映射同一个app,所以这就产生一个问题。以后在做反转的时候,如果使用应用命名空间,那么就会发生混淆。为了避免这个问题,我们可以使用实例命名空间。实例命名空间也是非常简单,只要在“include”函数中传递一个“namespace”变量即可。
在上面代码的基础上继续修改代码,修改cms包的views.py文件代码如下:

def index(request):
    username = request.GET.get('username')
    if username:
        return HttpResponse("后端首页")
    else:
        current_namespace = request.resolver_match.namespace  # 返回当前app对应的实例命名空间(cms1或cms2)
        return redirect(reverse('%s:login' % current_namespace))  # 相当于"cms1:login"或”cms2:login“

接着修改book_project主包的urls.py文件代码如下:

from django.urls import path,include

urlpatterns = [
    path('', include("front.urls")),
    path('cms1/', include("cms.urls", namespace="cms1")),  # 使用了实例命名空间namespace
    path('cms2/', include("cms.urls", namespace="cms2"))  
]

这时候按下Ctrl+S保存,输入127.0.0.1:8000/cms1成功跳转到127.0.0.1:8000/cms1/login,然后输入127.0.0.1:8000/cms2也成功跳转到了127.0.0.1:8000/cms2/login页面。如下图:
谈谈Python之Django搭建企业级官网(第三篇下)
(2)include((pattern_list,app_namespace),namespace=None) :“include”函数的第一个参数既可以作为一个字符串,也可以作为一个元组,如果是元组,那么元组的第一个参数是子“urls.py”模块的字符串,元组的第二个参数是应用命名空间,也就是说应用命名空间既可以在子“urls.py”种通过"app_name"指定,也可以在“include”函数中指定。
将book_project主包的urls.py文件代码修改如下:

from django.urls import path, include
from front import views

urlpatterns = [
    path('', include(([
                          path('', views.index, name="index"),
                          path("login/", views.login, name='login')
                      ], "front"))),  # 注意
    path('cms1/', include("cms.urls", namespace="cms1")),
    path('cms2/', include("cms.urls", namespace="cms2"))
]

上面的代码相当于完全忽略了front包的urls.py文件的作用,因为已经被include函数的第一个参数列表给替代了,所以urls.py文件的“app_name = "front"”指定的应用命名空间自然也失效了。这时运行代码完全OK,跟之前的一摸一样,输入127.0.0.1:8000自动跳转到127.0.0.1:8000/login页面。
(3)include(pattern_list) :可以包含一个列表或者一个元组,这个元组或者列表中又包含的是 path 或者是 re_path 函数(该函数后面会讲)。
这个函数跟上一个函数差不多,也是用含有path函数的列表或元组替代了之前的pattern。但是需要注意的是,因为这样会忽略了front包的urls.py文件的作用,所以urls.py文件的“app_name = "front"”指定的应用命名空间自然也失效了。那么如果你映射的视图函数内部进行反转URL时指定了应用命名空间,那么将会报错,会提示找不到front命名空间,如下:

谈谈Python之Django搭建企业级官网(第三篇下)

所以,综上建议,如果你映射的视图函数内部进行反转URL时指定了应用命名空间,最好调用include((pattern_list,app_namespace),namespace=None) 函数,在指定列表或元组 的同时也指定应用命名空间。

reverse函数

之前我们都是通过url来访问视图函数。有时候我们定义了URL名字,然后通过调用reverse函数反转回它的url。就好像前面的代码调用那样:

reverse("login")
> /login/

如果有应用命名空间或者有实例命名空间,那么应该在反转的时候加上命名空间。就好像前面的代码调用那样:

reverse('front:login')
> /login/  # 注意,反转的是前端app的url

接着我们就要考虑另外一个问题,就是如果URL映射时需要传递参数,这时reverse函数该怎么调用才能传递参数呢?我们都知道传递参数有其中两种方式:1.尖括号方式传递参数 2.查询字符串传递参数。
下面分别讲解这两种传递参数方式在reverse函数中的使用:
(1)URL尖括号传递参数
如果这个url中需要传递参数,那么可以通过 kwargs 来传递参数。
首先,我们修改front包的views.py文件的代码如下:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,否则正常显示前台首页。
    if username:
        return HttpResponse('前台首页')
    else:
        return redirect(reverse('front:login', kwargs={'user_id': 1}))  # 跳转通过redirect函数来进行页面重定向,重新访问指定的url页面

def login(request, user_id):
    text = "当前用户登陆的ID是:%s" % user_id
    return HttpResponse(text)

接着,由于上节测试include函数,将front的url映射全放在book_project主包的urls.py文件里,所以修改成这样:

urlpatterns = [
    path('', include(([
                          path('', views.index, name="index"),
                          path("login/<user_id>/", views.login, name='login')
                      ], "front"))),
    path('cms1/', include("cms.urls", namespace="cms1")),
    path('cms2/', include("cms.urls", namespace="cms2"))
]

按下Ctrl+S保存,运行结果如下(成功访问,说明reverse传递尖括号参数是有效的):
谈谈Python之Django搭建企业级官网(第三篇下)
(2)查询字符串传递参数
因为 django 中的 reverse 反转 url 的时候不区分 GET 请求和 POST 请求,因此不能在反转的时候添加查询字符串的参数。如果想要添加查询字符串的参数,只能手动的添加。
首先,我们修改front包的views.py文件的代码如下(注意代码的细微调动):

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 当用户输入127.0.0.1:8000,检测到没有账户则跳转到注册页面,否则正常显示前台首页。
    if username:
        return HttpResponse('前台首页')
    else:
        return redirect(reverse('front:login') + "?next=/")  # 跳转通过redirect函数来进行页面重定向,重新访问指定的url页面

def login(request):
    n = request.GET.get("next")
    text = "你输入的next值为:%s" % n
    return HttpResponse(text)

接着,我们修改book_project主包的urls.py文件的代码如下(注意代码的细微调动):

urlpatterns = [
    path('', include(([
                          path('', views.index, name="index"),
                          path("login", views.login, name='login')
                      ], "front"))),
    path('cms1/', include("cms.urls", namespace="cms1")),
    path('cms2/', include("cms.urls", namespace="cms2"))
]

按下Ctrl+S保存,运行结果如下(成功访问,说明reverse传递查询字符串参数是有效的):
谈谈Python之Django搭建企业级官网(第三篇下)

re_path函数

有时候我们在写url匹配的时候,想要写使用正则表达式来实现一些复杂的需求,那么这时候我们可以使用 re_path 来实现。 re_path 的参数和 path 参数一模一样,只不过第一个参数也就
是 route 参数可以为一个正则表达式。
一些使用 re_path 的示例代码如下:

from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'articles/(?P<year>[0-9]{4})/', views.year_archive),
re_path(r'articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/', views.month_archiv
e),
re_path(r'articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-_]+)/',
views.article_detail),
]

以上例子中我们可以看到,所有的 route 字符串前面都加了一个 r ,表示这个字符串是一个原生字符串。在写正则表达式中是推荐使用原生字符串的,这样可以避免在 python 这一层面进行转义。
用法:(?P<参数变量>匹配参数量的正则表达式),这个用法显而易见,这个参数的名字是通过尖括号 <year> 进行包裹,之后才是写正则表达式的语法,说明如果在传递这个参数变量的时候,这个参数变量必须满足后面所写的正则表达式,否则就会报错。

自定义URL转换器

在学习前面的转换器和reverse函数反转时,肯定会产生疑惑:为什么转换器能实现转换功能?还有reverse函数是怎么反转成一个URL的呢?
好的,这些转换和反转过程容许我一一道来。
我们都看过converters模块的实现,如下图:
谈谈Python之Django搭建企业级官网(第三篇下)
可以看出这个Int转换器类除了定义了正则表达式,还定义了两个函数to_Python和to_url。
转换器和reverse相互转化过程如下:

当给path函数的第一个参数使用尖括号参数,如果使用转换器,那么会将这个参数传递给to_python函数进行int转换,然后将转换后的int值传递给视图函数的同名形参接收,所以视图函数的形参变成int类型的了。与此同时,如果视图函数内调用reverse函数进行反转的话,那么reverse函数的kwargs参数的int类型值(即视图函数的形参)传递给to_url转换为str类型,然后将转换后的str值传递path函数的第一个参数,组合成真正的字符串URL,最后reverse函数再返回这个字符串URL。

之前已经学到过一些django内置的 url 转换器,包括有 int 、 uuid 等。有时候这些内置的 url转换器并不能满足我们的需求,因此django给我们提供了一个接口可以让我们自己定义自己的url转换器,即自己定义一个转换器类来实现转换。自定义 url 转换器按照以下五个步骤来走就可以了:

  1. 定义一个类。
  2. 在类中定义一个属性 regex ,这个属性是用来保存 url 转换器规则的正则表达式。
  3. 实现 to_python(self,value) 方法,这个方法是将 url 中的值转换一下,然后传给视图函数
    的。
  4. 实现 to_url(self,value) 方法,这个方法是在做 url 反转的时候,将传进来的参数转换后拼
    接成一个正确的url。
  5. 将定义好的转换器,注册到django中。
    为了更好的描述如何自定义URL转换器,这里我们重新创建一个名为my_converters的Django项目,然后创建一个名为my_app的app。

(1)修改my_converters主包的urls.py文件代码如下:

from django.urls import path
from my_app import views
from django.urls import register_converter

#1. 定义一个类
class FourDigitYearConverter:
    #2. 定义一个正则表达式
    regex = '[0-9]{4}'

    #3. 定义to_python方法
    def to_python(self, value):
        return int(value)

    #4. 定义to_url方法
    def to_url(self, value):
        return '%04d' % value

#5. 注册到django中
register_converter(FourDigitYearConverter, 'four_year')

urlpatterns = [
    path('index/<four_year:year>/', views.index)
]

(2)修改my_app包的views.py文件代码如下:

from django.http import HttpResponse

def index(request, year):
    text = "你输入的年份是:%s" % year
    return HttpResponse(text)

然后按下Ctrl+S保存,输入127.0.0.1:8000/index/220,因为不满足我们自己定义的URL转换器的正则表达式,所以页面显示不出来,报以下错误:
谈谈Python之Django搭建企业级官网(第三篇下)
再次输入127.0.0.1:8000/index/2018,页面访问成功,说明我们定义的four_year转换器有效,如下图:
谈谈Python之Django搭建企业级官网(第三篇下)
我们其实除了在regex下修改,也可以随意编写to_python函数和to_url函数的内部实现,完全按照我们的意愿进行工作,很灵活呢。对于自定义URL转换器有个更好的改进之处,就是我们可以新建一个converters.py文件专门保存我们自定义的URL转换器,但有个问题就是,这个文件是我们自己新建的,Django怎么会知道并且执行里面的代码呢?如果Django不执行的话,那我们这个自己定义的URL转换器不就失去作用了吗?哈哈,有个方法可以解决,有没有发现就是每个app包里都会有个init.py文件,其实Django每次运行项目都会执行这个文件,那么当我们在init.py文件里导入这个cnverters.py文件,那么这个converters.py文件的代码就会自动被执行(导入代表导入模块的代码,例如:from . import converters)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章