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>
從上述代碼可以看到,模板將視圖函數傳遞的變量進行遍歷輸出,從而生成相應的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的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將視圖函數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')
從上述代碼看到,歌曲搜索頁的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>
從模板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())
從上述代碼可以看到,用戶中心的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>
我們在瀏覽器上訪問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的__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中實現。