博客園整體上是用Django框架完成的
Django的生命周期和主要內容在前面已經講過,現在來詳述該項目的主要內容。
建一個Django項目blogCMS1,再創一個APP應用名為blog
首先我們要知道,一個網站的數據庫是由多張表組成的,而在Django中一張表對應的是一個類,所以我們首先要做的是建立好類,
這就需要我們花時間去弄明白博客園中需要多少張表去存放多少數據。
以下是我設計的表結構(13張表)

1 #項目下的model.py文件中代碼: 2 from django.db import models 3 # Create your models here. 4 from django.contrib.auth.models import AbstractUser 5 6 7 class UserInfo(AbstractUser): 8 """ 9 用戶信息,只存放最主要的字段 10 """ 11 nid = models.BigAutoField(primary_key=True) 12 nickname = models.CharField(verbose_name='昵稱', max_length=32) 13 telephone = models.CharField(max_length=11, blank=True, null=True, unique=True, verbose_name='手機號碼') 14 avatar = models.FileField(verbose_name='頭像', upload_to='avatar', default="/avatar/default.jpeg") 15 create_time = models.DateTimeField(verbose_name='創建時間', auto_now_add=True) 16 17 def __str__(self): 18 return self.username 19 20 21 class Blog(models.Model): 22 """ 23 站點信息,每個注冊用戶有一個自己的個人站點 24 """ 25 nid = models.BigAutoField(primary_key=True) 26 title = models.CharField(verbose_name='個人博客標題', max_length=64) 27 site = models.CharField(verbose_name='個人博客后綴', max_length=32, unique=True) 28 theme = models.CharField(verbose_name='博客主題', max_length=32) 29 # 與用戶信息表一對一關聯,關聯字段建在blog表中 30 user = models.OneToOneField(to='UserInfo', to_field='nid') 31 32 def __str__(self): 33 return self.title 34 35 36 class Article(models.Model): 37 """ 38 文章表,存放着所有該用戶站點發布的文章 39 """ 40 nid = models.BigAutoField(primary_key=True) 41 title = models.CharField(max_length=50, verbose_name='文章標題') 42 desc = models.CharField(max_length=255, verbose_name='文章描述') 43 read_count = models.IntegerField(default=0) 44 comment_count = models.IntegerField(default=0) 45 up_count = models.IntegerField(default=0) 46 down_count = models.IntegerField(default=0) 47 create_time = models.DateTimeField(verbose_name='創建時間', auto_now_add=True) 48 #一個文章只能屬於一個分類,而一個分類下有多個文章,所以外鍵建在文章表 49 category = models.ForeignKey(verbose_name='文章類型', to='Category', to_field='nid', null=True) 50 #一個用戶可以發表多篇文章,而一個文章只能由一個用戶發表 51 user = models.ForeignKey(verbose_name='所屬用戶', to='UserInfo', to_field='nid') 52 #文章中有關鍵字,關鍵字可以看做是標簽,文章與標簽的關系是多對多 53 tags = models.ManyToManyField( 54 to="Tag", 55 through='Article2Tag', 56 through_fields=('article', 'tag'),#自己建的多對多關聯的第三張表 57 ) 58 #博客園首頁的總的文章大分類下的小分類,與個人站點分類無關,該分類與文章也是一對多關系 59 site_article_category=models.ForeignKey("SiteArticleCategory",null=True) 60 61 def __str__(self): 62 return self.title 63 64 65 class ArticleDetail(models.Model): 66 """ 67 文章詳細表 68 """ 69 nid = models.AutoField(primary_key=True) 70 content = models.TextField(verbose_name='文章內容', ) 71 #若文章表將所有文章內容存入,數據量會很大,所以我們用一個詳情表存儲,與文章表一對一 72 article = models.OneToOneField(verbose_name='所屬文章', to='Article', to_field='nid') 73 def __str__(self): 74 return self.article.title 75 76 77 class Category(models.Model): 78 """ 79 博主個人文章分類表 80 """ 81 nid = models.AutoField(primary_key=True) 82 title = models.CharField(verbose_name='分類標題', max_length=32) 83 #與站點進行多對一關聯,外鍵必須建在多的一方 84 blog = models.ForeignKey(verbose_name='所屬博客', to='Blog', to_field='nid') 85 86 def __str__(self): 87 return self.title 88 89 class Meta: 90 verbose_name = 'category' 91 verbose_name_plural = 'category' 92 ordering = ['title'] 93 94 95 class Tag(models.Model): 96 """ 97 標簽表 98 """ 99 nid = models.AutoField(primary_key=True) 100 title = models.CharField(verbose_name='標簽名稱', max_length=32) 101 blog = models.ForeignKey(verbose_name='所屬博客', to='Blog', to_field='nid') 102 103 104 class Article2Tag(models.Model): 105 """ 106 文章和標簽的多對多關系表,自己創第三張表的好處是可以自定義表的字段 107 """ 108 nid = models.AutoField(primary_key=True) 109 article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid') 110 tag = models.ForeignKey(verbose_name='標簽', to="Tag", to_field='nid') 111 112 class Meta: 113 #文章和標簽聯合唯一 114 unique_together = [ 115 ('article', 'tag'), 116 ] 117 118 119 class Comment(models.Model): 120 """ 121 評論表 122 """ 123 nid = models.BigAutoField(primary_key=True) 124 content = models.CharField(verbose_name='評論內容', max_length=255) 125 create_time = models.DateTimeField(verbose_name='創建時間', auto_now_add=True) 126 up_count = models.IntegerField(default=0)#點贊數 127 128 user = models.ForeignKey(verbose_name='評論者', to='UserInfo', to_field='nid') 129 article = models.ForeignKey(verbose_name='評論文章', to='Article', to_field='nid') 130 #自關聯,用於判斷該評論是對文章的評論還是對評論的評論 131 parent_comment = models.ForeignKey('self', blank=True, null=True, verbose_name='父級評論') 132 133 def __str__(self): 134 return self.content 135 136 137 class CommentUp(models.Model): 138 """ 139 點評論贊表 140 """ 141 142 nid = models.AutoField(primary_key=True) 143 user = models.ForeignKey('UserInfo', null=True) 144 comment = models.ForeignKey("Comment", null=True) 145 146 147 class ArticleUp(models.Model): 148 """ 149 點文章贊表 150 """ 151 nid = models.AutoField(primary_key=True) 152 user = models.ForeignKey('UserInfo', null=True) 153 article = models.ForeignKey("Article", null=True) 154 155 156 class ArticleDown(models.Model): 157 """ 158 點down表 159 """ 160 nid = models.AutoField(primary_key=True) 161 user = models.ForeignKey('UserInfo', null=True) 162 article = models.ForeignKey("Article", null=True) 163 164 165 class SiteCategory(models.Model): 166 """ 167 首頁的大分類表,與個人站點分類無關,該分類與文章不直接綁定關系, 168 因為該分類下還有小分類,小分類才與文章綁定關系 169 170 大分類(例如計算機編程語言)——> 171 小分類(例如python——>文章1 172 文章2 173 …… 174 java——>文章() 175 …… 176 c++——>文章() 177 …… 178 ……) 179 180 """ 181 name=models.CharField(max_length=32) 182 183 184 def __str__(self): 185 return self.name 186 187 class SiteArticleCategory(models.Model): 188 """ 189 首頁的大分類下的小分類表,與大分類是多對一關系,與個人站點的分類無關 190 """ 191 name=models.CharField(max_length=32) 192 site_category=models.ForeignKey('SiteCategory') 193 194 195 def __str__(self): 196 return self.name
建立完表關系后,數據庫就有了。接下來我們分配路由。
首先我們知道一個網站必須有一個主網頁即首頁,其次肯定還有其他的網頁內容,所以我們需要匹配很多的URL。原則上是有一個網頁的需求配一個URL,然后再分配好視圖函數view,最后再關聯上相應的模板頁面html。
在這里我將所有URL分為兩部分:
第一部分:網站的主要網址
第二部分:與個人站點有關的網址

