玩转Django2.0---Django笔记建站基础十一(二)((音乐网站开发))

 

 

11.5  歌曲排行榜

  歌曲排行榜是通过首页的导航链接进入的,按照歌曲的播放次数进行降序显示。从排行榜页面的设计图可以看到,网页实现三个功能:网页顶部搜索、歌曲分类筛选和歌曲信息列表,其说明如下:

    1、网页顶部搜索:每个网页都具备基本功能,而且每个网页的实现方式和原理是相同的。

    2、歌曲分类筛选:根据歌曲信息表song的song_type字段对歌曲进行筛选,并显示在网页左侧的歌曲分类中。

    3、歌曲信息列表:在网页上显示播放次数排在前10条的歌曲信息。

  歌曲排行榜是由项目music的项目应用ranking实现的,我们在ranking目录下创建模板文件夹templates并且在文件夹中放置模板文件ranking.html,如下图:

 

   歌曲排行榜是由ranking的urls.py、views.py和ranking.html实现的。在ranking的urls.py中设置歌曲排行榜的URL地址信息,并在views.py中编写相应的URL处理函数,其代码如下:

#ranking/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('', views.rankingView, name='ranking'),
]

#ranking的views.py
from django.shortcuts import render
from index.models import *
def rankingView(request):
    # 搜索歌曲
    search_song = Dynamic.objects.select_related('song').order_by('-dynamic_search').all()[:4]
    # 歌曲分类列表
    All_list = Song.objects.values('song_type').distinct()
    # 歌曲列表信息
    song_type = request.GET.get('type', '')
    if song_type:
        song_info = Dynamic.objects.select_related('song').filter(song__song_type=song_type).order_by('-dynamic_plays').all()[:10]
    else:
        song_info = Dynamic.objects.select_related('song').order_by('-dynamic_plays').all()[:10]
    return render(request, 'ranking.html', locals())

  上述代码将歌曲排行榜的响应处理交给视图函数rankingView执行,并且将URL命名为ranking。视图函数rankingView一共执行了三次数据查询,其说明如下:

    1、search_song:通过歌曲的搜索次数进行降序查询,由Django内置的select_related方法实现模型Song和Dynamic的数据查询

    2、All_list:对模型Song的字段song_type进行去重查询。

    3、song_info:根据用户的GET请求参数进行数据查询。若请求参数为空,则对全部歌曲进行筛选,获取播放次数排在前10位的歌曲;若请求参数不为空,则根据参数内容进行歌曲筛选,获取播放次数排在前10为的歌曲。

  根据视图函数rankingView所生成的变量,我们在模板ranking.html中编写相关的模板语法,由于模板ranking.html的代码较多,此处指列出相关的功能代码,完整的模板代码可在下载资源中查看。模板ranking.html的功能代码如下:

#模板index.html的功能代码
#排行榜的搜索框,由HTML表单实现,{%  url 'search' XXX  %}是搜索页面的地址链接
<form id="searchForm" action="{% url 'search' 1 %}" method="post" target="_blank">
        {% csrf_token %}
            <div class="search-keyword">
                <input name="kword" type="text" class="keyword" maxlength="120" placeholder="音乐节" />
            </div>
            <input id="subSerch" type="submit" class="search-button" value="搜 索" />
        </form>

#搜索框下面的热门搜索歌曲,{%  url 'play' XXX  %}是播放页面的地址链接
<div id="suggest" class="search-suggest"></div>
          <div class="search-hot-words">
            {% for song in search_song  %}
                <a target="play" href="{% url 'play' song.song.song_id %}" >{{ song.song.song_name }}</a>
            {% endfor  %}
          </div>

#网站导航栏
<ul class="nav clearfix">
            <li><a href="/">首页</a></li>
            <li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
            <li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
        </ul>

#歌曲分类列表
<div class="side-nav">
            <div class="nav-head">
                <a href="{% url 'ranking' %}">所有歌曲分类</a>
            </div>
            <ul id="sideNav" class="cate-item">
            {% for item in All_list  %}
                <li class="computer">
                <div class="main-cate">
                    <a href="{% url 'ranking' %}?type={{ item.song_type }}" class="main-title">{{ item.song_type }}</a>
                </div>
                </li>
            {% endfor  %}
            </ul>
        </div>

#歌曲列表信息
<table class="rank-list-table">
            <tr>
                <th class="cell-1">排名</th>
                <th class="cell-2">图片</th>
                <th class="cell-3">歌名</th>
                <th class="cell-4">专辑</th>
                <th class="cell-5">下载量</th>
                <th class="cell-6">播放量</th>
            </tr>
            {% for item in song_info  %}
                <tr>
                    {%if forloop.counter < 4  %}
                    <td><span class="n1">{{forloop.counter}}</span></td>
                    {%else %}
                    <td><span class="n2">{{forloop.counter}}</span></td>
                    {%endif %}
                    <td>
                    <a href="{% url 'play' item.song.song_id %}"  class="pic" target="play"><img src="{% static "songImg/" %}{{ item.song.song_img }}"  width="80" height="80"></a>
                    </td>
                    <td class="name-cell">
                    <h3><a href="{% url 'play' item.song.song_id %}" target="play" >{{item.song.song_name}}</a></h3>
                    <div class="desc">
                    <a href="javascript:;" target="_blank" class="type" >{{item.song.song_singer}}</a>
                    </div>
                    </td>
                    <td>
                    <div style="text-align:center;">{{item.song.song_album}}</div>
                    </td>
                    <td>
                    <div style="text-align:center;">{{item.dynamic_down}}</div>
                    </td>
                    <td class="num-cell">{{item.dynamic_plays}}</td>
                </tr>
            {% endfor  %}
        </table>
ranking

  从上述代码可以看到,模板将视图函数传递的变量进行遍历输出,从而生成相应的HTML网页内容,模板代码编写逻辑与首页的模板是相同的原理。为了检验网页是否正常显示,期待启动music项目,在浏览器上访问http://127.0.0.1:8000/ranking.html,运行结果如下:

 

 歌曲排行榜

  在上述实现过程中,URL的处理方式是由视图函数rankingView完成的,而视图函数rankingView主要是想数据查询并将查询结果传递给模板,因此,我们还可以使用通用视图来完成URL处理。使用通用视图实现视图函数rankingView的功能,只需在ranking的urls.py和views.py中编写相关代码即可实现,代码如下:

#ranking/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('', views.rankingView, name='ranking'),
    # 通用视图
    path('.list', views.RankingList.as_view(), name='rankingList'),
]

#ranking/views.py
# 通用视图
from django.views.generic import ListView
class RankingList(ListView):
    # context_object_name设置Html模版的某一个变量名称
    context_object_name = 'song_info'
    # 设定模版文件
    template_name = 'ranking.html'
    # 查询变量song_info的数据
    def get_queryset(self):
        # 获取请求参数
        song_type = self.request.GET.get('type', '')
        if song_type:
            song_info = Dynamic.objects.select_related('song').filter(song__song_type=song_type).order_by('-dynamic_plays').all()[:10]
        else:
            song_info = Dynamic.objects.select_related('song').order_by('-dynamic_plays').all()[:10]
        return song_info

    # 添加其他变量
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # 搜索歌曲
        context['search_song'] = Dynamic.objects.select_related('song').order_by('-dynamic_search').all()[:4]
        # 所有歌曲分类
        context['All_list'] = Song.objects.values('song_type').distinct()
        return context

  上述代码中,我们在ranking的urls.py中设置通用视图的URL地址信息,命名为rankingList,URL的处理由views.py的RankingList执行。通用视图RankingList也是实现三次数据查询,数据查询与视图函数rankingView是相同的,最后由模板ranking.html处理变量并生成HTML网页。重启项目,在浏览器上面访问http://127.0.0.1:8000/ranking.html.list

 

