涉及知识点:
- TemplateView直接渲染页面
- 视图类
一.视图层
- 列表页
from blog.models import *
from config.models import SideBar
def post_list(request, category_id=None, tag_id=None):
tag = None
category = None
if tag_id:
post_list, tag = Post.get_by_tag(tag_id)
elif category_id:
post_list, category = Post.get_by_category(category_id)
else:
post_list = Post.latest_posts()
context = {
'post_list': post_list,
'tag': tag,
'category': category,
'sidebars': SideBar.get_all(),
}
# 响应数据中加入分类信息
context.update(Category.get_navs())
return render(request, 'blog/list.html', context=context)
- 详情页
def post_detail(request, post_id):
try:
# 考虑id不存在的情况
post = Post.objects.get(id=post_id)
except Post.DoesNotExist:
post = None
tag_list = []
else:
# 多对多关系中查询出主表中所有的标签
tag_list = post.tag.all().filter(status=Tag.STATUS_NORMAL)
context = {
'post': post,
'tag_list': tag_list,
'sidebars': SideBar.get_all(),
}
context.update(Category.get_navs())
return render(request, 'blog/detail.html', context=context)
二.前端页面
- base.html:集成了通用数据:导航栏,侧边栏
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock%} - blogtype博客系统</title>
</head>
<body>
<h1>列表页</h1>
<hr>
<div>
顶部导航:
{% for cate in nav_categories %}
<!-- url反向解析,支持位置传参和关键字传参 -->
<a href="{% url 'category-list' category_id=cate.id %}">{{ cate.name }}</a>
{% endfor %}
</div>
{% block main %}{% endblock %}
<div>
底部导航:
{% for cate in normal_categories %}
<a href="{% url 'category-list' cate.id %}">{{ cate.name }}</a>
{% endfor %}
</div>
<hr>
<div>
侧边栏展示:
{% for sidebar in sidebars %}
<h4>{{ sidebar.title }}</h4>
<!-- 调用model中定义的属性方法,直接返回对应的html片段 -->
{{ sidebar.content_html }}
{% endfor %}
</div>
- list.html
{% extends './base.html'%}
<!-- extends时使用相对路径,因为此时tenplates文件夹下没有base.html -->
{% block title %}
{% if tag %}
标签页:{{ tag.name }}
{% elif category %}
分类页:{{ category.name }}
{% else %}
首页
{% endif %}
{% endblock %}
{% block main %}
<ul>
{% for post in post_list %}
<li>
<!-- 反向解析url地址, url 'name' arg1 arg2 -->
<!-- 后面跟的参数就是子组对应的内容,也可使用关键字传参 -->
<a href="{% url 'post-detail' post.id %}">{{ post.title }}</a>
<div>
<span>作者:{{ post.owner.username }}</span>
<span>分类:{{ post.category.name }}</span>
</div>
<p>摘要:{{ post.desc }}</p>
</li>
{% endfor %}
</ul>
<!-- ListView提供的分页功能 -->
{% if page_obj %}
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
page {{ page_obj.number }} of {{ paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
{% endif %}
{% endif %}
{% endblock %}
- detail.html
{% extends 'blog/base.html '%}
{% block title%} {{ post.title }} {% endblock %}
{% block main %}
{% if post %}
<h1>{{ post.title }}</h1>
<div>
<p>作者:{{ post.owner.username }}</p>
<p>分类:{{ post.category.name }}</p>
<p>标签:
{% for tag in tag_list %}
{{ tag.name }}
{% endfor %}
</p>
</div>
<p>{{ post.content }}</p>
{% endif %}
{% endblock %}
三.升级至class-based view
from django.shortcuts import render, get_object_or_404
from django.views.generic import DetailView, ListView
# 处理通用数据:导航栏和侧边栏
class CommonViewMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'sidebars': SideBar.get_all(),
})
context.update(Category.get_navs())
return context
# 详情页:DetailView继承自View,实现get方法,可以绑定某一模板并获取单个实例数据
class PostDetailView(CommonViewMixin, DetailView):
queryset = Post.latest_posts()
template_name = 'blog/detail.html'
context_object_name = 'post' # 设置模板中使用的变量名
pk_url_kwarg = 'post_id' # 设置url中的参数名,默认为pk
# 列表页:ListView继承自View,实现get方法,可通过绑定模板来批量获取数据,提供分页功能
class IndexView(CommonViewMixin, ListView):
queryset = Post.latest_posts() # 设置基础的数据集,和model属性二选一,比model多提供了过滤功能
paginate_by = 2 # 每页显示的数量
context_object_name = 'post_list' # 模板中使用的变量名,默认为object_list
template_name = 'blog/list.html'
# 分类列表页
class CategoryView(IndexView):
# 拿到需要渲染到模板中的数据
def get_context_data(self, **kwargs):
# 逻辑顺序:先调用父类IndexView中的get_context_data,但他没有,于是再去找CommonViewMixin
# 在里面又调用了一次父类中的方法,此时会去找ListView,最终拿到列表页的数据
context = super().get_context_data(**kwargs)
category_id = self.kwargs.get('category_id') # 从url中取参数
# 获取一个对象的实例,如果不存在则抛出404错误
category = get_object_or_404(Category, pk=category_id)
context.update({
'category': category,
})
return context
# 此方法获取数据源
def get_queryset(self):
"""重写queryset,根据分类过滤"""
queryset = super().get_queryset()
category_id = self.kwargs.get('category_id') # 从url中获取参数
return queryset.filter(category_id=category_id)
# 标签列表页
class TagView(IndexView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tag_id = self.kwargs.get('tag_id') # 从url中取参数
tag = get_object_or_404(Tag, pk=tag_id)
context.update({
'tag': tag,
})
return context
def get_queryset(self):
"""重写queryset,根据分类过滤"""
queryset = super().get_queryset()
tag_id = self.kwargs.get('tag_id')
return queryset.filter(category_id=tag_id)
四.class-based view的种类
- View:
实现了基于HTTP方法的分发,GET请求会调用对应的get方法,POST请求调用post方法 - TemplateView:
继承自View,实现了get方法,可传递变量到模板中;也可以直接写在URL上直接返回静态资源
url(r'^about/', TemplateView.as_view(template_name='about.html')),
- DetailView:
继承自View,实现get方法,可以绑定某一模板并获取单个实例数据 - ListView:
继承自View,实现get方法,可通过绑定模板来批量获取数据,提供分页功能
五.属性和接口
- model属性:
指定当前View要使用的Model - queryset属性:
跟Model一样,二选一,提供过滤功能 - template_name
模板名称 - get_queryset接口
获取数据源,如果设定了queryset,则会直接返回queryset - get_object接口
根据URL参数,从queryset上取得对应的实例 - get_context_data
获取渲染到模板中的所有上下文,如果要新增数据,可重写该方法来完成
六.处理流程
- as_view分发请求
- 在get请求中先调用get_queryset方法拿到数据源
- 调用get_context_data方法拿到数据
先调用get_paginate_by拿到每页的数据
接着调用get_context_object_name拿到变量名称
然后调用paginate_queryset进行分页处理 - 调用render_to_response渲染数据到页面中
在render_to_response中调用get_template_name拿到模板名
然后将request,context,template_name等传递到模板中