在本讲中,咱们开始首页功能的开发,在开发过程当中,你们将会学习到Django中的通用视图类、分页对象paginator以及foreignKey外键的使用。html
效果演示 前端
你们可先经过 网站演示地址 浏览一下首页的效果。咱们首页呢,比较简洁大方,让人一目了然。我这样设计的目的呢,是让你们把精力放到学习django上面来,没必要过分关注花哨的页面效果。python
咱们把首页拆解为4个小的业务模块来开发,分别是:列表显示、分页功能、搜索功能、分类功能。下面咱们分别对这四个功能模块进行开发讲解。数据库
开发一个功能的基本思路是:先新建应用,而后分析功能涉及到哪些业务,从而分析出须要的数据库字段,而后编写模型,以后就是展现阶段,经过url路由配置视图函数,来将模型里面的数据显示出来。django
ok,咱们经过命令创建应用,命名为video。执行后,django将为咱们新建video文件夹。浏览器
python3 manage.py startapp video
复制代码
下面的功能模块开发都在该应用(video)下进行。bash
此处,咱们须要创建两个模型,分别是分类表(classification)和视频表(video)。他们是多对一的关系(一个分类对应多个视频,一个视频对应一个分类)。app
首先编写Classification表,在model.py下面,咱们键入以下代码。 字段有title(分类名称)和status(是否启用)ide
class Classification(models.Model):
list_display = ("title",)
title = models.CharField(max_length=100,blank=True, null=True)
status = models.BooleanField(default=True)
class Meta:
db_table = "v_classification"
复制代码
字段说明函数
而后编写Video模型,根据网站业务,咱们设置了title(标题)、 desc(描述)、 classification(分类)、file(视频文件)、cover(封面)、status(发布状态)等字段。其中classification是一个ForeignKey外键字段,表示一个分类对应多个视频,一个视频对应一个分类(多对一)
class Video(models.Model):
STATUS_CHOICES = (
('0', '发布中'),
('1', '未发布'),
)
title = models.CharField(max_length=100,blank=True, null=True)
desc = models.CharField(max_length=255,blank=True, null=True)
classification = models.ForeignKey(Classification, on_delete=models.CASCADE, null=True)
file = models.FileField(max_length=255)
cover = models.ImageField(upload_to='cover/',blank=True, null=True)
status = models.CharField(max_length=1 ,choices=STATUS_CHOICES, blank=True, null=True)
create_time = models.DateTimeField(auto_now_add=True, blank=True, max_length=20)
复制代码
字段说明
ForeignKey 代表一种一对多的关联关系。好比这里咱们的视频和分类的关系,一个视频只能对应一个分类,而一个分类下能够有多个视频。 更多关于ForeinkKey的说明,能够参看 ForeignKey官方介绍
要想访问到首页,必须先配置好路由。在video下创建urls.py文件,写入以下代码
from django.urls import path
from . import views
app_name = 'video'
urlpatterns = [
path('index', views.IndexView.as_view(), name='index'),
]
复制代码
一条path语句就表明一条路由信息。这样咱们就能够在浏览器输入127.0.0.1:8000/video/index来访问首页了。
显示列表数据很是简单,咱们使用django中内置的视图模版类ListView来显示,首先在view.py中编写IndexView类,用它来显示列表数据。键入以下代码
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
复制代码
此处,咱们使用了django提供的通用视图类ListView, ListView使用很简单,只须要咱们简单的配置几行代码,便可将数据库里面的数据渲染到前端。好比上述代码中,咱们配置了
以后,咱们在templates文件夹下,创建video目录,用来存放视频相关的模板文件,首先咱们建立首页文件index.html。并将刚才获取到的数据显示出来。
<div class="ui grid">
{% for item in video_list %}
<div class="four wide column">
<div class="ui card">
<a class="image">
{% thumbnail item.cover "300x200" crop="center" as im %}
<img class="ui image" src="{{ im.url }}">
{% empty %}
{% endthumbnail %}
<i class="large play icon v-play-icon"></i>
</a>
<div class="content">
<a class="header">{{ item.title }}</a>
<div class="meta">
<span class="date">发布于{{ item.create_time|time_since}}</span>
</div>
<div class="description">
{{ item.view_count}}次观看
</div>
</div>
</div>
</div>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</div>
复制代码
经过for循环,将video_list渲染到前端。这里咱们使用到了django中的内置标签,好比for语句、empty语句。这些都是django中很是经常使用的语句。在以后的教程中咱们会常常遇到。
另外,还使用了thumbnail标签来显示图片,thumbnail是一个很经常使用的python库,经常被用来作图片显示。
显示结果以下
在写分类功能以前,咱们先学习一个回调函数 get_context_data() 这是ListView视图类中的一个函数,在 get_context_data() 函数中,能够传一些额外内容到模板。所以咱们可使用该函数来传递分类数据。
要使用它,很简单。
只须要在IndexView类下面,追加get_context_data()的实现便可。
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
def get_context_data(self, *, object_list=None, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
classification_list = Classification.objects.filter(status=True).values()
context['classification_list'] = classification_list
return context
复制代码
在上述代码中,咱们将分类数据经过Classification.objects.filter(status=True).values()从数据库里面过滤出来,而后赋给classification_list,最后放到context字典里面。
在前端模板(templates/video/index.html)中,就能够经过classification_list来取数据。添加代码
<div class="classification">
<a class="ui red label" href="">所有</a>
{% for item in classification_list %}
<a class="ui label" href="">{{ item.title }}</a>
{% endfor %}
</div>
复制代码
显示效果以下
固然如今只是实现了分类展现效果,咱们还须要继续实现点击效果,即点击不一样的分类,显示不一样的视频列表。
咱们先给每一个分类按钮加上href连接
<div class="classification">
<a class="ui red label" href="{% url 'home' %}">所有</a>
{% for item in classification_list %}
<a class="ui label" href="?c={{ item.id }}">{{ item.title }}</a>
{% endfor %}
</div>
复制代码
经过添加?c={{ item.id }} 这里用c表明分类的id,点击后,会传到视图类中,在视图类中,咱们使用 get_queryset() 函数,将get数据取出来。经过self.request.GET.get("c", None) 赋给c,判断c是否为None,若是为None,就响应所有,若是有值,就经过get_object_or_404(Classification, pk=self.c)先获取当前类,而后classification.video_set获取外键数据。
def get_queryset(self):
self.c = self.request.GET.get("c", None)
if self.c:
classification = get_object_or_404(Classification, pk=self.c)
return classification.video_set.all().order_by('-create_time')
else:
return Video.objects.filter(status=0).order_by('-create_time')
复制代码
更多关于ForeignKey的使用方法,可参考 这里
在Django中,有现成的分页解决方案,咱们开发者省了很多事情。若是是简单的分页,只须要配置一下paginate_by便可实现。
class IndexView(generic.ListView):
model = Video
template_name = 'video/index.html'
context_object_name = 'video_list'
paginate_by = 12
c = None
复制代码
这样每页的分页数据就能正确的显示出来来,如今来完善底部的页码条。
页码列表须要视图类和模板共同来完成,咱们先来写视图类。在前面咱们已经写过get_context_data了,该函数的主要功能就是传递额外的数据给模板。这里,咱们就利用get_context_data来传递页码数据。
咱们先定义一个工具函数,叫get_page_list。 在项目根目录下,新建一个文件helpers.py该文件看成一个全局的工具类,用来存放各类工具函数。把get_page_list放到helpers.py里面 该函数用来生产页码列表,不但这里可使用,之后在其余地方也能够调用该函数。
def get_page_list(paginator, page):
page_list = []
if paginator.num_pages > 10:
if page.number <= 5:
start_page = 1
elif page.number > paginator.num_pages - 5:
start_page = paginator.num_pages - 9
else:
start_page = page.number - 5
for i in range(start_page, start_page + 10):
page_list.append(i)
else:
for i in range(1, paginator.num_pages + 1):
page_list.append(i)
return page_list
复制代码
分页逻辑:
if 页数>=10:
当前页<=5时,起始页为1
当前页>(总页数-5)时,起始页为(总页数-9)
其余状况 起始页为(当前页-5)
复制代码
举例:
假设一共16页
状况1: 当前页==5 则页码列表为[1,2,3,4,5,6,7,8,9,10]
状况2: 当前页==8 则页码列表为[3,4,5,6,7,8,9,10,11,12]
状况3: 当前页==15 则页码列表为[7,8,9,10,11,12,13,14,15,16]
复制代码
固然你看到这个逻辑会有点乱,建议你们读着代码,多试验几遍。
当拿到页码列表,咱们继续改写get_context_data()函数。 将获取到的classification_list追加到context字典中。
def get_context_data(self, *, object_list=None, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
classification_list = Classification.objects.filter(status=True).values()
context['c'] = self.c
context['classification_list'] = classification_list
context['page_list'] = page_list
return context
复制代码
你或许对 paginator = context.get('paginator') page = context.get('page_obj')这两行代码感到陌生,咱们只须要知道context.get('page_obj')返回的是当前页码,context.get('paginator')返回的是分页对象,就够了。更加详细的介绍,可参考官方。
当数据传递给模板以后,模板就负责显示出来就好了。
由于分页功能比较经常使用,因此须要把它单独拿出来封装到一个单独的文件中,咱们新建templates/base/page_nav.html文件。而后在index.html里面咱们将该文件include进来。
{% include "base/page_nav.html" %}
复制代码
打开page_nav.html,写入代码
{% if is_paginated %}
<div class="video-page">
<div class="ui circular labels">
{% if page_obj.has_previous %}
<a class="ui circular label" href="?page={{ page_obj.previous_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}"><</a>
{% endif %}
{% for i in page_list %}
{% if page_obj.number == i %}
<a class="ui red circular label">{{ i }}</a>
{% else %}
<a class="ui circular label" href="?page={{ i }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="ui circular label" href="?page={{ page_obj.next_page_number }}{% if c %}&c={{c}}{% endif %}{% if q %}&q={{q}}{% endif %}">></a>
{% endif %}
</div>
</div>
{% endif %}
复制代码
上面代码中,咱们用到了page_obj对象的几个属性:has_previous、previous_page_number、next_page_number。经过这几个属性,便可实现复杂的页码显示效果。其中咱们还这href里面加了
{% if c %}&c={{c}}
复制代码
表明分类的id。
要实现搜索,咱们须要一个搜索框
由于搜索框是不少页面都须要的,因此咱们把代码写到templates/base/header.html文件里面。
<div class="ui small icon input v-video-search">
<input class="prompt" value="{{ q }}" type="text" placeholder="搜索视频" id="v-search">
<i id="search" class="search icon" style="cursor:pointer;"></i>
</div>
复制代码
点击搜索或回车的代码写在了static/js/header.js里面。
咱们还须要配置一下路由,添加一行搜索的路由。
app_name = 'video'
urlpatterns = [
path('index', views.IndexView.as_view(), name='index'),
path('search/', views.SearchListView.as_view(), name='search'),
]
复制代码
搜索路由指向的视图类为SearchListView
下面咱们来写SearchListView的代码
class SearchListView(generic.ListView):
model = Video
template_name = 'video/search.html'
context_object_name = 'video_list'
paginate_by = 8
q = ''
def get_queryset(self):
self.q = self.request.GET.get("q","")
return Video.objects.filter(title__contains=self.q).filter(status=0)
def get_context_data(self, *, object_list=None, **kwargs):
context = super(SearchListView, self).get_context_data(**kwargs)
paginator = context.get('paginator')
page = context.get('page_obj')
page_list = get_page_list(paginator, page)
context['page_list'] = page_list
context['q'] = self.q
return context
复制代码
关键代码就是Video.objects.filter(title__contains=self.q).filter(status=0) title__contains是包含的意思,表示查询title包含q的记录。利用filter将数据过滤出来。这里写了两层过滤,第一层过滤搜索关键词,第二层过滤status已发布的视频。
另外,这里也用到了get_context_data来存放额外的数据,包括分页数据、q关键词。
配置模板文件是templates/video/search.html
所以模板代码写在search.html里面
<div class="ui unstackable items">
{% for item in video_list %}
<div class="item">
<div class="ui tiny image">
{% thumbnail item.cover "300x200" crop="center" as im %}
<img class="ui image" src="{{ im.url }}">
{% empty %}
{% endthumbnail %}
</div>
<div class="middle aligned content">
<a class="header" href="{% url 'video:detail' item.pk %}">{{ item.title }}</a>
</div>
</div>
{% empty %}
<h3>暂无数据</h3>
{% endfor %}
</div>
{% include "base/page_nav.html" %}
复制代码
搜索功能效果