经过前一天得学习(Django学习路线之从虚拟环境开始Django初体验),相信你已经对Django已经有了基本的认识,今天来具体学习一下MVC思想的V(视图)
- Django视图
- URL映射
- URL中添加参数
- URL模块化
- URL命名与反转
- 指定默认的参数
- re_path函数
Django视图
新建一个名为django_view的项目:
并新建一个名为news的app:
在新建的app里的 views.py文件里补充如下代码:
from django.http import HttpResponse
# Create your views here.
def news(request):
return HttpResponse("新闻首页")
回到项目的urls.py文件做映射:
from news import views
刚导入的时候可能会有如下报错:
这时候只需要对项目文件夹进行如下操作即可:
把映射写完后:
from news import views
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news)
]
进入网站界面:
因为没有定义首页,所以首页会报错,但是进入news的界面已经定义了,所以直接进入即可:
方法里的request参数
接下来我们回到news的views.py文件,看一下request的类型:
def news(request):
print(type(request))
return HttpResponse("新闻首页")
看一下request的类型:
<class ‘django.core.handlers.wsgi.WSGIRequest’>
可以看出这是一个类,下面看一下这个类的源码:
from django.core.handlers.wsgi import WSGIRequest
点进去看一下:
看一下这个类里的方法:
既然是类,我们就可以调用里面的方法,下面来试一下调用method方法:
清空输出再刷新一下页面:
发现这是一个GET请求
注意事项
- request参数是必须的
- 返回的HttpResponse()也是必须的
如果直接返回字符串会报错,如下图所示:
‘str’ object has no attribute ‘get’
视图的总结
视图一般都写在app的views.py中
并且视图的第一个参数永远都是request(一个HttpRequest)对象。这个对象存储了请求过来的所有信息,包括携带的参数以及一些头部信息等。在视图中,一般是完成逻辑相关的操作。
比如这个请求是添加一篇博客,那么可以通过request来接收到这些数据,然后存储到数据库中,最后再把执行的结果返回给浏览器。
视图函数的返回结果必须是HttpResponseBase对象或者子类的对象。示例代码如下:
news/views.py:
from django.http import HttpResponse
def news(request):
return HttpResponse("这是新闻首页")
urls.py:
from news import views
urlpatterns = [
path("news",views.news)
]
URL映射
基本原理
视图写完后,要与URL进行映射,也即用户在浏览器中输入什么url的时候可以请求到这个视图函数
向浏览器输入:http://127.0.0.1:8000/news/
且urls.py里添加了映射:
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news)
]
因此才能访问到如下界面:
换句话说,在用户输入了某个url,请求网站的时候,django会从项目的urls.py文件中寻找对应的视图另外,打开settings.py文件,找到ROOT_URLCONF:
如果把这一行注释,那么就无法找到对应的视图了
问题的引入
假设我多了一个book的界面:
from django.contrib import admin
from django.urls import path
from news import views
from book import views
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news)
path('books/', views.news)
]
这时会出现两个views,这时因为不知道使用哪一个views,所以会报错
要解决这一问题,请继续往下看
URL中添加参数
有时候,url中包含了一些参数需要动态调整。
后面的105962054就是我这篇文章的id
那么如何在django中实现这种需求呢。这时候我们可以在path函数中,使用尖括号的形式来定义一个参数
指定参数
urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news),
# news_id 必须和视图函数中的参数保持一致
path('news/<news_id>', views.news_detail)
]
news_id 必须和视图函数中的参数保持一致
在views.py里补充对应的方法:
def news_detail(request,news_id):
return HttpResponse("新闻详情页-{}".format(news_id))
效果如下:
但是这样的写法不够安全,我们随意打开百度搜索一个页面:
可以看到后面的id都是乱码,那么怎么实现这一功能呢?
查询字符串的方式传递参数
下面的部分把刚刚的代码注释,便于大家比对
urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news),
# news_id 必须和视图函数中的参数保持一致
# path('news/<news_id>', views.news_detail)
# 查询字符串的方式传递参数
path('news/news_detail/', views.news_detail)
]
里面就不使用尖括号了,改动主要在views.py里:
# def news_detail(request,news_id):
# return HttpResponse("新闻详情页-{}".format(news_id))
def news_detail(request):
news_id = request.GET.get("news_id")
return HttpResponse("新闻详情页-{}".format(news_id))
这次把参数放在方法里
来看一下效果:http://127.0.0.1:8000/news/news_detail/?news_id=2
URL模块化
回到URL映射里提到的那个问题,这里我们新建一个名为book的app:
在urls.py里添加如下代码:
from news import views
from book import views
直接运行会发生报错:
AttributeError: module ‘book.views’ has no attribute ‘news’
也就是说当前有两个views,程序不知道使用哪个views,而且现在只有两个app,到后期app更多时,这样的写法肯定会有问题
因此我们可以用as语句来优化一下:
from news import views
from book import views as bviews
不过这也有个问题,虽然不报错了,但是如果把所有的app的views中的视图都放在urls.py中进行映射,肯定会让代码显得非常乱
因此django给我们提供了一个方法,可以在app内部包含自己的url匹配规则,而在项目的urls.py中再统一包含这个app的urls。使用这个技术需要借助include函数。下面是示例代码
django_views/urls.py文件:
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path("news/",include("news.urls"))
]
在news里新建一个urls.py:
from django.urls import path
from . import views #从当前目录导入views
urlpatterns = [
path("",views.news),
path('news_detail/', views.news_detail)
]
如此一来,在django_views/urls.py文件中把所有的和news这个app相关的url都移动到news/urls.py中了,django_first/urls.py中,通过include函数包含news.urls,以后在请求news相关的url的时候都需要加一个news的前缀
那么在book这个app里也是同样的做法
在django_view\urls.py里补充book的url映射:
urlpatterns = [
path("news/",include("news.urls")),
path("book/",include("book.urls"))
]
在book\views里补充方法:
from django.http import HttpResponse
# Create your views here.
def book(request):
return HttpResponse("图书首页")
def book_detail(request, book_id):
return HttpResponse("图书详情页-{}".format(book_id))
在book里新建urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("",views.book),
path("book_detail/<book_id>",views.book_detail)
]
效果是一样的:
这里不知道大家有没有注意到,book_id应该为int类型:
def book_detail(request, book_id):
print(type(book_id))
return HttpResponse("图书详情页-{}".format(book_id))
打印一下:
很明显,并不是int类型,传一个字符串也是可以的:
那么这里就出现一个问题,我无法用这里的id做SQL查询,或者是在列表里根据id取值
这时试一下强制类型转换:
def book_detail(request, book_id):
print(type(book_id))
new_book_id = int(book_id)
print(type(new_book_id))
return HttpResponse("图书详情页-{}".format(new_book_id))
保存并运行一下:
直接就报错了: invalid literal for int() with base 10: ‘zbp’
Django内置转换器
这里有一个优化的方法,在book\urls.py里:
path("book_detail/<int:book_id>",views.book_detail
尖括号里由<book_id>改成 <int: book_id>
这时再试一下输入字符串:
报错信息变成了:“没有找到页面”,这样的效果是比直接报错要好的另外,输入一个数字,输出一下id的类型:
可以看到已经变成了int类型
除了int和str,还有其他的类型可以输出:
from django.urls import converters
来看一下Django内置的5个类型:
URL命名与反转
- 为什么需要URL命名
因为在项目开发的过程中URL地址可能经常变动,如果写死会经常去修改 - 如何给一个URL指定名称
path("",views.index,name=“index”) - 应用命名空间
在多个app之间可能产生同名的URL,这时候为了避免这种情况,可以使用命名空间来加以区分。在urls.py中添加app_name即可。
下面新建两个app,分别是前台和后台:
接下来是代码补充,跟前面讲到的方法是一样的,我们再一起来过一遍,加深印象:
来测试一下:
重定向
接下来要实现的功能是判断用户是否登陆,如果没有登陆,就跳转到登陆界面
拿知乎的官网举例:https://www.zhihu.com/
直接访问时,会直接跳转到登陆界面:https://www.zhihu.com/signin?next=%2F
下面来到front\views.py实现这一功能,我们一步步来:
def index(request):
# url是否传递username
username = request.GET.get("username")
if username:
return HttpResponse("前台首页")
else:
# 跳转到登陆界面
pass
下面我们需要redirect(),进行重定向,完整代码如下:
from django.shortcuts import render,redirect
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否传递username
username = request.GET.get("username")
if username:
return HttpResponse("前台首页")
else:
# 跳转到登陆界面
return redirect("login/")
def login(request):
return HttpResponse("前台登录界面")
下面来看一下效果: http://127.0.0.1:8000/front/
自动跳转到了: http://127.0.0.1:8000/front/login/
但这时有个问题,这里的映射都必须对应:
这样一来,改起来就会很麻烦,app一多就更难改了,这时就要用到反转
反转
在front\urls.py里增加一个参数name:
from django.urls import path
from . import views
urlpatterns = [
path("",views.index),
path("login/",views.login, name = "login")
]
这样一来,前面的映射写什么地址都行,都不影响页面的跳转
在front\views.py里多导入一个reverse库:
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否传递username
username = request.GET.get("username")
if username:
return HttpResponse("前台首页")
else:
# 跳转到登陆界面
return redirect(reverse("login"))
def login(request):
return HttpResponse("前台登录界面")
跳转时使用反转,参数对应于front\urls.py里参数name的值
而这时也有一个问题,前台和后台的name一样的话:
在多个app之间可能产生同名的URL,这时候为了避免这种情况,可以使用命名空间来加以区分
在cms\urls.py里加上app_name:
app_name = "cms"
在cms\views.py里:
这时来看一下效果,输入http://127.0.0.1:8000/cms/:
得到的返回:http://127.0.0.1:8000/cms/login/
应用命名空间和实例命名空间
一个app,可以创建多个实例。可以使用多个URL映射同一个App。在做反转的时候,如果使用应用命名空间,就会发生混淆,为了避免这个问题,可以使用实例命名空间,实例命名空间使用,namespace=‘实例命名空间’
urls.py:
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('cms1/', include("cms.urls",namespace='cms1')),
path('cms2/', include("cms.urls",namespace='cms2')),
path('front/', include("front.urls")),
]
URL反转传递参数
如果这个url中需要传递参数,那么可以通过kwargs来传递参数
front\views.py里的reverse加入kwargs:
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否传递username
username = request.GET.get("username")
if username:
return HttpResponse("前台首页")
else:
# 跳转到登陆界面
return redirect(reverse("front:login", kwargs={"uid":1}))
def login(request, uid):
return HttpResponse("前台登录界面-接收到的uid为{}".format(uid))
front\views.py:
from django.urls import path
from . import views
app_name = "front"
urlpatterns = [
path("",views.index),
path("login/<uid>",views.login, name = "login")
]
下面是效果:
访问http://127.0.0.1:8000/front/
如果要传入字符串:
把front\urls.py里的path改成path(“login/”,views.login, name = “login”),即去掉< uid >
然后到front\views.py,把代码改成:
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否传递username
username = request.GET.get("username")
if username:
return HttpResponse("前台首页")
else:
# 跳转到登陆界面
login_url = reverse("front:login") + "?name=zbp"
return redirect(login_url)
def login(request):
uid = request.GET.get("name")
return HttpResponse("前台登录界面-接收到的uid为{}".format(uid))
因为django中的reverse反转url的时候不区分GET请求和POST请求,因此不能在反转的时候添加查询字符串的参数
效果如下:
指定默认的参数
在book\views.py里添加图书列表以及对应方法:
book_list = ["a", "b", "c", "d"]
def book_lists(request):
return HttpResponse(book_list[0])
在book\urls.py补充映射:
urlpatterns = [
path("",views.book),
path("book_detail/<int:book_id>",views.book_detail),
path("book_lists/",views.book_lists)
]
效果如下:
图书详情页的功能也可以进行完善:
book\views.py:
def book_detail(request, book_id = 1):
print(type(book_id))
return HttpResponse("图书详情页-{}".format(book_list[book_id]))
给book_id指定一个默认参数1
book\urls.py:
urlpatterns = [
path("",views.book),
path("book_detail/<int:book_id>",views.book_detail),
path("book_detail/",views.book_detail),
path("book_lists/",views.book_lists)
]
看一下效果:
默认的参数是1,传一个其他的参数:
re_path函数
有时候在写url匹配的时候,想要写使用正则表达式来实现一些复杂的需求,那么这时候我们可以使用re_path来实现
re_path的参数和path参数一模一样,只不过第一个参数也就是route参数可以为一个正则表达式
在news\urls.py里添加re_path:
urlpatterns = [
path("",views.news),
path('news_detail/', views.news_detail),
# r是原生字符串
re_path(r'news_list/(?P<year>\d{4})', views.news_list)
]
年份是4位的,问号大P用于命名< year >, 匹配4个长度
在news\viewss.py里补充news_list方法:
def news_list(request, year):
return HttpResponse("新闻列表页面-{}年".format(year))
来看一下效果:
这里需要注意的是,如果传入5位的参数,也只会匹配前4位:
如果要匹配月份,写法如下:
re_path(r"^article_list/(?P<mouth>\d{2})/",views.article_mouth)