在写视图的时候,Django除了使用函数作为视图,也可以使用类作为视图。使用类视图可以使用类的一些特性,比如继承等。
View视图:
django.views.generic.base.View
是主要的类视图,所有的类视图都是继承自他。如果我们写自己的类视图,也可以继承自他。然后再根据当前请求的method,来实现不同的方法。
例如:这个视图只能使用get的方式来请求,那么就可以在这个类中定义get(self,request,*args,**kwargs)
方法。
在views.py中新建一个类视图
from django.http import HttpResponse
from django.views.generic import View
class BookListView(View):
def get(self,request,*args,**kwargs):
return HttpResponse('book list view')
这样就只能使用get
方法来访问这个类视图了。
要想访问我们的类视图,我们还需要配置url,添加映射,而类视图不能像我们访问视图函数那样进行映射,还需要使用as_view()方法才能进行映射。
path('get',views.BookListView.as_view()),
将上面代码添加至urls中就能成功的进行映射了。
以此类推,如果只需要实现post方法,那么就只需要在类中实现post(self,request,*args,**kwargs)。
from django.http import HttpResponse
from django.views.generic import View
class BookListView(View):
def post(self,request,*args,**kwargs):
return HttpResponse('book list view')
接下来我们实现一个需求:我们需要获取到前端传入的值,如果用户是使用GET
方法访问的我们定义的视图函数,那么我们就返回一个html页面让他输入值,如果是使用POST
方法,我们就对获取的值进行处理。
首先在templates下面新建一个add_book.html
的文件,写入代码:
<form action="" method="post">
<table>
<tbody>
<tr>
<td>图书名字:</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>作者:</td>
<td><input type="text" name="author"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
然后在views中新建一个类视图接收数据:
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')
在urls中添加映射
path('add',views.AddBookView.as_view()),
然后在浏览器中输入网址,写入信息,就能在控制台打印出获取的信息了。
除了get
,post
方法,View还支持以下方法['get','post','put','patch','delete','head','options','trace']。
如果用户访问了View中没有定义的方法。比如你的类视图只支持get方法,而出现了post方法,那么就会把这个请求转发给http_method_not_allowed(request,*args,**kwargs)
。
示例:
新建一个BookDetaial的类:
class BookDetaialView(View):
def post(self,request,book_id):
return HttpResponse('图书id是:{}'.format(book_id))
def http_method_not_allowed(self, request, *args, **kwargs):
return HttpResponse('你使用的是%s请求,但是不支持POST以外的其他请求!'%request.method)
然后在urls中添加映射:
path('detaial/<int:book_id>',views.BookDetaialView.as_view()),
当我们输入网址进行访问的时候,就会返回一个字符窜:你使用的是GET请求,但是不支持POST以外的其他请求!
其实不管是get请求还是post请求,都会走django.views.generic.base.View
中dispatch(request,*args,**kwargs)
方法,所以如果实现这个方法,将能够对所有请求都处理到。
TemplateView视图:
django.views.generic.base.TemplateView
,这个类视图是专门用来返回模版的。在这个类中,有两个属性是经常需要用到的,一个是template_name
,这个属性是用来存储模版的路径,TemplateView
会自动的渲染这个变量指向的模版。另外一个是get_context_data
,这个方法是用来返回上下文数据的,也就是在给模版传的参数的。
需求:在一个网站中,有一些页面不需要我们从数据库中提取数据到前端页面中,例如网址中的“关于我们”
这个页面一般都是在html中写死的数据,不需要进行改动,这个时候我们就可以直接在urls中直接渲染html文件,而不用视图函数或者视图类来进行渲染。
新建一个about.html
的文件,
<body>
这是关于我们的页面
</body>
然后直接在urls中渲染这个模板:
from django.views.generic import TemplateView
# 如果渲染的模板不需要传递任何参数,那么建议在urls中使用TemplateView直接进行渲染
path('about/',TemplateView.as_view(template_name='about.html')),
而我们又想使用这个TemplateView进行渲染模板,又想传递少许参数,这个时候我们就可以定义一个类,继承至TemplateView
示例:
在views中新建一个类继承至TemplateView
from django.views.generic import View,TemplateView
class AboutView(TemplateView):
# 指定模板的路径
template_name = 'about.html'
# 传入的参数,数据
def get_context_data(self,**kwargs):
context ={'phone':'xxx-xxxxxxx'}
return context
我们也需要在about.html
文件中接收传入的数据
<body>
这是关于我们的页面{{ phone }}
</body>
然后将上面的映射注释掉,添加新的映射
path('about/',views.AboutView.as_view()),
这里我们就传递了一个参数phone
过去。
ListView:
在网站开发中,经常会出现需要列出某个表中的一些数据作为列表展示出来。比如文章列表,图书列表等等。在Django中可以使用ListView来帮我们快速实现这种需求。
因为要对表中的数据进行操作,那么我们首先的创建一个模型,然后映射到数据库中去。
models.py中写入代码:
from django.db import models
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=100);
content = models.TextField()
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'listView_article'
如果你的models在某个app下面,那么先将app添加至settings.py
中,然后配置数据库连接MySQL数据库,当然不配置也可以,使用默认的SQLite数据库也可以。
如果models.py
没有在某个app下面,那么就不用进行添加app这一步骤了,直接配置数据库就行了。
然后执行makenigrations和migrate。将模型映射到数据库中。为了方便我们进行测试,首先我们先来进行批量添加数据:
在views中新家一个函数视图:用来批量添加数据。
# 添加测试数据
from . import models
def addInfo(request):
articles = []
for x in range(0,101):
article = models.Article(title='标题:%s'%x,content='内容:%s'%x)
articles.append(article)
models.Article.objects.bulk_create(articles) #批量添加数据
return HttpResponse('success')
添加映射
path('addInfo/',views.addInfo),
输入网址,执行视图函数,我们就添加了101个数据到数据库中了。
然后我们新建一个article_list.html
的文件,写入代码:
<ul>
{% for article in articles %}
<li>{{ article.title }}</li>
{% endfor %}
</ul>
接下来我们新建一个类视图:
class ArticleListView(ListView):
model = models.Article # 重写model类属性,指定模型的列表
template_name = 'article_list.html' # 指定这个列表的模板。
context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
paginate_by = 10 # 每一页需要展示多少条数据
ordering = 'create_time' # 以时间进行排序展示
然后添加映射
path('list/',views.ArticleListView.as_view()),
输入网址,就能查看效果了,并且当前页面只有10条数据。
而如果我们向访问后面的页面,我们只需要在输入的网址后面以GET的方式添加一个page=x
的参数就行了
例如:我的网址是
http://127.0.0.1:8000/class/list/
那么我想访问第二页,我就应该这样输入网址
http://127.0.0.1:8000/class/list/?page=2
以此类推,后面的也是一样的了,因为我们只添加了101条数据,所以只有11页,而我们如果让page=12的话,就会找不到页面。如果我们在网址后面没有传递page参数,默认返回的就是第一页。
而如果我们不想使用page
作为参数,而是换一个p
作为参数,那么就可以使用page_kwarg
这个属性了
示例:将默认参数page
改为p
class ArticleListView(ListView):
model = models.Article # 重写model类属性,指定模型的列表
template_name = 'article_list.html' # 指定这个列表的模板。
context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
paginate_by = 10 # 每一页需要展示多少条数据
ordering = 'create_time' # 以时间进行排序展示
page_kwarg = 'p' # 指定换页面的参数 默认为page 以GET的方式传递参数
这样,我们就不能使用page作为参数了,而是只能使用p
作为参数了。
上面演示的是常用属性的用法,而我们还有两个比较常用的方法。
接下来我们就将演示这两种方法。
- get_context_data:获取上下文的数据。并且可以添加自己的参数,例如下面的
username
在上面定义的类中添加这个方法
class ArticleListView(ListView):
model = models.Article # 重写model类属性,指定模型的列表
template_name = 'article_list.html' # 指定这个列表的模板。
context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
paginate_by = 10 # 每一页需要展示多少条数据
ordering = 'create_time' # 以时间进行排序展示
page_kwarg = 'p' # 指定换页面的参数 默认为page 以GET的方式传递参数
# 重写父类的get_context_data方法,添加自己的参数
def get_context_data(self,**kwargs):
# 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然后添加自定义的参数
context['username'] = 'zhiliao'
print(context)
我们就能在控制台看到打印出来的东西了。
里面包含了很多东西,有我们常用的paginator类、page_obj类和我们自定义的一些参数等。
(paginator类、page_obj类下面将会进行讲解)
- get_queryset:如果你提取数据的时候,并不是要把所有数据都返回,那么你可以重写这个方法。将一些不需要展示的数据给过滤掉。
def get_context_data(self,**kwargs):
# 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然后添加自定义的参数
context['username'] = 'zhiliao'
print(context)
return context
# 设置需要返回的数据
def get_queryset(self):
# 没有重写方法默认返回所有的数据
# return models.Article.objects.all()
return models.Article.objects.filter(id__lte=93)
注意: 在这里我们将id>93
的数据过滤掉了,所以现在的页面只有10页了。所以传入的参数page不能大于10。
上面类中的属性和方法说明:
- model:重写model类属性,指定这个列表是给哪个模型的。
- template_name:指定这个列表的模板。
- paginate_by:指定这个列表一页中展示多少条数据。
- context_object_name:指定这个列表模型在模板中的参数名称。
- ordering:指定这个列表的排序方式。
- page_kwarg:获取第几页的数据的参数名称。默认是page。
- get_context_data:获取上下文的数据。
- get_queryset:如果你提取数据的时候,并不是要把所有数据都返回,那么你可以重写这个方法。将一些不需要展示的数据给过滤掉。
Paginator和Page类:
Paginator
和Page
类都是用来做分页的。他们在Django中的路径为django.core.paginator.Paginator
和django.core.paginator.Page
。
Paginator常用属性和方法:
- count:总共有多少条数据。
- num_pages:总共有多少页。
- page_range:页面的区间。比如有三页,那么就range(1,4)。
示例代码:修改上面定义的类的代码:
class ArticleListView(ListView):
model = models.Article # 重写model类属性,指定模型的列表
template_name = 'article_list.html' # 指定这个列表的模板。
context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
paginate_by = 10 # 每一页需要展示多少条数据
ordering = 'create_time' # 以时间进行排序展示
page_kwarg = 'p' # 指定换页面的参数 默认为page 以GET的方式传递参数
# 重写父类的get_context_data方法,添加自己的参数
def get_context_data(self,**kwargs):
# 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然后添加自定义的参数
context['username'] = 'zhiliao'
# print(context)
paginator = context.get('paginator') # paginator类
print(paginator.count) # 获取数据的个数
# print(paginator.num_pages) # 获取页数
# print(paginator.page_range) #或缺页数的范围,要前不要后
return context
# 设置需要返回的数据
# def get_queryset(self):
# # 没有重写方法默认返回所有的数据
# # return models.Article.objects.all()
# return models.Article.objects.filter(id__lte=89)
然后可自行修改代码查看对应效果。
Page类:
page类的常用属性和方法:
- has_next:是否还有下一页。
- has_previous:是否还有上一页。
- next_page_number:下一页的页码。
- previous_page_number:上一页的页码。
- number:当前页。
只有这一个是属性,其他的都是方法
- start_index:当前这一页的第一条数据的索引值。
- end_index:当前这一页的最后一条数据的索引值。
示例代码:
class ArticleListView(ListView):
model = models.Article # 重写model类属性,指定模型的列表
template_name = 'article_list.html' # 指定这个列表的模板。
context_object_name = 'articles' # 指定这个列表模型在模板中的参数名称。
paginate_by = 10 # 每一页需要展示多少条数据
ordering = 'create_time' # 以时间进行排序展示
page_kwarg = 'p' # 指定换页面的参数 默认为page 以GET的方式传递参数
# 重写父类的get_context_data方法,添加自己的参数
def get_context_data(self,**kwargs):
# 我们需要先继承至父模板的get_context_data方法,否则我们有很多方法将不能使用
context = super(ArticleListView,self).get_context_data(**kwargs)
# 然后添加自定义的参数
context['username'] = 'zhiliao'
# print(context)
paginator = context.get('paginator') # paginator类
page_obj = context.get('page_obj') # page_obj类
# print(paginator.count) # 获取数据的个数
# print(paginator.num_pages) # 获取页数
# print(paginator.page_range) #或缺页数的范围,要前不要后
print(page_obj.number)
print(page_obj.start_index())
print(page_obj.end_index())
print(page_obj.has_next())
print(page_obj.has_previous())
print(page_obj.next_page_number())
print(page_obj.previous_page_number())
return context
# 设置需要返回的数据
# def get_queryset(self):
# # 没有重写方法默认返回所有的数据
# # return models.Article.objects.all()
# return models.Article.objects.filter(id__lte=89)
给类视图添加装饰器:
在开发中,有时候需要给一些视图添加装饰器。如果用函数视图那么非常简单,只要在函数的上面写上装饰器就可以了。但是如果想要给类添加装饰器,那么可以通过以下两种方式来实现:
需求:在访问个人中心页面的时候,如果没有登录,我们就让它跳转到登录页面,登录之后才能访问个人中心。
这里我们就是用get请求来模拟是否登录了,即如果我们在访问网址的时候使用get方法传递了一个username
的参数,我们就认为已经登录, 否则的话就没有登录。
1. 装饰类视图中的dispatch方法:
首先在view中定义个人中心的类视图和登录页面的函数视图:
# 个人中心的类视图
class Profileview(View):
# 只能通过GET请求来访问
def get(self,request):
return HttpResponse('个人中心页面')
# 因为所有的请求方法在类视图中最终都会通过diapatch这个方法,
# 所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。
def dispatch(self, request, *args, **kwargs):
return super(Profileview,self).dispatch(request,*args,**kwargs)
# 登录的函数视图
def login(request):
return HttpResponse('login')
在上面我们已经说到了所有的请求方法在类视图中最终都会通过diapatch这个方法,所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。然后再使用装饰器将dispatch装饰,就达到了对类视图进行装饰的目地了,所以我们需要先写一个装饰器:
def login_required(func):
def wrapper(request,*args,**kwargs):
username = request.GET.get('username')
# 判断是否得到username这个值,如果得到了,就认为已经登录成功了,否则就没有登录
if username:
return func(request,*args,**kwargs)
else:
# 没有登录 重定向到登录页面
return redirect(reverse('class:login'))
return wrapper
这里的reverse中的class
是我在这个view的app下面的urls中的app_name,login是我的给path起的名字。
app_name = 'class'
urlpatterns = [
path('login/',views.login,name='login'),
path('prfile/',views.Profileview.as_view(),name='profile'),
]
然后我们就将我们写的装饰器对dispatch进行装饰,这里我们需要使用到django中对类视图装饰的方法了:
from django.utils.decorators import method_decorator
对类视图进行装饰我们一般都使用这个方法。
然后我们使用这个方法对dispatch进行装饰,在dispatch函数的上面一行写入:
@method_decorator(login_required)
装饰完之后Profileview这个类视图的完整代码为:
# 个人中心的类视图
class Profileview(View):
# 只能通过GET请求来访问
def get(self,request):
return HttpResponse('个人中心页面')
# 因为所有的请求方法在类视图中最终都会通过diapatch这个方法,
# 所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(Profileview,self).dispatch(request,*args,**kwargs)
怎样我们就实现了第一种方法对类视图进行装饰了。
就可以输入url进行测试了,例如我的个人中心网址是:
http://127.0.0.1:8000/class/prfile/
当我进行访问的时候,他就会给我们跳转至登录页面,因为我们没有传入一个值username
进去,就认为我们没有登录,所以我们想要访问个人中心就需要传入username
进去。
http://127.0.0.1:8000/class/prfile/?username=xxx
这样,我们就能访问到我们的个人中心了,说明我们的装饰器也起到了相应的效果了。
2. 直接装饰在整个类上:
但是上面的方法并不太好,因为我们在写类视图的时候,绝大多数就不会去重写dispatch方法,比如说我使用get请求,那么我就重写get方法就行了,使用post请求,重写post方法,而不用去重写dispatch方法。
那么怎样直接装饰在整个类上面呢,非常简单,只需要在类视图上面添加语句代码就行了
# 个人中心的类视图
@method_decorator(login_required,name='dispatch')
class Profileview(View):
# 只能通过GET请求来访问
def get(self,request):
return HttpResponse('个人中心页面')
# 因为所有的请求方法在类视图中最终都会通过diapatch这个方法,
# 所以我们需要重写dispatch方法,并且继承至父类的dispatch方法。
# @method_decorator(login_required)
# def dispatch(self, request, *args, **kwargs):
# return super(Profileview,self).dispatch(request,*args,**kwargs)
这样,我们就不用重写dispatch方法了,只需要在后面添加一个参数name
指定装饰的类中的哪一个方法既可以了。
当然,如果我们有多个装饰器,我们还可以指定一个列表,
@method_decorator([login_required,xx_required],name='dispatch')
但是我们这里只定义了login_required,而没有定义xx_required,所以肯定会报错的,这里只是想说一下当有多个装饰器的用法。
这样,我们就成功的对类视图进行了装饰了,推介大家使用第二种方法,不用重写dispatch方法。