11.6  歌曲播放

  在前面的章节中,网站时哦也和歌曲排行榜以数据查询为主,本节主要实现歌曲在线试听和歌曲下载功能,这也是整个网站的核心功能之一。从网站的设计图可以看到,歌曲播放页面主要实现的功能有:网页顶部搜索、歌曲的基本信息、当前播放列表、歌曲点评下载和相关歌曲推荐,功能说明如下:

    1、网页顶部搜索:每个网页具有的基本功能,而且每个网页的实现方式和原理是相同的。

    2、歌曲的基本信息:显示当前播放歌曲的基本信息,如歌名、歌手、专辑、歌曲封面和歌词等。

    3、当前播放列表:记录用户的视听记录,并且可以对当前播放的歌曲进行切换。

    4、歌曲点评下载:主要实现歌曲的点评和下载功能。歌曲点评通过地址链接进入歌曲点评页面,歌曲下载用于实现文件的下载功能。

    5、相关歌曲推荐:根据当前播放歌曲的类型进行筛选,筛选结果以歌曲的播放次数进行排序,获取前6首歌曲信息,显示在网页的最下方。

  歌曲播放是由项目music的play实现的。在play的目录下创建模板文件夹templates并且在文件夹中放置模板文件play.html,如下图:

 

play目录结构

  我们在play的urls.py、views.py和play.html中编写相关的功能代码,实现歌曲的播放与下载功能。首先在urls.py这噢乖设置歌曲播放和歌曲下载的URL地址信息,并在views.py中编写相应的URL处理函数,其代码如下:

#play/urls.py
from django.urls import path
from . import views
urlpatterns = [
    # 歌曲播放页面
    path('<int:song_id>.html', views.playView, name='play'),
    # 歌曲下载
    path('download/<int:song_id>.html',views.downloadView, name='download')
]

#play/views.py
from django.shortcuts import render
from django.http import StreamingHttpResponse
from index.models import *
# 歌曲播放页面
def playView(request, song_id):
    # 热搜歌曲
    search_song = Dynamic.objects.select_related('song').order_by('-dynamic_search').all()[:6]
    # 歌曲信息
    song_info = Song.objects.get(song_id=int(song_id))
    # 播放列表
    play_list = request.session.get('play_list', [])
    song_exist = False
    if play_list:
        for i in play_list:
            if int(song_id) == i['song_id']:
                song_exist = True
    if song_exist == False:
        play_list.append({'song_id': int(song_id), 'song_singer': song_info.song_singer, 'song_name': song_info.song_name, 'song_time': song_info.song_time})
    request.session['play_list'] = play_list
    # 歌词
    if song_info.song_lyrics != '暂无歌词':
        f = open('static/songLyric/' +song_info.song_lyrics, 'r', encoding='utf-8')
        song_lyrics = f.read()
        f.close()
    # 相关歌曲
    song_type = Song.objects.values('song_type').get(song_id=song_id)['song_type']
    song_relevant = Dynamic.objects.select_related('song').filter(song__song_type=song_type).order_by('-dynamic_plays').all()[:6]
    # 添加播放次数
    # 扩展功能:可使用session实现每天只添加一次播放次数
    dynamic_info = Dynamic.objects.filter(song_id=int(song_id)).first()
    # 判断歌曲动态信息是否存在,存在就在原来基础上加1
    if dynamic_info:
        dynamic_info.dynamic_plays += 1
        dynamic_info.save()
    # 动态信息不存在则创建新的动态信息
    else:
        dynamic_info = Dynamic(dynamic_plays=1, dynamic_search=0, dynamic_down=0, song_id=song_id)
        dynamic_info.save()
    return render(request, 'play.html', locals())