1 #urls: 2 from django.conf.urls import url,include 3 from django.contrib import admin 4 from django.views.static import serve 5 from blog import views 6 from blogCMS1 import settings 7 8 urlpatterns = [ 9 url(r'^admin/', admin.site.urls),#Django自帶 10 11 url(r'^reg/', views.reg), # 注冊功能 12 url(r'^login/', views.log_in),#登錄功能 13 url(r'^log_out/', views.log_out),#注銷功能 14 url(r'^get_validCode_img/', views.get_validCode_img), # 驗證碼功能 15 url(r'^index/', views.index),#首頁功能 16 url(r'^$', views.index),#只輸入域名的情況下默認進入首頁 17 url(r'^cate/(?P<site_article_category>.*)/$', views.index), # 首頁的小分類 18 # index(requset,site_article_category=python) 19 20 # 個人站點首頁,將個人站點需要用到的路由下發到blog下的urls中 21 url(r'^blog/', include('blog.urls')),#路由分發 22 23 24 #media 配置:我們將網站中上傳的文件放置於media文件夾中, 25 # 因為該文件夾是新創建的 26 # 所以我們需要在settings中進行相應配置,稍后會有配置的代碼 27 28 url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), 29 url(r'^uploadFile/$', views.uploadFile),#添加文章時上傳圖片 30 31 ]

1 #個人應用下的urls(這里應用名為blog): 2 from django.conf.urls import url,include 3 from blogCMS1 import settings 4 from blog import views 5 urlpatterns = [ 6 #用戶修改個人資料功能 7 url(r'^edituser/(\d+)$', views.edituser), 8 #個人站點首頁,需要渲染個人分類、標簽和日期的歸檔,所以需要用到正則 9 url(r'^(?P<username>.*)/(?P<condition>tag|category|date)/(?P<para>.*)', views.homeSite), 10 #路由反向解析,在模板頁面中直接用別名即可 11 url(r'^(?P<username>.*)', views.homeSite,name="aaa"), 12 #文章詳情頁面 13 url(r'^(?P<username>.*)/article_detail/(?P<article_nid>\d+)', views.article_detail), 14 #文章贊、踩功能 15 url(r'^updown/$', views.updown), 16 url(r'^comment/$', views.comment), #文章評論功能 17 #顯示文章已發表的評論,樹狀結構 18 url(r'^commentTree/(?P<article_id>\d+)$', views.commentTree), 19 url(r'^backend/$', views.backendindex),#后台管理首頁 20 url(r'^backend/addarticle/$', views.addarticle),#后台管理添加新文章功能 21 url(r'^backend/editarticle/(\d+)$', views.editarticle),#編輯文章 22 url(r'^backend/del/(\d+)', views.delarticle),#刪除文章 23 24 ]
上面提到了media配置,這里我們要提一下。media是靜態文件,Django中的靜態文件有static和media兩種,前者用來存放css、js等文件,后者則是用戶上傳的圖片。這兩種靜態文件都需要我們自己在settings中進行配置后才能使用。
''' 靜態文件的處理又包括STATIC和MEDIA兩類,這往往容易混淆,在Django里面是這樣定義的: #static的配置: STATIC_URL = '/static/' # 別名 STATICFILES_DIRS = ( os.path.join(BASE_DIR,"static"), #實際名 ,即實際文件夾的名字 ) ''' 注意點1: django對引用名和實際名進行映射,引用時,只能按照引用名來,不能按實際名去找 <script src="/statics/jquery-3.1.1.js"></script> ------error-----不能直接用,必須用STATIC_URL = '/static/': <script src="/static/jquery-3.1.1.js"></script> 注意點2: STATICFILES_DIRS = ( ("app01",os.path.join(BASE_DIR, "app01/statics")), ) <script src="/static/app01/jquery.js"></script> ''' ################################### MEDIA:指用戶上傳的文件,比如在Model里面的FileFIeld,ImageField上傳的文件。如果你定義 MEDIA_ROOT=c:\temp\media,那么File=models.FileField(upload_to="abc/")#,上傳的文件就會被保存到c:\temp\media\abc eg: class blog(models.Model): Title=models.charField(max_length=64) Photo=models.ImageField(upload_to="photo") 上傳的圖片就上傳到c:\temp\media\photo,而在模板中要顯示該文件,則在這樣寫 在settings里面設置的MEDIA_ROOT必須是本地路徑的絕對路徑,一般是這樣寫: BASE_DIR= os.path.abspath(os.path.dirname(__file__)) MEDIA_ROOT=os.path.join(BASE_DIR,'media/').replace('\\','/') MEDIA_URL是指從瀏覽器訪問時的地址前綴,舉個例子: MEDIA_ROOT=c:\temp\media\photo MEDIA_URL="/data/" 在開發階段,media的處理由django處理: 訪問http://localhost/data/abc/a.png就是訪問c:\temp\media\photo\abc\a.png 在模板里面這樣寫<img src="/media/abc/a.png"> 在部署階段最大的不同在於你必須讓web服務器來處理media文件,因此你必須在web服務器中配置, 以便能讓web服務器能訪問media文件 以nginx為例,可以在nginx.conf里面這樣: location ~/media/{ root/temp/ break; } 具體可以參考如何在nginx部署django的資料。 '''
在本項目中,static和media的配置代碼如下:
STATIC_URL = '/static/'#別名,引用時的名字 STATICFILES_DIRS=[ os.path.join(BASE_DIR, 'blog','static'),#別名所指的實際文件夾路徑 ] #別名所指的實際文件夾路徑 MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media","uploads") MEDIA_URL="/media/"#別名 #model中userinfo表繼承auth模塊 AUTH_USER_MODEL = "blog.UserInfo"
以上全部創建完畢,進行數據庫遷移后的項目目錄應該是下面這個樣子
接下來我們要做的就是對視圖函數進行編寫,視圖函數應該對應URL進行編寫,並映射到相應的模板中。
注:並不是一條視圖函數對應一個模板,根據實際需求,有的視圖函數也許是重定向功能。
網站首頁:
url(r'^index/', views.index),#首頁功能
url(r'^$', views.index),#只輸入域名的情況下默認進入首頁
url(r'^cate/(?P<site_article_category>.*)/$', views.index), # 首頁的小分類
# index(requset,site_article_category=python)
#views中:
from django.shortcuts import render,HttpResponse,redirect
def index(request,*args,**kwargs): print('首頁首頁首頁首頁首頁首頁首頁首頁首頁') if kwargs:#用戶點擊超鏈接,顯示指定小分類下的文章 article_list=models.Article.objects.filter(site_article_category__name=kwargs.get("site_article_category")) else:#默認,顯示全部文章 article_list=models.Article.objects.all() cate_list=models.SiteCategory.objects.all()#所有大分類 return render(request,'index.html',{'article_list':article_list,'cate_list':cate_list})

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>首頁</title> <script src="/static/jquery-3.2.1.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <link rel="stylesheet" href="/static/mycss/index.css"> </head> <body> {#導航條#} <nav class="navbar navbar-inverse"> <div class="container"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客園</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="/">首頁 <span class="sr-only">(current)</span></a></li> <li><a href="#">Link</a></li> <li><a href="#">Link</a></li> <li><a href="#">Link</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if request.user.is_authenticated %} {# 用戶登錄狀態,顯示用戶信息#} <li class="active"><a>歡迎:</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> {{ request.user.username }} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="/blog/{{ request.user.username }}">個人首頁</a></li> <li><a href="/blog/backend/">文章管理</a></li> <li><a href="/blog/edituser/{{ request.user.nid }}">修改資料</a></li> <li role="separator" class="divider"></li> <li><a href="/log_out/">注銷</a></li> </ul> </li> {% else %} {# 未登錄狀態,顯示登錄、注冊按鈕#} <li><a href="/login/">登錄</a></li> <li><a href="/reg/">注冊</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> {#內容#} <div class="container"> <div class="row"> <div class="left1 col-md-2"> {# 左側菜單#} <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">網站分類</h3> </div> <div class="panel-body"> {% for cate in cate_list %} {# 文章大分類歸檔#} <div class="panel panel-info menu"> <div class="panel-heading "> <h3 class="panel-title"> {{ cate.name }} </h3> </div> <div class="panel-body hides"> {% for sitearticlecategory in cate.sitearticlecategory_set.all %} {# 文章小分類#} <p><a href="/cate/{{ sitearticlecategory.name }}">{{ sitearticlecategory.name }}</a> </p> {% endfor %} </div> </div> {% endfor %} </div> </div> </div> {# 文章顯示部分#} <div class="middle1 col-md-8"> {% for article in article_list %} <div class="article"> <div class="articlt-head"> <a href="/blog/{{ article.user.username }}/article_detail/{{ article.nid }}">{{ article.title }}</a> </div> <div class="content row"> <div class="col-md-2"> {# 用戶頭像#} <a href="{% url 'aaa' article.user.username %}"><img src="{{ article.user.avatar.url }}" alt="" width="100" height="100"></a> </div> <div class="col-md-10"> {{ article.desc }} </div> </div> <div class="pub-info row">{#發布信息#} <a href="/blog/{{ article.user.username }}"> {{ article.user.username }} </a> <span>發布於</span> {{ article.user.create_time|date:"Y-m-d H:i" }} <a href="/blog/{{ article.user.username }}/article_detail/{{ article.nid }}"><span class="glyphicon glyphicon-comment"></span>評論({{ article.comment_count }})</a> <a href="/blog/{{ article.user.username }}/article_detail/{{ article.nid }}"><span class="glyphicon glyphicon-thumbs-up"></span>點贊({{ article.up_count }})</a> </div> </div> {% endfor %} </div> <div class="right1 col-md-2"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> <script> $('.menu').mouseover(function () { {# 鼠標移動到大分類上時顯示它所屬的小分類#} $(this).children().slideDown(300) }).mouseleave(function () { {# 鼠標從大分類上移開時隱藏它所屬的小分類#} $(this).children(".panel-body").slideUp(300) }) </script> </body> </html>

