在本讲中,我们开始首页功能的开发,在开发过程中,大家将会学习到 Django 中的通用视图类、分页对象 paginator 以及 foreignKey 外键的使用。

效果演示

整体功能

大家可先通过 网站演示地址 浏览一下首页的效果。我们首页呢,比较简洁大方,让人一目了然。我这样设计的目的呢,是让大家把精力放到学习 django 上面来,不必过度关注花哨的页面效果。

我们把首页拆解为 4 个小的业务模块来开发,分别是:列表显示、分页功能、搜索功能、分类功能。下面我们分别对这四个功能模块进行开发讲解。

开发思路

开发一个功能的基本思路是:先新建应用,然后分析功能涉及到哪些业务,从而分析出需要的数据库字段,然后编写模型,之后就是展示阶段,通过 url 路由配置视图函数,来将模型里面的数据显示出来。

ok,我们通过命令建立应用,命名为 video。执行后,django 将为我们新建 video 文件夹。

python3 manage.py startapp video
复制代码

下面的功能模块开发都在该应用 (video) 下进行。

建模型

此处,我们需要建立两个模型,分别是分类表 (classification) 和视频表 (video)。他们是多对一的关系 (一个分类对应多个视频,一个视频对应一个分类)。

首先编写 Classification 表,在 model.py 下面,我们键入如下代码。 字段有 title(分类名称) 和 status(是否启用)

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 = <span class="hljs-string">"v_classification"</span>
复制代码

字段说明

  • title 分类名称。数据类型是 CharField,最大长度为 max_length=100,允许为空 null=True
  • status 是否启用。数据类型是 BooleanField, 默认为 default=True
  • db_table 表名

然后编写 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)
复制代码

字段说明

  • title 视频标题。数据类型是 charField,最大长度为 max_length=100,允许为空 null=True
  • desc 视频描述。数据类型是 charField,最大长度为 max_length=255,允许为空 null=True
  • file 视频文件地址。数据类型是 fileField。其中存的是视频文件的地址,在之后的视频管理中我们将会对视频的上传进行具体的讲解。
  • cover 视频封面。数据类型是 ImageField。存储目录为 upload_to='cover/',允许为空 null=True
  • status 视频状态。是一个选择状态,用 choices 设置多选元祖。
  • create_time 创建时间。数据类型是 DateTimeField 。设置自动生成时间 auto_now_add=True

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 使用很简单,只需要我们简单的配置几行代码,即可将数据库里面的数据渲染到前端。比如上述代码中,我们配置了

  • model = Video, 作用于 Video 模型
  • template_name = 'video/index.html' ,告诉 ListView 要使用我们已经创建的模版文件。
  • context_object_name = 'video_list' ,上下文变量名,告诉 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[<span class="hljs-string">'classification_list'</span>] = classification_list
    <span class="hljs-built_in">return</span> 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
复制代码
  • painate_by = 12 每页显示 12 条

这样每页的分页数据就能正确的显示出来来,现在来完善底部的页码条。

页码列表需要视图类和模板共同来完成,我们先来写视图类。在前面我们已经写过 get_context_data 了,该函数的主要功能就是传递额外的数据给模板。这里,我们就利用 get_context_data 来传递页码数据。

我们先定义一个工具函数,叫 get_page_list。 在项目根目录下,新建一个文件 helpers.py 该文件当作一个全局的工具类,用来存放各种工具函数。把 get_page_list 放到 helpers.py 里面 该函数用来生产页码列表,不但这里可以使用,以后在其他地方也可以调用该函数。

def get_page_list(paginator, page):
page_list = []

<span class="hljs-keyword">if</span> paginator.num_pages &gt; 10:
    <span class="hljs-keyword">if</span> page.number &lt;= 5:
        start_page = 1
    <span class="hljs-keyword">elif</span> page.number &gt; paginator.num_pages - 5:
        start_page = paginator.num_pages - 9
    <span class="hljs-keyword">else</span>:
        start_page = page.number - 5

    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(start_page, start_page + 10):
        page_list.append(i)
<span class="hljs-keyword">else</span>:
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(1, paginator.num_pages + 1):
        page_list.append(i)

<span class="hljs-built_in">return</span> 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 %}">&lt;</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 %}">&gt;</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(<span class="hljs-string">"q"</span>,<span class="hljs-string">""</span>)
    <span class="hljs-built_in">return</span> 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(<span class="hljs-string">'paginator'</span>)
    page = context.get(<span class="hljs-string">'page_obj'</span>)
    page_list = get_page_list(paginator, page)
    context[<span class="hljs-string">'page_list'</span>] = page_list
    context[<span class="hljs-string">'q'</span>] = self.q
    <span class="hljs-built_in">return</span> 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">
{% <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> video_list %}
&lt;div class=<span class="hljs-string">"item"</span>&gt;
    &lt;div class=<span class="hljs-string">"ui tiny image"</span>&gt;
        {% thumbnail item.cover <span class="hljs-string">"300x200"</span> crop=<span class="hljs-string">"center"</span> as im %}
        &lt;img class=<span class="hljs-string">"ui image"</span> src=<span class="hljs-string">"{{ im.url }}"</span>&gt;
        {% empty %}
        {% endthumbnail %}
    &lt;/div&gt;
    &lt;div class=<span class="hljs-string">"middle aligned content"</span>&gt;
      &lt;a class=<span class="hljs-string">"header"</span> href=<span class="hljs-string">"{% url 'video:detail' item.pk %}"</span>&gt;{{ item.title }}&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;
{% empty %}
&lt;h3&gt;暂无数据&lt;/h3&gt;
{% endfor %}

</div>

{% include “base/page_nav.html” %}

复制代码

搜索功能效果

  • python

    Python (发音:[ paiθ(ə)n; (US) paiθɔn ]n. 蟒蛇,巨蛇 ),是一种面向对象的解释性的计算机程序设计语言,也是一种功能强大而完善的通用型语言,已经具有十多年的发…

    7951 引用 • 22 回帖 • 2 关注
感谢    赞同    分享    收藏    关注    反对    举报    ...