# 歌曲下载
def downloadView(request, song_id):
    # 根据song_id查找歌曲信息
    song_info = Song.objects.get(song_id=int(song_id))
    # 添加下载次数
    dynamic_info = Dynamic.objects.filter(song_id=int(song_id)).first()
    # 判断歌曲动态信息是否存在,存在就在原来基础上加1
    if dynamic_info:
        dynamic_info.dynamic_down += 1
        dynamic_info.save()
    # 动态信息不存在则创建新的动态信息
    else:
        dynamic_info = Dynamic(dynamic_plays=0,dynamic_search=0,dynamic_down=1,song_id=song_id)
        dynamic_info.save()
    # 读取文件内容
    file = 'static/songFile/' + song_info.song_file
    def file_iterator(file, chunk_size=512):
        with open(file, 'rb') as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break
    # 将文件内容写入StreamingHttpResponse对象,并以字节流方式返回给用户,实现文件下载
    filename = str(song_id) + '.mp3'
    response = StreamingHttpResponse(file_iterator(file))
    response['Content-Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment; filename="%s"' %(filename)
    return response
play

  从上述代码可以看到,play的urls.py中设有两个URL地址,分别命名为play和download。具体说明如下:

    1、命名为play的URL代表歌曲播放页面的地址,并设有参数song_id;参数song_id是当前的歌曲在歌曲信息表song中的主键,视图函数通过URL的参数来获取歌曲的信息。

    2、命名为download的URL用于实现歌曲的下载功能。在歌曲播放页面可以看到"下载"按钮,该按钮是一个URL地址链接,当用户单击"下载"按钮时,网站触发一个GET请求,该请求指向命名为download的URL,由视图函数downloadView处理并作出响应。

  由于urls.py设有两个URL地址,因此URL对应的视图函数分别是playView和downloadView。首先讲述视图函数playView实现的功能,视图函数playView分别实现4此数据查询、播放列表的设置、歌词的读取和播放次数的累加。

    1、search_song:获取热门搜索的歌曲信息,数据查询在前面的章节已讲述过,此处不再重复讲述。

    2、song_info:根据URL提供的参数song_id在歌曲信息表song中查询当前歌曲的信息。

    3、play_list:获取当前Session的play_list信息,play_list代表用户的播放记录。将URL的参数song_id与olay_list的song_id进行对比,如果两者匹配的上,说明当前歌曲已加入播放记录,如果匹配不上,将当前的歌曲信息加入play_list。

    4、song_lyrics:当前歌曲的歌词内容。首先判断当前歌曲是否存在歌词文件,如果存在,就读取歌词文件的内容并赋值给变量song_lyrics。

    5、song_relevant:根据URL提供的参数song_id在歌曲信息表song中查询当前歌曲的类型,然后根据歌曲类型查询同一类型的歌曲信息,并以歌曲的播放次数进行排序。

    6、dynamic_info:根据URL提供的参数song_id在歌曲动态表dynamic中查询当前歌曲的动态信息。如果不存在歌曲动态信息,就新建动态信息,并且播放次数累加1;如果存在歌曲动态信息,就对原有的播放次数累加1.

  接着分析视图函数downloadView实现的功能,视图函数downloadView用于实现歌曲文件的下载功能,歌曲每下载一次,就要对歌曲的下载次数累加1。因此,视图函数downloadView主要实现两个功能:歌曲下载次数的累加和文件下载,功能说明如下:

    1、dynamic_info:根据URL提供的参数song_id在歌曲动态表dynamic中查找歌曲的动态信息。如果不存在歌曲动态信息,就新建动态信息,并且下载次数累加1;如果存在歌曲动态信息,就对原有的下载次数累加1。

    2、response:网站的响应对象,由StreamingHttpResponse实例化生成,首先以字节流的方式读取歌曲文件内容,然后将文件内容写入response对象并设置response的响应类型,从而实现文件的下载功能。

  我们根据视图函数playView和downloadView的响应内容进行分析,视图playView是在浏览器上返回相关的网页,函数downloadView是直接返回歌曲文件供用户下载。因此,我们对视图函数playView所使用的模板play.html进行代码编写。由于模板代码较多,此处只列举相关的功能代码,完整的代码可在本书提供的源代码中查看。代码说明如下:

#模板play.html的共嫩代码
#歌曲播放,播放功能由Javascript实现,Django只需提供歌曲文件即可实现在线试听
<div id="jquery_jplayer_1" class="jp-jplayer" data-url={% static "songFile/" %}{{ song_info.song_file }}></div>

#歌曲封面
<div class="jp_img layz_load pic_po" title="点击播放"><img data-src={% static "songImg/" %}{{ song_info.song_img }}></div>

#歌词
<textarea id="lrc_content" style="display: none;">
                      {{ song_lyrics }}
                    </textarea>

#歌曲信息
<div class="product-price">
                        <h1 id="currentSong" >{{ song_info.song_name }}</h1>
                        <div class="product-price-info">
                            <span>歌手:{{ song_info.song_singer }}</span>
                        </div>
                        <div class="product-price-info">
                            <span>专辑:{{ song_info.song_album }}</span>
                            <span>语种:{{ song_info.song_languages }}</span>
                        </div>
                        <div class="product-price-info">
                            <span>流派:{{ song_info.song_type }}</span>
                            <span>发行时间:{{ song_info.song_release }}</span>
                        </div>
                    </div>

#播放列表
<ul class="playing-li" id="songlist">
                            <!--播放列表-->
                            {% for list in play_list %}
                             #设置当前歌曲的样式
                            {%if list.song_id == song_info.song_id %}
                            <li data-id="{{list.song_id}}" class="current">
                            {%else %}
                            <li data-id="{{list.song_id}}">
                            {%endif %}
                            #设置歌曲列表的序号、歌名和歌手
                            <span class="num">{{forloop.counter}}</span>
                            <a class="name" href="{% url 'play' list.song_id %}" target="play" >{{list.song_name}}</a>
                            <a class="singer" href="javascript:;" target="_blank" >{{list.song_singer}}</a>
                            </li>
                            {%endfor %}
                        </ul>

#相关歌曲
<ul id="" class="parts-list clearfix f_s">
                                    {% for item in song_relevant %}
                                    <li>
                                        #将当前歌曲排除显示
                                        {% if item.song.song_id != song_info.song_id %}
                                        #设置歌曲封面和歌曲播放的链接
                                        <a class="pic layz_load pic_po" href="{% url 'play' item.song.song_id %}" target="play" >
                                            <img data-src="{% static "songImg/" %}{{ item.song.song_img }}">
                                        </a>
                                        #设置歌名,歌名带播放链接
                                        <h4><a href="{% url 'play' item.song.song_id %}" target="play" >{{ item.song.song_name}}</a></h4>
                                        #设置歌手
                                        <a href="javascript:;" class="J_MoreParts accessories-more">{{ item.song.song_singer }}</a>
                                        {% endif %}
                                    </li>
                                    {% endfor %}
                                </ul>
                                              


                                          
play.html

  从上述代码可以看到,模板play.html将视图函数playView传递的变量进行遍历输出,从而生成相应的HTML网页内容。最后检验功能是否正常运行,我们重新启动music项目,在浏览器上输入:http://127.0.0.1:8000/play/6.html

 

 歌曲播放页

 

11.7  歌曲点评

  歌曲点评是通过歌曲播放页的点评按钮而进入的页面,整个网站只能通过这种方式才能访问歌曲点评页。歌曲点评页主要实现两个功能:歌曲点评和歌曲点评信息列表,功能说明如下:

    1、歌曲点评:主要为用户提供歌曲点评功能,以表单的形式实现数据提交。

    2、歌曲点评信息列表:根据URL的参数song_id查找歌曲点评表comment的相关点评内容,然后以数据列表的方式显示在网页上。

  在项目music中,歌曲点评由comment实现,在编写代码之前,在comment目录下创键模板文件夹templates并在文件夹中放置模板文件comment.html,如下图:

 

 

  调整comment目录结构后,我们在comment的urls.py、views.py和comment.html中编写相关的功能代码。首先在urls.py中设置歌曲点评的URL地址信息,并在views.py中编写URL的处理函数,其代码如下:

#comment/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('<int:song_id>.html', views.commentView, name='comment'),
]

#comment/views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render, redirect
from django.http import Http404
from index.models import *
import time
def commentView(request, song_id):
    # 热搜歌曲
    search_song = Dynamic.objects.select_related('song').order_by('-dynamic_search').all()[:6]
    # 点评提交处理
    if request.method == 'POST':
        comment_text = request.POST.get('comment','')
        comment_user = request.user.username if request.user.username else '匿名用户'
        if comment_text:
            comment = Comment()
            comment.comment_text = comment_text
            comment.comment_user = comment_user
            comment.comment_date = time.strftime('%Y-%m-%d', time.localtime(time.time()))
            comment.song_id = song_id
            comment.save()
        return redirect('/comment/%s.html' %(str(song_id)))
    else:
        song_info = Song.objects.filter(song_id=song_id).first()
        # 歌曲不存在抛出404异常
        if not song_info:
            raise Http404
        comment_all = Comment.objects.filter(song_id=song_id).order_by('comment_date')
        song_name = song_info.song_name
        page = int(request.GET.get('page', 1))
        paginator = Paginator(comment_all, 2)
        try:
            contacts = paginator.page(page)
        except PageNotAnInteger:
            contacts = paginator.page(1)
        except EmptyPage:
            contacts = paginator.page(paginator.num_pages)
        return render(request, 'comment.html', locals())

  从上述代码看到,urls.py的URL设置了参数song_id,参数值由歌曲播放页设置,我们将URL命名为comment,相应处理由视图函数commentView执行。视图函数commentView根据不同的请求方式执行不同的响应处理,具体说明如下:

  当用户从歌曲播放页进入歌曲点评页时,浏览器访问歌曲点评页的URL相当于向网站发送GET请求,视图函数commentView执行以下处理:

    1、根据URL的参数song_id查询歌曲信息表song,判断歌曲是否存在。如果歌曲不存在,网站抛出404错误信息。

    2、如果歌曲存在,在歌曲点评表comment中查询当前歌曲的全部点评信息,然后获取GET请求的请求参数page。参数page代表点评信息的分页页数,如果请求参数page不存在,默认页数为1,如果存在,将参数值转换成Int类型。

    3、根据歌曲的点评信息和页数进行分页处理,将每两条点评信息设置为一页。

  如果用户在歌曲点评页天下点评内容并单击"发布"按钮,浏览器向网站发送POST请求,POST请求由歌曲点评页的URL接收和处理吗视图函数commentView执行以下处理:

    1、首先获取表单里的点评内容,命名为comment_text,然后获取当前用户名,如果当前用户没有登录网站,用户为匿名用户,用户名为comment_user。

    2、如果comment_text不为空,在歌曲点评表comment中新增一条点评信息,分别记录点评内容、用户名、点评日期和当前歌曲在歌曲信息表的主键。

    3、最后以重定向的方式跳回歌曲点评页,网站的重定向可以防止表单多次提交,解决同一条点评信息重复创建的问题。

  下一步在模板comment.html中编写相应的功能代码,模板comment.html实现5个功能,分别是网页的搜索框、网站导航链接、歌曲点评框、点评信息列表和列表的分页导航。其中,网页的搜索框和网站导航链接在前面的章节已讲述过,此处不再重复讲解。由于模板comment.html的代码较多,本章只列出歌曲点评框、点评信息列表和列表的分页导航的实现过程,具体的代码可在本书源代码中查看。模板comment.html的功能代码如下:

#comment.html的功能代码
#歌曲点评框
<div class="comments-box">
                        <div class="comments-box-title">我要点评<<{{ song_name }}>></div>
                        <div class="comments-default-score clearfix"></div>
                        <form action="" method="post" id="usrform">
                            {% csrf_token %}
                            <div class="writebox">
                                <textarea name="comment" form="usrform"></textarea>
                            </div>
                            <div class="comments-box-button clearfix">
                                <input type="submit" value="发布" class="_j_cc_post_entry cc-post-entry" id="scoreBtn">
                                <div data-role="user-login" class="_j_cc_post_login"></div>
                            </div>
                            <div id="scoreTips2" style="padding-top:10px;"></div>
                        </form>
                    </div>

#显示当前分页的歌曲点评信息,生成点评信息列表
<ul class="comment-list">
                    {% for item in contacts.object_list %}
                        <li class="comment-item ">
                            <div class="comments-user">
                <span class="face">
                <img src="{% static "image/user.jpg" %}" width="60" height="60">
                </span>
                            </div>
                            <div class="comments-list-content">
                                <div class="single-score clearfix">
                                    <span class="date">{{ item.comment_date }}</span>
                                    <div><span class="score">{{ item.comment_user }}</span></div>
                                </div>
                                <!--comments-content-->
                                <div class="comments-content">
                                    <div class="J_CommentContent comment-height-limit">
                                        <div class="content-inner">
                                            <div class="comments-words">
                                                <p>{{ item.comment_text }}</p>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </li>
                    {% endfor %}
                </ul>

#分页导航
<div class="page-box">
                    <div class="pagebar" id="pageBar">
                        {% if contacts.has_previous %}
                            <a href="{% url 'comment' song_id %}?page={{ contacts.previous_page_number }}" class="prev"
                               target="_self"><i></i>上一页</a>
                        {% endif %}
                        {% for page in contacts.paginator.page_range %}
                            {% if contacts.number == page %}
                                <span class="sel">{{ page }}</span>
                            {% else %}
                                <a href="{% url 'comment' song_id %}?page={{ page }}" target="_self">{{ page }}</a>
                            {% endif %}
                        {% endfor %}
                        {% if contacts.has_next %}
                            <a href="{% url 'comment' song_id %}?page={{ contacts.next_page_number }}" class="next"
                               target="_self">下一页<i></i></a>
                        {% endif %}
                    </div>
                </div>

  从上述代码可以看到,歌曲点评框是一个form表单,表单通过编写HTML代码实现:点评信息列表和分页导航是在分页对象contacts的基础上实现的。重新启动music访问http://127.0.0.1:8000/comment/6.html

 

 歌曲点评页

 

 

11.8  歌曲搜索

 

  歌曲搜索也是通过触发网页顶部的搜索框而生成的网页,用户输入内容可以实现歌曲搜索,搜索结果在歌曲搜索页显示。歌曲搜索悦项目music的search实现,在search目录下创建模板文件夹templates,并在文件夹中放置模板文件search.html,如下图:

 

   从前面的章节可以知道,网页顶部的搜索框是由{%  url 'search' 1  %}的URL接收用户的搜索请求,该请求是一个POST请求。因此,我们在search的urls.py和views.py中编写相关的功能代码,代码如下:

#search的urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('<int:page>.html', views.searchView, name='search'),
]

#search的views.py
from django.shortcuts import render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q
from index.models import *

def searchView(request, page):
    if request.method == 'GET':
        # 搜索歌曲
        search_song = Dynamic.objects.select_related('song').order_by('-dynamic_search').all()[:6]
        # 获取搜索内容,如果kword为空即查询全部歌曲
        kword = request.session.get('kword', '')
        if kword:
            # Q是SQL语句里的or语法
            song_info = Song.objects.values('song_id', 'song_name', 'song_singer', 'song_time').filter(Q(song_name__icontains=kword) | Q(song_singer=kword)).order_by('-song_release').all()
        else:
            song_info = Song.objects.values('song_id', 'song_name', 'song_singer', 'song_time').order_by('-song_release').all()[:50]
        # 分页功能
        paginator = Paginator(song_info, 5)
        try:
            contacts = paginator.page(page)
        except PageNotAnInteger:
            contacts = paginator.page(1)
        except EmptyPage:
            contacts = paginator.page(paginator.num_pages)
        # 添加歌曲搜索次数
        song_exist = Song.objects.filter(song_name=kword)
        if song_exist:
            song_id = song_exist[0].song_id
            dynamic_info = Dynamic.objects.filter(song_id=int(song_id)).first()
            # 判断歌曲动态信息是否存在,存在就在原来基础上加1
            if dynamic_info:
                dynamic_info.dynamic_search += 1
                dynamic_info.save()
            # 动态信息不存在则创建新的动态信息
            else:
                dynamic = Dynamic(dynamic_plays=0, dynamic_search=1, dynamic_down=0, song_id=song_id)
                dynamic.save()
        return render(request, 'search.html', locals())
    else:
        # 处理POST请求,并重定向搜索页面。
        request.session['kword'] = request.POST.get('kword', '')
        return redirect('/search/1.html')