.left1 { text-align: center; font-size: large; } .articlt-head{ font-size: 20px; margin-bottom: 10px; } .article{ padding: 10px 10px; border-bottom: solid 1px; } .hides{ display: none; }
注冊頁面:
url(r'^reg/', views.reg), # 注冊功能
這里我們會用到一個form組件,form組件可以幫我們更方便的處理有關form表單的操作。在blog下新建一個form.py,專門用來寫form組件。

#form.py中: from django import forms from django.forms import widgets,ValidationError from blog import models #注冊頁面的form組件 class RegForm(forms.Form):#必須繼承 username=forms.CharField(max_length=32, min_length=5, error_messages={'required':'用戶名不能為空'}, widget=widgets.TextInput(attrs={"class":"form-control","placeholder":"請輸入用戶名"}) ) password=forms.CharField(max_length=32, min_length=6, widget=widgets.PasswordInput( attrs={"class": "form-control", "placeholder": "請輸入密碼"} )) repeat_pwd = forms.CharField(min_length=6, widget=widgets.PasswordInput( attrs={"class": "form-control", "placeholder": "請再次輸入密碼"} )) email = forms.EmailField(widget=widgets.EmailInput( attrs={"class": "form-control", "placeholder": "請輸入郵箱"} )) def clean_username(self): """ 鈎子函數,判斷用戶名是否已存在 :return: """ ret = models.UserInfo.objects.filter(username=self.cleaned_data.get("username")) if not ret: return self.cleaned_data.get("username") else: raise ValidationError("用戶名已注冊") def clean_password(self): """ 鈎子函數,規定密碼不能是全數字或全字母 :return: """ ret = self.cleaned_data.get("password") if ret.isdigit(): raise ValidationError("密碼不能全是數字") elif ret.isalpha(): raise ValidationError("密碼不能全是字母") else: return ret def clean(self): """ 全局鈎子函數,判斷兩次密碼是否一致 :return: """ if self.cleaned_data.get("password") == self.cleaned_data.get("repeat_pwd"): return self.cleaned_data else: raise ValidationError("兩次密碼不一致")
#views中:
def reg(request): if request.is_ajax():#ajax提交 form_obj=RegForm(request.POST)#拿到頁面提交的form對象 print('................',form_obj) regResponse={"user":None,"errorsList":None} if form_obj.is_valid():#數據正常 username=form_obj.cleaned_data["username"] password=form_obj.cleaned_data["password"] email=form_obj.cleaned_data.get("email") pic=request.FILES.get("pic")#頭像 #用戶表中添加一條信息,即增加一條表記錄 user_obj=models.UserInfo.objects.create_user(username=username,password=password,email=email,avatar=pic,nickname=username) print(user_obj.avatar,"......")#路徑 regResponse["user"]=user_obj.username else:#數據異常 regResponse["errorsList"]=form_obj.errors#異常信息 import json #ajax返回的數據必須是json字符串 return HttpResponse(json.dumps(regResponse)) form=RegForm()#實例化組件 return render(request,'reg.html',{'form':form})

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>注冊</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.2.1.min.js"></script> <link rel="stylesheet" href="/static/mycss/reg.css"> <script src="/static/js/jquery.cookie.js"></script> </head> <body> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action=""> {% csrf_token %}{#Django自帶的防止攻擊的中間件#} <div class="form-group"> <label for="username">用戶名:</label> {{ form.username }} </div> <div class="form-group"> <label for="password">密碼:</label> {{ form.password }} </div> <div class="form-group"> <label for="repeat_pwd">確認密碼:</label> {{ form.repeat_pwd }} </div> <div class="form-group"> <label for="email">郵箱:</label> {{ form.email }} </div> <div class="form-group picDiv"> <label for="picBtn">頭像:</label> <img id="pic" class="pic" src="/static/img/default.png" alt=""> <input id="picBtn" type="file" class="picBtn"> </div> <div class="sub" style="padding-top: 50px"> <input type="button" value="submit" class="btn btn-primary" id="subBtn"><span class="error"></span> </div> </form> </div> </div> </div> <script> // 頭像預覽,固定代碼 $("#picBtn").change(function () { var ele_file=$(this)[0].files[0]; //this.files var reader=new FileReader(); reader.readAsDataURL(ele_file); reader.onload=function () { $("#pic")[0].src=this.result } }); // ajax提交數據 $("#subBtn").click(function () { var formdata=new FormData(); formdata.append("username",$("#id_username").val()); formdata.append("password",$("#id_password").val()); formdata.append("repeat_pwd",$("#id_repeat_pwd").val()); formdata.append("email",$("#id_email").val()); formdata.append("pic",$("#picBtn")[0].files[0]); $.ajax({ url:"/reg/", type:'POST', data:formdata, contentType:false, processData:false, headers:{"X-CSRFToken":$.cookie('csrftoken')},//中間件的值,必須寫 success:function (data) { console.log(data); var data=JSON.parse(data);//將后端傳來的json字符串轉成js字符串 if (data.user){ // 注冊成功自動跳轉至登錄頁面 location.href="/login/" } else { // 查看錯誤信息 console.log(data.errorsList); $.each(data.errorsList,function (i,j) { console.log(i,j); // 將錯誤信息打印到相對應的輸入框下仿 $span=$("<span>"); $span.addClass("pull-right").css("color","red"); $span.html(j[0]); $("#id_"+i).after($span).parent().addClass("has-error") if (i=="__all__"){ // 全局鈎子捕捉到錯誤 $("#id_repeat_pwd").after($span) } }) } } }) }) </script> </body> </html>

