最近在學django框架,準備用django寫一個博客園的系統,並且在寫的過程中也遇到一些問題,實踐出真知,對django開發web應用方面也有了進一步的瞭解。很多操作實現都是以我所認知的技術完成的,可能存在不合理的地方(畢竟實現的方法多種多樣),基本完成後會將源碼上傳到git,也歡迎各位大神指正。 ...
最近在學django框架,準備用django寫一個博客園的系統,並且在寫的過程中也遇到一些問題,實踐出真知,對django開發web應用方面也有了進一步的瞭解。很多操作實現都是以我所認知的技術完成的,可能存在不合理的地方(畢竟實現的方法多種多樣),基本完成後會將源碼上傳到git,也歡迎各位大神指正。
首先,要寫未登錄主站(index)。這裡需要註意文章的分類:
文章的分類切換,網站本身有定義的文章類型:
type_choices = [ (1, "Python"), (2, "Linux"), (3, "OpenStack"), (4, "GoLang"), ]
要實現主頁的分類(分類標簽樣式要突出)需要使用一個前端與後端都有的id來顯示分類。
if request.method=='GET': type_id = int(kwargs.get('type_id')) if kwargs.get('type_id') else None #後臺都是get傳參 if type_id: article_list = models.Article.objects.filter(article_type_id=type_id).extra(select={'c': "strftime('%%Y-%%m',create_time)"}) else: article_list = models.Article.objects.all().extra(select={'c': "strftime('%%Y-%%m',create_time)"}) type_choice_list = models.Article.type_choices#分類的 # print(type_choice_list)#[(1, 'Python'), (2, 'Linux'), (3, 'OpenStack'), (4, 'GoLang')]後臺代碼
{% if type_id %} <li><a href="/">全部</a></li> {% else %} <li class="active"><a href="/">全部</a></li> {% endif %} {% for item in type_choice_list %} {% if item.0 == type_id %} <li class="active"><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li> {% else %} <li><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li> {% endif %} {% endfor %}前端代碼
登陸與註冊頁面
登陸與註冊的驗證使用form表單功能完成,除此之外我們還需要有一個圖片驗證碼用於認證。
在前端設置一個圖片,圖片src屬性指向後端(獲取圖片時向後端發生get請求方式,後端返回的),驗證碼圖片由後端生成圖片在上面顯示,點擊更換我們使用每次點擊在src屬性後面加一個?,這樣url改變了前端向後臺發送一個get請求,那麼就會獲得一個新的驗證碼圖片了。
<img style="width: 120px;height: 30px;" src="/check_code/" title="點擊更換" id="change_img"> $(function(){ change_img(); }); function change_img() {//get方式在url上加?刷新圖片 $('#change_img').click(function () { $(this)[0].src=$(this)[0].src+'?'; }) }前端代碼
from PIL import Image,ImageDraw,ImageFont,ImageFilter import random def rd_check_code(width=120, height=30, char_length=4, font_file='kumo.ttf', font_size=28): code = [] img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img, mode='RGB') def rndChar(): """ 生成隨機字母 :return: """ return chr(random.randint(65, 90)) def rndColor(): """ 生成隨機顏色 :return: """ return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255)) # 寫文字 font = ImageFont.truetype(font_file, font_size) for i in range(char_length): char = rndChar() code.append(char) h = random.randint(0, 4) draw.text([i * width / char_length, h], char, font=font, fill=rndColor()) # 寫干擾點 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) # 寫干擾圓圈 for i in range(40): draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor()) x = random.randint(0, width) y = random.randint(0, height) draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor()) # 畫干擾線 for i in range(5): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) draw.line((x1, y1, x2, y2), fill=rndColor()) img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) return img,''.join(code)PIL生成隨機碼模塊
def check_code(request): from io import BytesIO from utils.random_check_code import rd_check_code img,code = rd_check_code() stream = BytesIO()#開闢一個記憶體空間,類似於文件句柄 print(stream)#io空間,<_io.BytesIO object at 0x06D6EAE0> print(img)#pillow生成的圖片對象 img.save(stream,'png') print(stream) print(stream.getvalue())#bytes類型的圖片信息,返回前端生成圖片 print(img) request.session['code'] = code#將生成的隨機字元串存到session用於驗證 return HttpResponse(stream.getvalue())後端返回隨機碼
登錄後將session信息寫入瀏覽器cookie,可以完成兩周免登陸效果。註銷我使用的時Ajax,後臺需要清理session。這個過程中要註意,Ajax需要向後臺發送自己的csrf碼,否則後端預設是偽造的跨站請求,不給予服務。
$(function () { $(".take_off").click(function () { {# $(".take_off").click(function () {#} {# $.ajaxSetup({#} {# data:{csrfmiddlewaretoken:'{{ csrf_token }}'}#} {# });#} $.ajax({ url:'/', type:'POST', {#data:{ 'csrftoken':{{ csrf_token}} },#} data:{csrfmiddlewaretoken:'{{ csrf_token }}'}, dataType:"JSON", success:function(arg){ console.log(arg); if(arg.status){ location.href='/' }else{ }} }) }); });Ajax註銷
註冊也有一個地方需要註意,就是圖片上傳的問題,我使用的是硬解碼的方式存放圖片:
with open(os.path.join('/static/imgs/', obj.cleaned_data.get('avatar').name), 'wb') as file: all = obj.cleaned_data.get('avatar').chunks() # 拿到整個文件 for trunk in all: file.write(trunk) file.close() obj.cleaned_data['avatar'] = os.path.join('/static/imgs/', obj.cleaned_data.get('avatar').name) models.UserInfo.objects.create(**obj.cleaned_data)直接在後端進行存儲
這種方法還是比較笨重的解決方法,在創建資料庫的時候有一個upload_to欄位可以直接指定文件存放路徑。
avatar = models.ImageField(verbose_name='頭像',upload_to='static/imgs')創建數據表直接指定
不過這兩種方法都不夠靈活,不能防止圖片名重覆的問題,這裡有一篇博客對存儲路徑進行優化的方式。這已經解決了很多一部分命名問題了。http://blog.csdn.net/alxandral_brother/article/details/53415551。
用Ajax完成圖片預覽功能
首先文件上傳的醜陋的介面我們是沒有辦法修改的(點擊上傳那個),所以我們使用預設圖片遮住這個文件框。
<div class="col-sm-10" style="position: relative;height:80px;width: 80px;"> <img id="previewImg" style="position: absolute;height:80px;width: 80px;" src="/static/imgs/default.png"> {{ obj.avatar }}<span>{{ obj.errors.avatar.0 }}</span> </div>
接下來是關於上傳預覽的部分,最早我們使用Ajax把前端獲取的圖片發給後端,後端接收後保存再發送回前端顯示預覽,但是這樣做會導致用戶上傳了圖片但是沒有註冊成功,那麼後端保存的圖片信息就是垃圾數據,那麼我們必須要進行定期的數據清理工作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" /> <style> .login{ width: 600px; margin: 0 auto; padding: 20px; margin-top: 80px; } .f1{ position: absolute;height:80px;width: 80px;top:0;left: 0;opacity: 0; } </style> </head> <body> <div class="login"> <div style="position: relative;height:80px;width: 80px; left:260px;top: -10px "> <img id="previewImg" style="height:80px;width: 80px;" src="/static/image/default.png"> <input id="imgSelect" style="height: 80px;width: 80px; position: absolute; top:0;left: 0; opacity: 0" type="file"> {{ obj.avatar }} </div> </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function () { bindAvartar1(); }); function bindAvartar1() { $("#imgSelect").change(function () { //$(this)[0] #jquery變成DOM對象 //$(this)[0].files #獲取上傳當前文件的上傳對象 //$(this)[0].files[0] #獲取上傳當前文件的上傳對象的某個對象 var obj = $(this)[0].files[0]; console.log(obj); //ajax 發送後臺獲取頭像路徑 //img src 重新定義新的路徑 var formdata = new FormData(); //創建一個對象 formdata.append("file",obj); var xhr = new XMLHttpRequest(); xhr.open("POST","/register/"); xhr.send(formdata); xhr.onreadystatechange = function () { if(xhr.readyState ==4){ var file_path = xhr.responseText; console.log(file_path); $("#previewImg").attr("src","/" + file_path) } }; }) } </script> </body> </html>Ajax上傳預覽
import os def register(request): if request.method == "GET": return render(request,"register.html") else: print(request.POST) print(request.FILES) file_obj = request.FILES.get("file") print(file_obj) file_path = os.path.join("static", file_obj.name) with open(file_path, "wb") as f: for chunk in file_obj.chunks(): f.write(chunk) return HttpResponse(file_path)後端保存圖片
當然,我們還可以使用本地預覽的方式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" /> <style> .login{ width: 600px; margin: 0 auto; padding: 20px; margin-top: 80px; } .f1{ position: absolute;height:80px;width: 80px;top:0;left: 0;opacity: 0; } </style> </head> <body> <div class="login"> <div style="position: relative;height:80px;width: 80px; left:260px;top: -10px "> <img id="previewImg" style="height:80px;width: 80px;" src="/static/image/default.png"> <input id="imgSelect" style="height: 80px;width: 80px; position: absolute; top:0;left: 0; opacity: 0" type="file"> {{ obj.avatar }} </div> </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function () { bindAvartar2(); }); function bindAvartar2() { $("#imgSelect").change(function () { var obj = $(this)[0].files[0]; console.log(obj); //將文件對象上傳到瀏覽器 //IE10 以下不支持 var v = window.URL.createObjectURL(obj); $("#previewImg").attr("src",v); //不會自動釋放記憶體 //當載入完圖片後,釋放記憶體 document.getElementById("previewImg").onload= function () { window.URL.revokeObjectURL(v); }; }) } function bindAvartar3() { $("#imgSelect").change(function () { var obj = $(this)[0].files[0]; console.log(obj); var reader = new FileReader(); reader.onload = function (e) { $("#previewImg").attr("src",this.result); }; reader.readAsDataURL(obj) }) } </script> </body> </html>本地上傳預覽的兩種方式
因為用戶的瀏覽器版本限制,我們可以採用多重手段給不同的用戶使用預覽功能:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" /> <style> .login{ width: 600px; margin: 0 auto; padding: 20px; margin-top: 80px; } .f1{ position: absolute;height:80px;width: 80px;top:0;left: 0;opacity: 0; } </style> </head> <body> <div class="login"> <div style="position: relative;height:80px;width: 80px; left:260px;top: -10px "> <img id="previewImg" style="height:80px;width: 80px;" src="/static/image/default.png"> <input id="imgSelect" style="height: 80px;width: 80px; position: absolute; top:0;left: 0; opacity: 0" type="file"> </div> </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function(){ bindAvatar(); }); function bindAvatar(){ if(window.URL.createObjectURL){ bindAvatar2(); }else if(window.FileReader){ bindAvatar3() }else{ bindAvatar1(); } } function bindAvatar1() { $("#imgSelect").change(function () { //$(this)[0] #jquery變成DOM對象 //$(this)[0].files #獲取上傳當前文件的上傳對象 //$(this)[0].files[0] #獲取上傳當前文件的上傳對象的某個對象 var obj = $(this)[0].files[0]; console.log(obj); //ajax 發送後臺獲取頭像路徑 //img src 重新定義新的路徑 var formdata = new FormData(); //創建一個對象 formdata.append("file",obj); var xhr = new XMLHttpRequest(); xhr.open("POST","/register/"); xhr.send(formdata); xhr.onreadystatechange = function () { if(xhr.readyState ==4){ var file_path = xhr.responseText; {# console.log(file_path);#} $("#previewImg").attr("src","/" + file_path) } }; }) } function bindAvatar2() { $("#imgSelect").change(function () { var obj = $(this)[0].files[0]; console.log(obj); //將文件對象上傳到瀏覽器 //IE10 以下不支持 //不會自動釋放記憶體 //當載入完圖片後,釋放記憶體 document.getElementById("previewImg").onload= function () { window.URL.revokeObjectURL(v); }; var v = window.URL.createObjectURL(obj); $("#previewImg").attr("src",v); }) } function bindAvatar3() { $("#imgSelect").change(function () { var obj = $(this)[0].files[0]; console.log(obj); var reader = new FileReader(); reader.onload = function (e) { $("#previewImg").attr("src",this.result); }; reader.readAsDataURL(obj) }) } </script> </body> </html>一步到位,大家都能用
主頁部分,主頁部分的主要操作就是各項分類,你可以將標簽,隨筆和時間分開寫,其實我一開始也是這麼做的,但實際上重覆代碼有很多,這些按分類展現的頁面,唯一的不同就是根據不同類型分類的文章也不同。根據這一點,我們可以將分類寫到一個視圖函數裡面,這樣代碼更為精簡。
url(r'^(?P<site>\w+)/(?P<key>((tag)|(date)|(category)))/(?P<val>\w+-*\w*)/', views.filter)
而分類的過程中主要涉及的就是ORM的操作,並且也沒有十分難的數據表操作。
文章頁
文章頁的部分主要是點贊與評論部分,先說一下評論部分,評論可以做成縮進的多級評論,但是需要將資料庫獲得的數據進行數據結構改造,快速索引。
msg_list = [ {'id':1,'content':'寫的太好了','parent_id':None}, {'id':2,'content':'你說得對','parent_id':None}, {'id':3,'content':'頂樓上','parent_id':None}, {'id':4,'content':'你眼瞎嗎','parent_id':1}, {'id':5,'content':'我看是','parent_id':4}, {'id':6,'content':'雞毛','parent_id':2}, {'id':7,'content':'你是沒呀','parent_id':5}, {'id':8,'content':'惺惺惜惺惺想尋','parent_id':3}, ] msg_list_dict = {} for item in msg_list: item['child'] = []#每一行加一個空列表child,存放子數據 msg_list_dict[item['id']] = item#每個行加一個索引的序列改造成[1;{},2:{}] # #### msg_list_dict用於查找,msg_list result = [] for item in msg_list: pid = item['parent_id'] if pid:#如果有父id msg_list_dict[pid]['child'].append(item)#加到剛纔的child列表中 else: result.append(item)#列表裡都是第一級的評論 # ########################### 列印 ################### from utils.comment import comment_tree comment_str = comment_tree(result)#自定義把所有的評論一級一級遞歸的撥開,解析成HTML格式多級評論
def comment_tree(comment_list): """ :param result: [ {id,:child:[xxx]},{}] :return: """ comment_str = "<div class='comment'>" for row in comment_list: tpl = "<div class='content'>%s</div>" %(row['content']) comment_str += tpl if row['child']: # child_str = comment_tree(row['child']) comment_str += child_str comment_str += "</div>" return comment_strutil.comment
個人覺得也可以寫成博客園的@的方式,@的回覆可跨表取到。
{% for re in reply %} <div style="background-color: #e0e0e0">{{ re.comment__create_time }}  [發言人]{{ re.comment__user__username }}</div> {% if re.comment__reply__user__username %} <p>@{{ re.comment__reply__user__username }}</p> {% else %} <p></p> {% endif %} <div style="width: 100%;border-bottom: #00b3ee 1px solid ;margin-top: 5px"> {{ re.comment__content }} </div> {% endfor %}數據可以後端跨表取
點贊要給贊綁定點擊事件,定義1為贊,0為踩,
onclick="updown(this,{{ content.nid }},1);//傳給綁定事件觸發的函數
function updown(ths,nid,val){ $.ajax({ url: '/updown.html', data:{'val':val,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'}, type: "POST", dataType:'JSON', success:function(arg){ if(arg.status){ // 點贊成功刷新頁面 location.reload(); }else{ alert(arg.msg) } } }) }綁定事件
後臺管理可以使用xadmin來做,當然也可以寫一個後臺管理,我這個後臺管理暫時使用管理員管理界面。
這兩天把後臺搭起來再把源碼上傳。我發現這個xadmin功能很強大啊,待我修習幾日直接用它來做後臺管理。