search

  从上述代码看到,歌曲搜索页的URL命名为search,响应处理由视图函数searchView执行,并且URL设置了参数page,该参数代表搜索结果的分页页数。在views.py中分析视图函数searchView,了解歌曲搜索的实现过程,说明如下:

    1、当用户点击搜索框的"搜索"按钮后,程序根据form表单的action所指向的URL发送一个POST请求,URL接受到请求后,将请求信息交给视图函数searchView进行处理。

    2、如果视图函数searchView收到一个POST请求,首先将请求参数kword写入用户的Session进行存储,请求参数kword是搜索框的文本输入框,然后以重定向的方式跳回歌曲搜索页的URL。

    3、当歌曲搜索页的URL以重定向的方式访问时,相当于向网站发送一个GET请求,视图函数searchView首先获取用户的Session数据,判断Session数据的kword是否存在。

    4、如果kword存在,以kword作为查询条件,分别在歌曲信息表song的字段song_name和song_singer中进行模糊查询,并将查询结果以歌曲发现时间进行排序;如果kword不存在,以歌曲发行时间的先后顺序对歌曲信息表song进行排序,并且获取前50首的歌曲信息。

    5、将查询结果进行分页处理,以每5首歌为一页的方式进行分页。其中,函数searchView的参数page是分页的页数。

    6、根据搜索内容kword查找完全匹配的歌名,只有匹配成功,才会判断歌曲的动态信息是否存在。若动态信息存在,则对该歌曲的搜索次数累加1,否则为歌曲新建一条动态信息,并将搜索次数设为1。

    7、最后将分页对象contacts传递给模板search.html,由模板引擎进行解析并生成相应的HTML网页。

  当模板search.html接收分页对象contacts时,模板引擎会对模板语法进行解析并转换成HTML网页。我们根据分页对象contacts在模板search.html中编写相关的模板语法,分页对象contacts主要实现当前分页的数据列表和分页导航功能,其模板语法如下:

#模板search的功能代码
#当前分页的数据列表
<ul class="songlist__list">
            {%for list in contacts.object_list %}
            <li class="js_songlist__child">
                <div class="songlist__item">
                    <div class="songlist__songname">
                        <span class="songlist__songname_txt">
                            <a href="{% url 'play' list.song_id %}" class="js_song" target="play" >{{list.song_name}}</a>
                        </span>
                    </div>
                    <div class="songlist__artist">
                        <a href="javascript:;" class="singer_name" >{{list.song_singer}}</a>
                    </div>
                    <div class="songlist__time">{{list.song_time}}</div>
                </div>
            </li>
            {%endfor %}
        </ul>

#分页导航功能
<div class="page-box">
            #列举全部页数按钮
            <div class="pagebar" id="pageBar">
                {% if contacts.has_previous %}
                <a href="{% url 'search' contacts.previous_page_number %}" class="prev" target="_self"><i></i>上一页</a>
                {% endif %}

                #列举全部页数按钮
                {% for page in contacts.paginator.page_range %}
                    {% if contacts.number == page %}
                        <span class="sel">{{ page }}</span>
                    {% else %}
                        <a href="{% url 'search' page %}" target="_self">{{ page }}</a>
                    {% endif %}
                {% endfor %}

                #下一页的按钮
                {% if contacts.has_next %}
                <a href="{% url 'search' contacts.next_page_number %}" class="next" target="_self">下一页<i></i></a>
                {% endif %}
            </div>
        </div>

  从上述代码可知,歌曲搜索主要是Django分页功能的应用,除此之外还涉及歌曲搜索次数的累加和Session的使用,启动项目music,在搜索框中进行两次搜索,第一次是有搜索内容进行搜索,第二次是没有搜索内容直接搜索,运行结果如下:

 

 

 

 

 

 11.9  用户注册与登录

  用户注册与登录是用户管理的必备功能之一,没有用户的注册与登录,就没有用户管理的存在。只要涉及用户方面的功能,我们都可以使用Django内置的Auth认证系统去实现。用户管理由项目music的user实现,在user目录下分别创建文件form.py和模板文件夹templates,并且在文件夹templates中创建模板文件login.html和home.html。

 

 

 

  在user目录中,新建文件分别有home.html、login.html和form.py,新建文件分别实现以下功能。

    1、home.html:用户中心的模板文件,显示当前用户的基本信息和用户的歌曲播放记录。

    2、login.html:用户注册与登录的模板文件,注册和登录功能都是由同一个模板实现的。

    3、form.py:创建用户注册的表单类,用户注册功能由表单类实现。

  由于项目music的用户管理是在Django内置的Auth认证系统的基础上实现的,因此我们采用AbstractUser方式对模型User进行扩展,在user的models.py中自定义用户模型MyUser,代码如下:

#user/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):
    qq = models.CharField('QQ号码', max_length=20)
    weChat = models.CharField('微信账号', max_length=20)
    mobile = models.CharField('手机号码', max_length=11, unique=True)
    # 设置返回值
    def __str__(self):
        return self.username

  定义模型MyUser之后,还需要在项目的配置文件settings.py中设置配置属性AUTH_USER_MODEL,否则执行数据迁移时,Django还是默认使用内置模型User。配置属性如下:

#配置文件settings.py
#配置自定义用户表MyUser
AUTH_USER_MODEL = 'user.MyUser'

  最后,在PyCharm的Terminal模式下输入数据迁移指令,在数据库中创建相应的数据表。我们打开数据库music_db查看数据表的创建情况,如下图:

 

   实现用户的注册和登录之前,除了自定义用户模型MyUser之外,还需要定义用户注册的表单类。用户注册的表单类通过重写Django内置表单类UserCreationForm即可实现,我们在user的form.py中定义表单类MyUserCreationForm,代码如下:

#user/form.py
from django.contrib.auth.forms import UserCreationForm
from .models import MyUser
from django import forms

# 定义MyUser的数据表单,用于用户注册
class MyUserCreationForm(UserCreationForm):
    # 重写初始化函数,设置自定义字段password1和password2的样式和属性
    def __init__(self, *args, **kwargs):
        super(MyUserCreationForm, self).__init__(*args, **kwargs)
        self.fields['password1'].widget = forms.PasswordInput(attrs={'class': 'txt tabInput', 'placeholder':'密码,4-16位数字/字母/特殊符号(空格除外)'})
        self.fields['password2'].widget = forms.PasswordInput(attrs={'class': 'txt tabInput', 'placeholder':'重复密码'})
    class Meta(UserCreationForm.Meta):
        model = MyUser
        # 在注册界面添加模型字段:手机号码和密码
        fields = UserCreationForm.Meta.fields +('mobile',)
        # 设置模型字段的样式和属性
        widgets = {
            'mobile': forms.widgets.TextInput(attrs={'class': 'txt tabInput', 'placeholder':'手机号'}),
            'username': forms.widgets.TextInput(attrs={'class': 'txt tabInput', 'placeholder':'用户名'}),
        }

  表单类MyUserCreationForm在父类UserCreationForm的基础上实现两个功能:添加用户注册的字段和设置字段的CSS样式,功能说明如下:

    1、添加用户注册的字段:在Meta类对fields属性设置字段即可,添加的字段必须是模型字段并且以元组或列表的形式添加。

    2、设置字段的CSS样式:设置表单字段mobile、username、password1和password2的attrs属性。其中,mobile和username是模型MyUser的字段,所以在Meta类中重写widgets属性即可实现;而password1和password2是父类UserCreationForm额外定义的表单字段,所以重写初始函数__init__可以实现字段样式设置。

  完成模型MyUser的定义、数据迁移和表单类MyUserCreationForm,接着在user的urls.py、views.py和login.html中实现用户的注册和登录功能。urls.py和views.py的功能代码如下:

#user/urls.py
from django.urls import path
from . import views
urlpatterns = [
    # 用户的注册和登录
    path('login.html', views.loginView, name='login'),
    # 退出用户登录
    path('logout.html', views.logoutView, name='logout'),
]

#user/views.py
from django.shortcuts import render, redirect
from index.models import *
from user.models import *
from .form import MyUserCreationForm
from django.db.models import Q
from django.contrib.auth import login, logout
from django.contrib.auth.hashers import check_password
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

# 用户注册与登录
def loginView(request):
    user = MyUserCreationForm()
    # 表单提交
    if request.method == 'POST':
        # 判断表单提交是用户登录还是用户注册
        # 用户登录
        if request.POST.get('loginUser', ''):
            loginUser = request.POST.get('loginUser', '')
            password = request.POST.get('password', '')
            if MyUser.objects.filter(Q(mobile=loginUser) | Q(username=loginUser)):
                user = MyUser.objects.filter(Q(mobile=loginUser) | Q(username=loginUser)).first()
                if check_password(password, user.password):
                    login(request, user)
                    return redirect('/user/home/1.html')
                else:
                    tips = '密码错误'
            else:
                tips = '用户不存在'
        # 用户注册
        else:
            user = MyUserCreationForm(request.POST)
            if user.is_valid():
                user.save()
                tips = '注册成功'
            else:
                if user.errors.get('username',''):
                    tips = user.errors.get('username','注册失败')
                else:
                    tips = user.errors.get('mobile', '注册失败')
    return render(request, 'login.html', locals())

# 退出登录
def logoutView(request):
    logout(request)
    return redirect('/')

  上述代码实现了用户注册、登录与注销功能,其中注销功能是由Django的内置函数logout实现的,此功能实现过程较为简单,此处不做过多介绍。我们主要分析用户注册和登录功能的实现过程,由于注册和登录都是使用模板login.html,因此将两个功能放在同一个视图函数loginView中进行处理。用户注册和登录的实现过程如下:

    1、首先视图函数loginView判断用户的请求方式,如果是POST请求,该请求可能是用户注册或者用户登录。

    2、由于注册和登录的文本输入框的命名不同,因此通过判断请求参数loginUser的内容是否为空即可分辨当前用户是执行用户登录还是用户注册,请求参数loginUser代表用户登录的账号。

    3、若当前请求执行的是用户登录,则以参数request的方式获取请求参数loginUser和password,然后在模型MyUser中查找相关的用户信息并进行验证处理。若验证成功,则返回用户中心页面,否则提示相应的错误信息。

    4、若当前请求执行的是用户注册,则将请求参数加载到表单类MyUserCreationForm中,生成用户对象user,然后验证用户对象user的数据。若验证成功,则在模型MyUser中创建用户信息,否则提示相应的错误信息。

  根据视图函数loginView的功能代码,在模板login.html中实现用户登录和注册的模板功能。由于模板login.html的代码较多,本章之列出用户登录和注册的功能代码,具体的代码可以在本书源代码中查看。模板login.html的功能代码如下:

#模板user/login.html的功能代码
#用户登录
<div class="login-box switch_box" style="display:block;">
                <div class="title">用户登录</div>
                <form id="loginForm" class="formBox" action="" method="post">
                    {% csrf_token %}
                    <div class="itembox user-name">
                        <div class="item">
                            <input type="text" name="loginUser" placeholder="用户名或手机号" class="txt tabInput">
                        </div>
                    </div>
                    <div class="itembox user-pwd">
                        <div class="item">
                            <input type="password" name="password" placeholder="登录密码" class="txt tabInput">
                        </div>
                    </div>
                    {% if tips %}
                        <div>提示:<span>{{ tips }}</span></div>
                    {% endif %}
                    <div id="loginBtnBox" class="login-btn">
                        <input id="J_LoginButton" type="submit" value="马上登录" class="tabInput pass-btn" />
                    </div>
                    <div class="pass-reglink">还没有我的音乐账号?<a class="switch" href="javascript:;">免费注册</a></div>
                </form>
            </div>

#用户注册
<div class="regist-box switch_box" style="display:none;">
                <div class="title">用户注册</div>
                <form id="registerForm" class="formBox" method="post" action="">
                    {% csrf_token %}
                    <div id="registForm" class="formBox">
                    #用户名
                    <div class="itembox user-name">
                    <div class="item">
                        {{ user.username }}
                    </div>
                    </div>
                    #手机号码
                    <div class="itembox user-name">
                    <div class="item">
                        {{ user.mobile }}
                    </div>
                    </div>
                    #用户密码
                    <div class="itembox user-pwd">
                    <div class="item">
                        {{ user.password1 }}
                    </div>
                    </div>
                    #重复用户密码
                    <div class="itembox user-pwd">
                    <div class="item">
                        {{ user.password2 }}
                    </div>
                    </div>
                    #信息提示
                    {% if tips %}
                        <div>提示:<span>{{ tips }}</span></div>
                    {% endif %}
                    #用户注册协议
                    <div class="member-pass clearfix">
                        <input id="agree" name="agree" checked="checked" type="checkbox" value="1"><label for="agree" class="autologon">已阅读并同意用户注册协议</label>
                    </div>
                        #注册按钮
                        <input type="submit" value="免费注册" id="J_RegButton" class="pass-btn tabInput"/>
                        #切换登录界面
                        <div class="pass-reglink">已有我的音乐帐号,<a class="switch" href="javascript:;">立即登录</a></div>
                    </div>
                </form>
            </div>
user/login.html

  从模板login.html的代码可以看到,用户登录和注册是由不同的表单分别实现的。其中,用户登录表单是由HTML代码编写的,因此视图函数loginView只能通过参数request的方式获取表单数据;用户注册表单是由Django的表单类MyUserCreationForm生成的,因此可以由MyUserCreationForm获取表单数据。

  上述例子分别列出了两种不同的表单的应用方式,两者各有优缺点,在日常开发中,应结合实际情况选择合适的表单应用方式。

 

11.10  用户中心

  用户中心是项目应用user的另一个应用页面,主要在用户登录后显示用户基本信息和用户的歌曲播放记录。因此,用户访问用户中心时,必须检验当前用户的登录状态。由于用户中心是在user中实现的,因此在user的urls.py和views.py中添加一下代码:

#urls/urls.py
from django.urls import path
from . import views
urlpatterns = [
    # 用户的注册和登录
    path('login.html', views.loginView, name='login'),
    # 用户中心
    path('home/<int:page>.html', views.homeView, name='home'),
    # 退出用户登录
    path('logout.html', views.logoutView, name='logout'),
]

#user的views.py
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

# 用户中心
# 设置用户登录限制
@login_required(login_url='/user/login.html')
def homeView(request, page):
    # 热搜歌曲
    search_song = Dynamic.objects.select_related('song').order_by('-dynamic_search').all()[:4]
    # 分页功能
    song_info = request.session.get('play_list', [])
    paginator = Paginator(song_info, 3)
    try:
        contacts = paginator.page(page)
    except PageNotAnInteger:
        contacts = paginator.page(1)
    except EmptyPage:
        contacts = paginator.page(paginator.num_pages)
    return render(request, 'home.html', locals())