.container{ margin-top: 100px; } .picDiv{ position: relative; width: 40px; height: 40px; } #pic{ position: absolute; top:0; left: 50px; width: 40px; height: 40px; } #picBtn{ opacity: 0; position: absolute; top:0; left: 50px; width: 40px; height: 40px; } .sub{ padding-top: 100px; }
登錄頁面:
url(r'^login/', views.log_in),#登錄功能
url(r'^get_validCode_img/', views.get_validCode_img), # 驗證碼功能
#views中: def log_in(request): if request.is_ajax(): username=request.POST.get('username') password=request.POST.get('password') validCode=request.POST.get('validCode') login_response={"is_login":False,"error_msg":None} if validCode.upper()==request.session.get("keepValidCode").upper():#檢驗驗證碼 user=auth.authenticate(username=username,password=password)#驗證碼正確時校驗用戶 if user:#用戶正確 login_response["is_login"]=True auth.login(request,user) # session request.session[is_login]=True else:#用戶名或密碼輸入錯誤 login_response["error_msg"] = "username or password error" else:#驗證碼錯誤 login_response["error_msg"]='validCode error' import json #檢驗通過 return HttpResponse(json.dumps(login_response)) return render(request,"login.html") ############################## import random def get_validCode_img(request): def random_color():#隨機顏色 the_color = random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) return the_color def random_int():#隨機數字 x_y=random.randint(0,150) return x_y from io import BytesIO from PIL import Image,ImageDraw,ImageFont img=Image.new(mode='RGB',size=(120,40),color=random_color()) # img = Image.new(mode="RGB", size=(120, 40), color=(random.randint(0,255),random.randint(0,255),random.randint(0,255))) draw=ImageDraw.Draw(img,mode='RGB') font=ImageFont.truetype("blog/static/font/kumo.ttf",25) valid_list=[] for i in range(5): random_num=str(random.randint(0,9))#數字 random_lower=chr(random.randint(65,90))#小寫字母 random_upper=chr(random.randint(97,122))#大寫字母 random_char=random.choice([random_num,random_lower,random_upper])#隨機選 draw.text([5+i*24,10],random_char,random_color(),font=font) #(坐標, 隨機文本 , 隨機顏色, 字體) for i in range(100):#畫噪點 draw.point([random_int(), random_int()], fill=random_color()) for i in range(3):#畫線 draw.line((random_int(), random_int(), random_int(), random_int()), fill=random_color()) valid_list.append(random_char) #寫入內存中 f = BytesIO() img.save(f, "png")#以png格式 data = f.getvalue() valid_str = "".join(valid_list) request.session["keepValidCode"] = valid_str#將驗證碼的字符串寫入session中,方便以后校驗 return HttpResponse(data)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>登錄</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.2.1.min.js"></script> <link rel="stylesheet" href="/static/mycss/login.css"> <script src="http://static.geetest.com/static/tools/gt.js"></script> </head> <body> <div class="container popup"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form method="post"> {% csrf_token %} <div class="form-group"> <label for="username">username</label> <input type="text" class="inp form-control" id="username" placeholder="請輸入你的用戶名"> </div> <div class="form-group"> <label for="Password">Password</label> <input type="password" class="inp form-control" id="password" placeholder="請輸入你的密碼"> </div> <div class="row validCode"> <div class="col-md-6"> <div class="form-group"> <label for="validCode">驗證碼</label> <input type="validCode" class="form-control" id="validCode" placeholder="請輸入驗證碼"> </div> </div> <div class="col-md-6"> {#驗證碼圖片,路由獲得#} <img class="validCode_img" src="/get_validCode_img/" alt="" width="200px" height="50px"> </div> </div> <input type="btn" value="登錄" class="btn btn-primary"><span class="errorMsg"></span> </form> <a href="/reg/">暫無賬號,點我注冊</a> </div> </div> </div> <script> $('.btn').click(function () { $.ajax({ url: '/login/', type: 'POST', data: { "username": $('#username').val(), "password": $('#password').val(), "validCode": $('#validCode').val(), "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(), }, success: function (data) { var response = JSON.parse(data); var prevLink = document.referrer; if (response["is_login"]) { alert(prevLink) location.href = prevLink// 登陸后跳轉回原頁面 } else { $(".errorMsg").html(response["error_msg"]).css("color", "red") } } }) }) $('.validCode_img').click(function () { $(this)[0].src += '?'// 點擊驗證碼圖片時刷新驗證碼 }) </script> </form> </body> </html>

.container{ margin-top: 100px; } .validCode .validCode_img{ margin-top: 20px; }
注銷功能:
url(r'^log_out/', views.log_out),#注銷功能
利用auth模塊可以很方便的進行注銷,沒有對應的模板,重定向到登錄頁面
def log_out(request): auth.logout(request) return redirect("/login/")
到這里為止,有關首頁的功能我們就基本完善好了。接下來要做的都是與個人站點有關的操作,所以之后我們的路徑就都映射到blog文件夾下的urls文件中(新建的.py文件)
需在原來的urls.py文件中加一句路由分發:
url(r'^blog/', include('blog.urls')),#路由分發 #之后的所有以/blog/開頭的路由都匹配到blog下的urls.py文件中
好,下面我們來做個人站點的主頁
from django.conf.urls import url,include from blogCMS1 import settings from blog import views urlpatterns = [ url(r'^(?P<username>.*)/(?P<condition>tag|category|date)/(?P<para>.*)', views.homeSite), ]
下面來解釋一下上段代碼:
首先是正則表達式有名分組,因為每個人都有自己的站點,所以我們根據所傳的用戶名不同來確定用戶進入的是哪一個用戶的主頁(每個用戶都可以互相查看主頁,並不是用戶只能看自己的主頁)
第二個有名分組的作用,我們在主頁的左側會有一個菜單欄,菜單欄上要渲染出每個用戶站點各自的文章分類歸檔、文章標簽歸檔、文章日期歸檔,所以就需要有一個可供選擇的正則表達式來接收參數。
最后一個para是指
接下來我們需要在視圖函數中處理參數,然后返回給用戶一個個人站點的主頁
#views中: def homeSite(request,username,**kwargs): # 查詢當前用戶站點 current_user=models.UserInfo.objects.filter(username=username).first() if not current_user:#如果當前用戶異常,返回404頁面 return render(request,'notFound.html') # 查詢當前用戶站點所有文章 article_list=models.Article.objects.filter(user=current_user) # 查詢 當前用戶的分類歸檔,格式為: ''' python(3) linux(5) go(1) ''' from django.db.models import Count, Sum ''' 可先在視圖函數中利用雙下划綫查詢的方式取出對象再渲染到模板中, 本文用的方法是直接在模板中用.的方法進行取對象 雙下划線方式如下: current_blog=current_user.blog category_list = models.Category.objects.all().filter(blog=current_blog).annotate( c=Count("article__nid")).values_list("title", "c") print(category_list) # <QuerySet [<Category: yuan的go>, <Category: yuan的java>]> ''' # 查詢當前用戶的標簽歸檔 ''' a(3) b(5) c(1) ''' ''' 雙下划線查詢方式: tag_list = models.Tag.objects.all().filter(blog=current_blog).annotate(c=Count("article__nid")).values_list("title",c) print(tag_list) # <QuerySet [('基礎知識', 3), ('插件框架', 0), ('web開發', 1)]> ''' # 查詢當前用戶的日期歸檔 ''' 2016-12(2) 2017-1(3) ''' #因為時間對象用Django原生api方式取無法做到格式化輸出,所以這里必須用到extra(數據庫查詢語句) date_list=models.Article.objects.filter(user=current_user).extra(select={"filter_create_date":"strftime('%%Y/%%m',create_time)"})
.values_list("filter_create_date").annotate(Count("nid")) print(date_list) if kwargs: if kwargs.get("condition") == "category": #分類歸檔 article_list = models.Article.objects.filter(user=current_user, category__nid=kwargs.get("para")) print(article_list) elif kwargs.get("condition") == "tag": #標簽歸檔 article_list = models.Article.objects.filter(user=current_user, tags__nid=kwargs.get("para")) elif kwargs.get("condition") == "date": #時間歸檔 year, month = kwargs.get("para").split("/") article_list = models.Article.objects.filter(user=current_user, create_time__year=year, create_time__month=month) return render(request,"homeSite.html",locals())

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %} 個人首頁 {% endblock %}</title> <script src="/static/jquery-3.2.1.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <link rel="stylesheet" href="/static/mycss/homeSite.css"> <link rel="stylesheet" href="/static/mycss/article_detail.css"> <link rel="stylesheet" href="/static/user_home_style/{{ current_user.blog.theme }}"> <script src="/static/kindeditor/kindeditor-all.js"></script> </head> <body> <nav class="navbar navbar-inverse" id="nav"> <div class="container"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客園</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="/blog/{{ current_user.username }}">{{ current_user.blog.title }} <span class="sr-only">(current)</span></a> </li> <li><a href="#">Link</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="/">首頁</a></li> <li><a href="#">新隨筆</a></li> <li><a href="#">聯系</a></li> <li><a href="#">訂閱</a></li> <li><a href="#">管理</a></li> {% if request.user.is_authenticated %} <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> {{ request.user.username }} <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="/blog/{{ request.user.username }}">個人首頁</a></li> <li><a href="/blog/backend/">文章管理</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="/log_out/">注銷</a></li> </ul> </li> {% else %} <li><a href="/login/">請登錄</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <div class="row"> <div class="col-md-3"> <div class="panel panel-primary"> <div class="panel-heading">公告</div> <div class="panel-body"> <p><b>Python開發交流群:255012808</b></p> <p><b>Python開發交流群:255012808</b></p> <p><b>Python開發交流群:255012808</b></p> <p><b>Python技術交流群:557877767</b></p> <p><b>開源Tyrion交流群:564068039</b></p> </div> </div> <div class="panel panel-primary"> <div class="panel-heading">個人信息</div> <div class="panel-body"> <p><img class="img-circle " src="{{ current_user.avatar.url }}" alt="" height="100" width="100"></p> <h3>昵稱:{{ current_user.username }}</h3> </div> </div> <div class="panel panel-danger"> <div class="panel-heading">我的標簽</div> <div class="panel-body"> <ul class="list-group"> {% for foo in current_user.blog.tag_set.all %} <li class="list-group-item"> <span class="badge">{{ foo.article_set.count }}</span> <a href="/blog/{{ current_user.username }}/tag/{{ foo.nid }}">{{ foo.title }}</a> </li> {% endfor %} </ul> </div> </div> <div class="panel panel-info"> <div class="panel-heading">隨筆分類</div> <div class="panel-body"> <ul class="list-group"> {% for foo in current_user.blog.category_set.all %} <li class="list-group-item"> <span class="badge">{{ foo.article_set.count }}</span> <a href="/blog/{{ current_user.username }}/category/{{ foo.nid }}">{{ foo.title }}</a> </li> {% endfor %} </ul> </div> </div> <div class="panel panel-success"> <div class="panel-heading">隨筆檔案</div> <div class="panel-body"> <ul class="list-group"> {% for date in date_list %} <li class="list-group-item"> <span class="badge">{{ date.1 }}</span> <a href="/blog/{{ current_user.username }}/date/{{ date.0 }}">{{ date.0 }}{{ date.1 }}</a> </li> {% endfor %} </ul> </div> </div> </div> <div class="col-md-9 c1"> {% block con %} {% for foo in article_list %} <div class="article"> <div class="head"> <h3> <a href="/blog/{{ current_user.username }}/article_detail/{{ foo.nid }}">{{ foo.title }}</a> </h3> </div> <div class="desc"> {{ foo.desc }} </div> <div class="foot pull-right"> posted@ <span>{{ foo.create_time|date:"Y-m-d" }}</span> <span>評論({{ foo.comment_count }})</span> <span>點贊({{ foo.up_count }})</span> <span>閱讀({{ foo.read_count }})</span> </div> <p></p> </div> {% endfor %} {% endblock %} </div> </div> </div> <div> </div> </body> </html>