user

  从上述代码可以看到,用户中心的URL命名为home,视图函数为homeView,URL的参数page代表页数。视图函数homeView实现歌曲播放记录的分页处理,歌曲播放记录来自于当前用户的播放列表,由Session的play_list进行存储。

  但从网站的需求与设计来看,用户中心主要显示当前用户的基本信息和用户的歌曲播放记录。视图函数homeView只实现歌曲播放记录,而用户信息由Django自动完成。回顾9.6节可以知道,在配置文件settings.py中设置处理器django.contrib.auth.context_processors.auth,当使用Django内置Auth实现用户登录时,Django自动生成变量user和perms并传入模板变量TemplateContext。因此,模板home.html的功能代码如下:

#home.html的功能代码
#用户信息
<div class="section_inner">
            #用户头像
            <div class="profile__cover_link">
                <img src="{% static "image/user.jpg" %}" class="profile__cover">
            </div>
            #用户名
            <h1 class="profile__tit">
                <span class="profile__name">{{ user.username }}</span>
            </h1>
                #退出登录
                <a href="{% url 'logout' %}" style="color:white;">退出登录</a>
        </div>

#歌曲列表信息
<ul class="songlist__list">
                            {% for item in contacts.object_list %}
                            <li>
                                <div class="songlist__item songlist__item--even">
                                <div class="songlist__songname">
                                    <a href="{% url 'play' item.song_id %}" class="js_song songlist__songname_txt" >{{ item.song_name }}</a>
                                </div>
                                <div class="songlist__artist">
                                    <a href="javascript:;" class="singer_name">{{ item.song_singer }}</a>
                                </div>
                                <div class="songlist__time">{{ item.song_time }}</div>
                                </div>
                            </li>
                            {% endfor %}
                        </ul>

#分页导航按钮
<div class="pagebar" id="pageBar">

                            #上一页的按钮
                            {% if contacts.has_previous %}
                            <a href="{% url 'home' contacts.previous_page_number %}" class="prev" target="_self"><i></i>上一页</a>
                            {% endif %}
                          
                             #列举全部页数按钮
                            {% for page in contacts.paginator.page_range %}
                                {% if contacts.number == page %}
                                    <span class="sel">{{ page }}</span>
                                {% else %}
                                    <a href="{% url 'home' page %}" target="_self">{{ page }}</a>
                                {% endif %}
                            {% endfor %}

                            #下一页的按钮
                            {% if contacts.has_next %}
                            <a href="{% url 'home' contacts.next_page_number %}" class="next" target="_self">下一页<i></i></a>
                            {% endif %}

                        </div>
user/home.html

  我们在浏览器上访问http://127.0.0.1:8000/user/home/1.html,查看当前用户的基本信息和歌曲播放记录,如下图:

 

 用户中心

 

 

11.11  Admin后台系统

  在前面的章节中,我们已完成网站界面的基本开发,接下来讲述网站Admin后台系统的开发。Admin后台系统主要方便网站管理员管理网站的数据和网站用户。在项目music的index和user中分别定义模型Label、Song、Dynamic、Comment和MyUser,由于index和user是两个独立的App,因此在Admin后台系统中是区分两个功能模块的。

  首先实现index在Admin后台系统的功能模块,在index的__init__.py和admin.py中分别编写一下代码:

#index的__init__.py
#对功能模块进行命名
from django.apps import AppConfig
import os
# 修改app在Admin后台显示名称
# default_app_config的值来自apps.py的类名
default_app_config = 'index.IndexConfig'

# 获取当前app的命名
def get_current_app_name(_file):
    return os.path.split(os.path.dirname(_file))[-1]

# 重写类IndexConfig
class IndexConfig(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = '网站首页'


#index的admin.py
from django.contrib import admin
from .models import *
# 修改title和header
admin.site.site_title = '我的音乐后台管理系统'
admin.site.site_header = '我的音乐'

@admin.register(Label)
class LabelAdmin(admin.ModelAdmin):
    # 设置模型字段,用于Admin后台数据的表头设置
    list_display = ['label_id', 'label_name']
    # 设置可搜索的字段并在Admin后台数据生成搜索框,如有外键应使用双下划线连接两个模型的字段
    search_fields = ['label_name']
    # 设置排序方式
    ordering = ['label_id']

@admin.register(Song)
class SongAdmin(admin.ModelAdmin):
    # 设置模型字段,用于Admin后台数据的表头设置
    list_display = ['song_id','song_name','song_singer','song_album','song_languages','song_release']
    # 设置可搜索的字段并在Admin后台数据生成搜索框,如有外键应使用双下划线连接两个模型的字段
    search_fields = ['song_name','song_singer','song_album','song_languages']
    # 设置过滤器,在后台数据的右侧生成导航栏,如有外键应使用双下划线连接两个模型的字段
    list_filter = ['song_singer','song_album','song_languages']
    # 设置排序方式
    ordering = ['song_id']

@admin.register(Dynamic)
class DynamicAdmin(admin.ModelAdmin):
    # 设置模型字段,用于Admin后台数据的表头设置
    list_display = ['dynamic_id','song','dynamic_plays','dynamic_search','dynamic_down']
    # 设置可搜索的字段并在Admin后台数据生成搜索框,如有外键应使用双下划线连接两个模型的字段
    search_fields = ['song']
    # 设置过滤器,在后台数据的右侧生成导航栏,如有外键应使用双下划线连接两个模型的字段
    list_filter = ['dynamic_plays','dynamic_search','dynamic_down']
    # 设置排序方式
    ordering = ['dynamic_id']

@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    # 设置模型字段,用于Admin后台数据的表头设置
    list_display = ['comment_id','comment_text','comment_user','song','comment_date']
    # 设置可搜索的字段并在Admin后台数据生成搜索框,如有外键应使用双下划线连接两个模型的字段
    search_fields = ['comment_user','song','comment_date']
    # 设置过滤器,在后台数据的右侧生成导航栏,如有外键应使用双下划线连接两个模型的字段
    list_filter = ['song','comment_date']
    # 设置排序方式
    ordering = ['comment_id']
index

  从上述代码可以看到,index的__init__.py用于设置功能模块的名称,admin.py分别将模型Label、Song、Dynamic和Comment注册到Admin后台系统并设置相应的显示方式。在浏览器上访问Admin后台系统并使用超级管理员帐户进行登录,在Admin的首页可以看到index的功能模块,如下图:

 

Admin后台系统

  最后在user的__init__.py和admin.py中将自定义模型MyUser注册到Admin后台系统,代码如下:

#user/__init__.py
# 设置App(user)的中文名
from django.apps import AppConfig
import os
# 修改app在admin后台显示名称
# default_app_config的值来自apps.py的类名
default_app_config = 'user.IndexConfig'

# 获取当前app的命名
def get_current_app_name(_file):
    return os.path.split(os.path.dirname(_file))[-1]

# 重写类IndexConfig
class IndexConfig(AppConfig):
    name = get_current_app_name(__file__)
    verbose_name = '用户管理'


#user/admin.py
from django.contrib import admin
from .models import MyUser
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
@admin.register(MyUser)
class MyUserAdmin(UserAdmin):
    list_display = ['username','email','mobile','qq','weChat']
    # 在用户信息修改界面添加'mobile','qq','weChat'的信息输入框
    # 将源码的UserAdmin.fieldsets转换成列表格式
    fieldsets = list(UserAdmin.fieldsets)
    # 重写UserAdmin的fieldsets,添加'mobile','qq','weChat'的信息录入
    fieldsets[1] = (_('Personal info'),
                    {'fields': ('first_name', 'last_name', 'email', 'mobile', 'qq', 'weChat')})

  由于模型MyUser继承Django内置模型User,因此将MyUserAdmin继承UserAdmin即可使用内置模型User的Admin后台功能界面,并且通过重写的方式,根据模型MyUser的定义进一步调整模型User的Admin后台功能界面。在浏览器上访问Admin后台系统,在Admin首页找到名为"用户"的地址链接并单击访问,进入用户信息列表并修改某以用户信息,可以看到个人信息新增字段的信息,如图:

 

 

 Admin后台系统

 

 

11.12  自定义异常机制

  网站的异常是一个普遍存在的问题,常见的异常以404或500为主。异常的出现主要是网站自身的数据缺陷或者认为不合理的访问所导致的。比如网站链接为http://127.0.0.1:8000/play/6.html,其中链接中6代表歌曲信息表的主键,如果在歌曲信息表中不存在该数据,那么网站应抛出404异常。

  为了完善音乐网站的异常机制,我们对网站的404异常进行自定义设置。首先在项目music的根目录的templates中加入模板error404.html,如下图所示:

 

项目music的目录结构

  由于网站的404异常是作用在整个网站的,因此在项目music的urls.py中设置404的URL信息,代码如下:

#项目music/urls.py
# 设置404、500错误状态码
from index import views
handler404 = views.page_not_found
handler500 = views.page_not_found

  可以看到,网站的404和500异常信息都是由index的视图函数page_not_found进行处理的。所以我们在index的views.py中编写视图函数page_not_found的处理过程,代码如下:

#index的views.py
# 自定义404和500的错误页面
def page_not_found(request):
    pass
    return render(request, 'error404.html', status=404)

  上述例子是网站自定义404异常信息,实现方式相对简单,只需在项目music的urls.py中设置404或500的视图函数即可实现。当网站出现异常的时候,异常处理都会由视图函数page_not_found进行处理。

 

 

11.13  项目上线部署

  由于自定义的异常功能需要项目上线后才能测试运行状况,因此我们将项目music由开发模式改为项目上线模式。首先在配置文件settings.py中关闭debug模式、设置域名访问权限和静态资源路径,代码如下:

关闭debug模型
DEBUG = False
#允许所有域名访问
ALLOWED_HOSTS = ['*']

# STATIC_ROOT用于项目部署上线的静态资源文件
STATIC_ROOT = 'e:/music/static'
# STATICFILES_DIRS用于收集admin的静态资源文件
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]

  当开启debug模式时,Django本身是提供静态资源服务的,主要方便开发者开发网站功能。当关闭debug模式时,开发模式转为项目上线模式,Django就不再提供静态资源服务,该服务应交由服务器来完成。

  在上述设置中,STATIC_ROOT和STATICFILES_DIRS都指向项目music根目录的static文件夹,首先将Admin后台的静态资源保存在static文件夹中,在PyCharm的Terminal下输入以下指令:

#Admin静态资源的收集指令
(py3_3) E:\test4\music>python manage.py collentstatic

  指令执行完毕后,我们打开项目music根目录的static文件夹,在其目录下新增admin文件夹,如下图:

 

static目录结构

  然后在项目music的urls.py中设置静态资源的读取路径。一般来说,项目上线的静态资源都是由配置属性STATIC_ROOT来决定的。因此,项目music的urls.py设置如下:

from django.contrib import admin
from django.urls import path, include
from django.conf.urls import url
from django.views import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('index.urls')),
    path('ranking.html', include('ranking.urls')),
    path('play/', include('play.urls')),
    path('comment/', include('comment.urls')),
    path('search/', include('search.urls')),
    path('user/', include('user.urls')),
    # 设置项目上线的静态资源路径
    url('^static/(?P<path>.*)$', static.serve,
        {'document_root': settings.STATIC_ROOT}, name='static')
]

  完成上述配置后,我们重启项目music并在浏览器上打开play/666.html

 

 

11.14  本章小结

  音乐网站的功能分为:网站首页、歌曲排行榜、歌曲播放、歌曲搜索、歌曲点评和用户管理,各个功能说明如下:

    1、网站首页是整个网站的主界面,主要显示网站最新的动态信息以及网站的功能导航。网站动态信息以歌曲的动态为主,如热门下载、热门搜索和新歌推荐等;网站的功能导航是将其他页面的链接展示在首页上,方便用户访问浏览。

    2、歌曲排行榜是按照歌曲的播放量进行降序,用户还可以根据歌曲类型进行自定义筛选。

    3、歌曲播放是为用户提供在线试听功能,此外还提供歌曲下载、歌曲点评和相关歌曲推荐。

    4、歌曲点评是通过歌曲播放页面进入的,每条点评信息包含用户名、点评内容和点评时间。

    5、歌曲搜索是根据用户提供的关键字进行歌曲或歌手匹配查询的,搜索结果以数据列表显示在网页上。

    6、用户管理分为用户注册、登录和用户中心。用户中心包含用户信息、登录注销和歌曲播放记录。

  网站首页主要以数据查询为主,由Django内置的ORM框架提供的API实现数据查询,查询结果主要以模板语法for标签和if标签共同实现输出并转化成相应的HTML网页。

  歌曲排行榜以GET请求进行歌曲筛选。若不存在请求参数,则将全部歌曲按播放量进行排序显示,若存在请求参数,则对歌曲进行筛选并按播放量进行排序显示。歌曲排行榜还可以使用Django的通过视图实现。

  歌曲播放主要实现文件下载、Session的应用和数据库操作。使用StreamingHttpResponse对象作为响应方式,为用户提供文件下载功能;歌曲播放列表使用Session实现,主要对Session的数据进行读写操作;数据操作主要对歌曲动态表dynamic进行数据的新增或更新。

  歌曲点评主要是由表单和分页功能。歌曲点评框是由HTML编写的表单实现的,通过视图函数的参数request获取表单数据,实现数据入库处理;分页功能是将当前歌曲的点评信息进行分页显示。

  用户管理是在Django的Auth认证系统上实现的;用户信息是在内置模型User的基础上进行扩展的;用户注册是在内置模型表单类UserCreation_Form的基础上实现的;用户登录由内置函数check_password和login共同实现;用户中心使用过滤器login_required实现访问限制,并由处理器context_processors.auth自动生成用户信息,最后使用Session和分页功能实现歌曲播放记录的显示。

  网站后台主要使用Admin后台的基本设置,如App的命名方法、Admin的标题设置和模型注册与设置。App的命名方法是由App的初始化文件__init__.py实现的,Admin的标题设置和模型注册与设置在App的admin.py中实现。

posted @ 2019-10-03 18:05  python坚持者  阅读(2143)  评论(10编辑  收藏  举报