/*body{*/ /*background-color: #9acfea;*/ /*}*/ .article{ padding-bottom:20px; border-bottom: solid; } .desc{ text-align: justify ; font-size: 16px; } .foot{ font-size: 14px; color: grey; } .c1{ background-color:white; } #comment_form_container{ margin-top: 80px; } .top{ display: inline-block; width: 40px; height: 40px; background-color: whitesmoke; position: fixed; bottom: 20px; right: 20px; text-align: center; }
上面的homesite.html中用到了一個路由反向解析,所以這里我們要增加一條路由匹配:
url(r'^(?P<username>.*)', views.homeSite,name="aaa")
這里我們用到了404頁面,可以用瀏覽器自帶的功能,我么也可以自己寫一個提示的HTML頁面

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Error_404_資源不存在</title> <style type="text/css"> body{margin:8% auto 0;max-width: 550px; min-height: 200px;padding:10px;font-family: Verdana,Arial,Helvetica,sans-serif;font-size:14px;}p{color:#555;margin:10px 10px;}img {border:0px;}.d{color:#404040;} </style> </head> <body> <a href="http://www.cnblogs.com/"><img src="//static.cnblogs.com/images/logo_small.gif" alt="cnblogs"/></a> <p><b>404.</b> 抱歉! 您訪問的資源不存在!</p> <p class="d">請確認您輸入的網址是否正確,如果問題持續存在,請發郵件至contact@cnblogs.com與我們聯系。</p> <p><a href="/">返回網站首頁</a></p> </body> </html>
接下來我們要做的頁面是個人站點中的文章詳情頁面,因為訪問者會在用戶的博客站點中任意點擊其已發表的文章。
因為文章詳情頁面的布局與個人站點首頁的布局相似,只是在右側的內容區變成了文章的詳細內容,所以我們可以將個人站點首頁的模板設置成基板,上面的homesite.html中已經設好,所以我們接下來在文章詳情頁中直接繼承就好。
文章詳情頁面是我們重點要做的頁面,因為其中包含的知識點最多。主要需實現的功能有:點贊、踩、評論、評論實時更新、評論樓結構與評論樹結構(任選一項)。
下面我們會來一一進行實現
文章詳情:
具體URL配置:
#文章詳情頁面 url(r'^(?P<username>.*)/article_detail/(?P<article_nid>\d+)', views.article_detail), #文章贊、踩功能 url(r'^updown/$', views.updown), url(r'^comment/$', views.comment), #文章評論功能 #顯示文章已發表的評論,樹狀結構 url(r'^commentTree/(?P<article_id>\d+)$', views.commentTree),
#views中 ########文章詳情######### def article_detail(request,username,article_nid): # 查詢當前用戶 current_user = models.UserInfo.objects.filter(username=username).first() if not current_user:#用戶異常時返回404頁面 return render(request, 'notFound.html') from django.db.models import Count, Sum #前端渲染時間時依然需使用extra date_list = models.Article.objects.filter(user=current_user).extra( select={"filter_create_date": "strftime('%%Y/%%m',create_time)"}).values_list( "filter_create_date").annotate(Count("nid")) #得到用戶點擊的文章標題對應的文章詳情 current_article=models.Article.objects.filter(nid=article_nid).first() return render(request,'article_detail.html',locals())

{% extends 'homeSite.html' %} {% block title %}文章詳情{% endblock %} {% block con %} <div class="article_title" id="article_title"><h3>{{ current_article.title }}</h3></div> <div class="article_con"> <div class="col-md-offset-1">{{ current_article.articledetail.content|safe }}</div> <div class="warn"> <div class="warn_con"><img src="/static/img/warning.png" alt="" width="70" height="70"></div> <div class="warn_con"> <p></p> <p></p> 作者:{{ current_user.username }} <br> 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。 </div> </div> <div class="post-info"> <img class="img-circle " src="{{ current_user.avatar.url }}" alt="" height="100" width="100"> 作者:{{ current_user.username }} <div id="digg"> <div class="diggit"> <span class="diggnum" id="digg_count">{{ current_article.up_count }}</span> </div> <div class="buryit"> <span class="burynum" id="bury_count">{{ current_article.down_count }}</span> </div> <div class="updownerror"> <div class="up_error_div pull-left"><span class="up_error"></span></div> <div class="down_error_div pull-right"><span class="down_error"></span></div> </div> </div> </div> <div class="comment-info"> <h3>已發表評論(評論樹):</h3> <div class="comment_tree_list"> <div class="content-tree-list"> {# 由js代碼從數據庫中取得#} </div> </div> <h2>----------------------------------------------------</h2> <div class="comment-header"><h3>評論列表</h3></div> <div class="content-list"> {# for循環從數據庫中取到已發布的評論#} {% for comment in current_article.comment_set.all %} <div class="comment-time"> #{{ forloop.counter }}樓: <img class="img-circle " src="{{ comment.user.avatar.url}}" alt="" width="30" height="30"> <a href="/blog/{{ comment.user.username }}">{{ comment.user.username }}</a> {{ comment.create_time|date:'Y-m-d,h:i:s' }} <div class="pull-right"> <a class="reply_comment_btn" comment_id="{{ comment.nid }}" conmment_username="{{ comment.user.username }}">回復</a> <a href="">引用</a> </div> </div> <div class="comment-detail"> {% if comment.parent_comment_id %}{#對評論的評論#} <div style="background-color: #c4e3f3"> @<a href="">{{ comment.parent_comment.user.username }}</a> : {{ comment.parent_comment.content }} </div> {% endif %} {{ comment.content }}{#對文章的評論#} </div> {% endfor %} </div> </div> <div class="top"><a href="#nav">返回頂部</a></div> {# 用戶在登錄狀態才顯示評論區,方可進行評論#} {% if request.user.is_authenticated %} <div class="add-comment"> <div class="add-icon"> <img src="/static/img/addcomment.gif" alt="" height="20" width="20"> <b><span>發表評論</span></b> </div> <div class="comment-user"> 昵稱:<input type="text" disabled value="{{ request.user.username }}"> </div> <div class="comment-text"> <div>評論內容:</div> {# <textarea name="textarea" id="textarea" cols="30" rows="10"></textarea>#} <textarea id="textarea" name="textarea" style="width:500px;height:300px;"></textarea> <p><button class="btn btn-primary comment-btn">提交評論</button></p> </div> </div> {% else %}{#訪客未登錄時只顯示提示信息,不顯示評論框#} <div id="comment_form_container"> <div class="login_tips"> <img src="/static/img/addcomment.gif" alt="" height="20" width="20"> 注冊用戶登錄后才能發表評論,請 <a href="/login/" >登錄</a> 或 <a href="/reg/">注冊</a>,<a href="/">訪問</a>網站首頁。 </div> </div> {% endif %} </div> {% endblock %}

.article_con{ padding-left: 14px; } .article_title{ text-align: center; color: #2b669a; } .warn{ border: #e0e0e0 1px dashed; margin: 20px 0; font-family: 微軟雅黑; font-size: 11px; } .warn_con{ display: inline-block; margin: 10px; } #digg{ float: right; margin-bottom: 10px; margin-right: 30px; font-size: 12px; width: 128px; text-align: center; margin-top: 10px; } .diggit{ background: url("/static/img/upup.gif") no-repeat; float: left; width: 46px; height: 52px; text-align: center; cursor: pointer; margin-top: 2px; padding-top: 5px; } .buryit { float: right; margin-left: 20px; width: 46px; height: 52px; background: url("/static/img/downdown.gif") no-repeat; text-align: center; cursor: pointer; margin-top: 2px; padding-top: 5px; } .updownerror{ margin-top: 60px; color: red; } .comment-info{ padding-top: 20px; } .comment-header{ border-bottom: 1px solid #ddd; } .comment-time{ margin-left: -20px; padding: 10px; font-size: 16px; } .comment-detail{ margin-left: -20px; font-size: 14px; padding: 0px 20px; border-bottom: 1px dotted #ccc; text-align: justify; } .add-icon{ padding: 20px 0 0 25px; } .add-icon span{ margin-left: 10px; } .comment-user input{ background-image: url("/static/img/icon_form.gif"); background-repeat: no-repeat; border: 1px solid #ccc; padding: 4px 4px 4px 30px; width: 300px; font-size: 13px; } textarea{ width: 450px; height: 300px; font-size: 13px; border: 1px solid #ccc; font-family: 'PingFang SC','Helvetica Neue','Helvetica','Arial',sans-serif; } .comment{ /*float: left;*/ margin-left: 40px; }
文章的贊、踩功能:
因為贊、踩功能都是在文章詳情中的局部功能,所以對應的模板頁面還是article_detail.html。我們需要做的是在模板下方寫js代碼
這里我們用ajax的方式提交請求,腳本語言用的是jQuery
#在article_detail.html中: <script> $('.diggit').click(function () { $.ajax({ url: '/blog/updown/', type: 'POST', data: { csrfmiddlewaretoken: '{{ csrf_token }}', article_id: '{{ current_article.nid }}', up: 'up' }, success: function (data) { var data = JSON.parse(data) if (data.is_login !== true) { alert(location.href) location.href = '/login/' } else if (data.state) { $('#digg_count').html(parseInt($('#digg_count').html()) + 1) } else if (data.is_repeat) { $('.up_error').html('重復推薦') setTimeout(function () { $('.up_error').html('') }, 3000); } ; } }) }) $('.buryit').click(function () { $.ajax({ url: '/blog/updown/', type: 'POST', data: { csrfmiddlewaretoken: '{{ csrf_token }}', article_id: '{{ current_article.nid }}', 'down': 'down' }, success: function (data) { var data = JSON.parse(data) if (data.is_login !== true) { alert(location.href) location.href = '/login/' } else if (data.state) { $('#bury_count').html(parseInt($('#bury_count').html()) + 1) } else if (data.is_repeat) { $('.down_error').html('重復反對') } } }) }) </script>
import json from django.db.models import F def updown(request): updown_response={'state':True,'is_repeat':None,'is_login':True,'down':None} if not request.user.is_authenticated(): #未登錄狀態下不可贊和踩 updown_response['is_login']=False return HttpResponse(json.dumps(updown_response)) user_id=request.user.nid#訪客已登錄,得到訪客id article_id=request.POST.get('article_id')#得到訪客查看的文章id # the_user=models.UserInfo.objects.filter(nid=user_id) # the_article=models.Article.objects.filter(nid=article_id) if request.POST.get('up')=='up':#判斷點的是贊 if models.ArticleUp.objects.filter(user_id=user_id, article_id=article_id): #若用戶已點過贊,不允許再點 updown_response['state']= False updown_response['is_repeat']=True else: try:#將用戶的點贊請求錄入數據庫 models.ArticleUp.objects.create(user_id=user_id, article_id=article_id) models.Article.objects.filter(nid=article_id).update(up_count=F("up_count") + 1) print('..........') except:#捕捉到異常返回 updown_response["state"] = False elif request.POST.get('down')=='down':#踩請求(同理) if models.ArticleDown.objects.filter(user_id=user_id, article_id=article_id): updown_response['state']= False updown_response['is_repeat']=True else: try: models.ArticleDown.objects.create(user_id=user_id, article_id=article_id) models.Article.objects.filter(nid=article_id).update(down_count=F("up_count") + 1) print('..........') except: updown_response["state"] = False return HttpResponse(json.dumps(updown_response))
評論功能、評論樹與評論樓:
因為評論功能依舊是在文章詳情中的局部功能,所以對應的模板頁面還是article_detail.html。我們需要做的仍舊是在模板下方寫js代碼。這里我們繼續用ajax的方式提交請求,腳本語言用的是jQuery
<script> $('.comment-btn').click(function () { var content if(parent_comment_id){ var index=$("#textarea").val().indexOf("\n"); // 子評論 content= $("#textarea").val().slice(index+1) } else { content=$("#textarea").val() } $.ajax({ url:'/blog/comment/', type:'POST', data:{ csrfmiddlewaretoken: '{{ csrf_token }}', article_id:'{{ current_article.nid }}', content:content, parent_comment_id:parent_comment_id }, success:function (data) { var data = JSON.parse(data) if (data.is_login !== true) { location.href = '/login/' } else if (parent_comment_id) { s='<div class="comment-time">#新樓: <img class="img-circle " src="{{ request.user.avatar.url}}" alt="" width="30" height="30">'+
' <a href="/blog/{{ request.user.username }}">{{ request.user.username }}</a> '+ data.create_time.slice(0,19)+ '<div class="pull-right"> <a class="reply_comment_btn" comment_id="'+ data.comment_id+ '" conmment_username="{{ request.user.username }}">回復</a> '+
' <a href="">引用</a> </div> </div> <div class="comment-detail">'+ ' <div style="background-color: #c4e3f3"> @<a href="">'+ data.parent_comment_username+'</a> : '+ data.parent_comment_content+'</div>'+' <div>'+content+'</div>'} else { // 根評論 s='<div class="comment-time">#新樓: <img class="img-circle " src="{{ request.user.avatar.url}}" alt="" width="30" height="30">'+
' <a href="/blog/{{ request.user.username }}">{{ request.user.username }}</a> '+ data.create_time.slice(0,19)+ '<div class="pull-right"> <a class="reply_comment_btn" comment_id='+ data.comment_id+ ' conmment_username="{{ request.user.username }}">回復</a> <a href="">引用</a>'+
' </div> </div> <div class="comment-detail">'+ ' <div>'+content+'</div>' } $(".content-list").append(s); $("#textarea").val(""); parent_comment_id=null; } }) }) // 回復按鈕 var parent_comment_id=null; $(".comment-info").on("click",".reply_comment_btn",function () { // 文本框中顯示父評論的名字 var parent_comment_username=$(this).attr("conmment_username"); $("#textarea").focus(); $("#textarea").val("@"+parent_comment_username+"\n"); // 獲取父評論的comment_id parent_comment_id=$(this).attr("comment_id") }); // 獲取評論樹 $.ajax({ url:"/blog/commentTree/{{ current_article.nid }}", success:function (data) { var data=JSON.parse(data) var s=showCommentTree(data); $(".content-tree-list").append(s); } }); // 展開評論樹 var dict={'father':true} function showCommentTree(comment_list) { // comment_list: [{"content":"1","children_list":[{}]},{"content":"2"},{"content":"3"},] var html=""; $.each(comment_list,function (i,comment_dict) { if(dict.father){var num=i+1+'樓';num='#'+num} else{var num='回復樓'} {# var num=i+1#} var val=comment_dict["content"]; var commnent_str= '<div class="comment"><div class="comment-time">'+num+
': <img class="img-circle " src="/media/'+comment_dict["user__avatar"]+
'" alt="" width="30" height="30"> <a href="/blog/'+comment_dict["user__username"]+'">'+
comment_dict["user__username"]+'</a> '+comment_dict["create_time"]+
' <div class="pull-right"> <a class="reply_comment_btn" comment_id="'+comment_dict["nid"]+
'" conmment_username="'+comment_dict["user__username"]+
'">回復</a> <a href="">引用</a> </div></div><div class="comment-detail"><span> '+
val+'</span></div>'; num+=1 if(comment_dict["chidren_commentList"]){ dict.father=false {# var num='評論'#} var s=showCommentTree(comment_dict["chidren_commentList"]); // [{},{}] commnent_str+=s dict.father=true } commnent_str+="</div>"; html+=commnent_str }); return html } </script>
最后我們需要實現的是后台管理的頁面:
在這里我們自己寫一個后台管理的頁面,主要用於用戶對自己站點的博客的增刪改查。其實可以用Django框架中的admin會可以輕松實現,不過因為是自己的項目,所以我們可以嘗試着自己來做一個具有相同功能的。
首先需要的是配置路由,因為是個人站點的功能,所以我們依舊將路由放在blog文件夾下的urls.py中
url(r'^backend/$', views.backendindex),#后台管理首頁
url(r'^backend/addarticle/$', views.addarticle),#后台管理添加新文章功能
url(r'^backend/editarticle/(\d+)$', views.editarticle),#編輯文章
url(r'^backend/del/(\d+)', views.delarticle),#刪除文章
下面我們進行具體的功能實現
首先要得到一個后台管理的首頁
url(r'^backend/$', views.backendindex),#后台管理首頁
def backendindex(request): print('-------------------') if not request.user.is_authenticated(): return redirect("/login/")#未登錄不可直接進入 #在后台管理需要渲染出自己寫的博客,所以要拿到所有的文章 article_list=models.Article.objects.filter(user=request.user).all() return render(request,"backendindex.html",{"article_list":article_list})

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>后台管理</title> <script src="/static/jquery-3.2.1.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <script src="/static/kindeditor/kindeditor-all.js"></script> </head> <body> <div class="container c1"> <div class="header"> <h1>后台管理</h1> </div> <hr> <div class="row"> <div class="leftMenu col-md-2"> <ul class="list-group"> <li class="list-group-item list-group-item-success"><a href="/blog/backend/">文章管理</a></li> <li class="list-group-item list-group-item-info"><a href="">標簽管理</a></li> <li class="list-group-item list-group-item-warning"><a href="">分類管理</a></li> </ul> </div> <div class="manageCon col-md-8"> {# 因為add頁面的排版相似,所以我們要建一個方便繼承用的盒子#} {% block manageCon %} <div class="addbutton"> <a href="/blog/backend/addarticle/"><button class="btn btn-primary">添加文章</button></a> </div> <table class="table table-border table-hover"> <tr> <th>標題</th> <th>評論數</th> <th>點贊數</th> <th>操作</th> <th>操作</th> </tr> {% for article in article_list %} <tr> <td>{{ article.title }}</td> <td>{{ article.comment_count }}</td> <td>{{ article.up_count }}</td> <td><a href="/blog/backend/editarticle/{{ article.nid }}"><button class="btn btn-info">編輯</button></a></td> <td><a href="/blog/backend/del/{{ article.nid }}"><button class="btn btn-danger">刪除</button></a></td> </tr> {% endfor %} </table> {% endblock %} </div> </div> </div> </body> </html>
接下來實現增刪改查的功能,首先說最簡單的刪,刪除只要將選中的文章的id發送到后台,后天將指定文章刪除然后redirect到后台首頁就好了,沒有對應的html模板。
后台管理刪除文章
url(r'^backend/del/(\d+)', views.delarticle),#刪除文章
def delarticle(request,nid): models.Article.objects.filter(nid=nid).delete() return redirect('/blog/backend/')
后台管理添加文章
這里我們需要用到一個編輯器插件,去網上下載kindeditor,然后照理將kindeditor文件夾放到static文件夾下,具體的使用方法可自行百度,這里直接使用。url(r'^backend/addarticle/$', views.addarticle),#后台管理添加新文章功能
#在views.py中: import datetime def addarticle(request): response={'result':None} if request.method=="POST":#點擊保存按鈕 article_form = ArticleForm(request.POST)#拿到input框中填入的數據 if article_form.is_valid():#數據沒問題 title=article_form.cleaned_data.get("title") content=article_form.cleaned_data.get("content") article_obj=models.Article.objects.create(title=title,desc=content[0:30],create_time=datetime.datetime.now(),user=request.user) models.ArticleDetail.objects.create(content=content,article=article_obj) response['result'] = '添加成功<a href="/blog/backend/">點擊返回</a>' else: response['result']='添加失敗' #添加成功后彈出添加成功的提示,然后給予一個點擊可返回后台管理首頁的按鈕 return HttpResponse(response['result']) article_form=ArticleForm()#由form組件,生成input框 return render(request,"addarticle.html",{"article_form":article_form})

{% extends "backendindex.html" %} {#繼承后台管理首頁#} {% block manageCon %} <div class="container"> <div class="row"> <div class="col-md-10"> {% csrf_token %} <div id="Editor_Edit_Header" class="CollapsibleTitle"> <span id="Editor_Edit_headerTitle">添加隨筆</span> </div> <form action="/blog/backend/addarticle/" method="post" novalidate> {% csrf_token %} <div class="form-group"> <label for="exampleInputEmail1">標題:</label> {{ article_form.title }} </div> <div> <label for="title">內容:</label> <p> {{ article_form.content }} </p> </div> <p><input type="submit" value=" 提交 " class="btn btn-info"></p> </form> </div> </div> </div> <script> // 使用KindEditor必須寫 KindEditor.ready(function(K) { window.editor = K.create('#id_content',{ width:"950px",//寬,可設定 height:"500px",//長,可設定 resizeType:0,//參數為0、1、2,指代長款可不可調 //還有其他許多可設定的功能,自行百度 uploadJson:"/uploadFile/",//傳圖片時需要用到的路徑 extraFileUploadParams:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), } }); }) </script> {% endblock %}
添加文章時我們肯定會相應的需要上傳一些圖片,這就需要再寫一個url,這里我將url放到全局的urls.py中
url(r'^uploadFile/$', views.uploadFile),#添加文章時上傳圖片
對應的視圖函數:
#在views.py中: def uploadFile(request): file_obj=request.FILES.get("imgFile")#拿到上傳的圖片 file_name=file_obj.name from blogCMS1 import settings import os #將圖片文件寫入到本地 path=os.path.join(settings.MEDIA_ROOT,"article_uploads",file_name) with open(path,"wb") as f: for i in file_obj.chunks(): f.write(i) response={ "error":0, "url":"/media/article_uploads/"+file_name+"/" } import json return HttpResponse(json.dumps(response))
編輯文章功能
url(r'^backend/editarticle/(\d+)$', views.editarticle),#編輯文章
#在views.py中: def editarticle(request,nid): response={'result':None} if request.method=="POST": article_form = ArticleForm(request.POST) if article_form.is_valid(): title=article_form.cleaned_data.get("title") content=article_form.cleaned_data.get("content") article_obj=models.Article.objects.filter(nid=nid).update(
title=title,desc=content[0:30],create_time=datetime.datetime.now(),user=request.user) models.ArticleDetail.objects.update(content=content) response['result'] = '添加成功<a href="/blog/backend/">點擊返回</a>' else: response['result']='添加失敗' return HttpResponse(response['result']) article_obj=models.Article.objects.get(nid=nid)#需要知道被編輯的文章對象 title=article_obj.title content=article_obj.articledetail.content article_form=ArticleForm({"title":title,"content":content})#只允許編輯標題和內容 return render(request,"editarticle.html",{"article_form":article_form,'nid':nid})

{% extends "backendindex.html" %} {% block manageCon %} <div class="container"> <div class="row"> <div class="col-md-10"> {% csrf_token %} <div id="Editor_Edit_Header" class="CollapsibleTitle"> <span id="Editor_Edit_headerTitle">添加隨筆</span> </div> <form action="/blog/backend/editarticle/{{ nid }}" method="post" novalidate> {% csrf_token %} <div class="form-group"> <label for="exampleInputEmail1">標題:</label> {{ article_form.title }} </div> <div> <label for="title">內容:</label> <p> {{ article_form.content }} </p> </div> <p><input type="submit" value=" 提交 " class="btn btn-info"></p> </form> </div> </div> </div> <script> KindEditor.ready(function(K) { window.editor = K.create('#id_content',{ width:"950px", height:"500px", resizeType:0, uploadJson:"/uploadFile/", extraFileUploadParams:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), } }); }) </script> {% endblock %}
基本功能就到這里了,還有一些其他的功能如修改用戶名密碼、園齡時間的顯示、美化的一些插件使用等等可隨喜好自行添加。
成品如下: