1.登录:http://www.cnblogs.com/shaojiafeng/p/7868195.html
2.注册
- urls -前端页面中写 username ,password,password,email,头像 -form组件创建注册页面 -widget的作用是,如果不加widget生成的前端标签是原生的input框,加上之后会变成渲染之后的 -eg:username,password,repeat_pwd,email class RegForm(forms.Form): email = forms.EmailField(widget=widgets.EmailInput( attrs={"class": "form-control", "placeholder": "email"} )) ....还有username,password,repeat_pwd 如果form中有这些字段,则可以在前端页面中{{form_obj.username}},{{form_obj.password}}这样的方式取到值 -验证每个字段 - clean_username(self):(用到了局部钩子) cleaned_data它代表的是一个字典 -ret=models.UserInfo.objects.filter(username=self.cleaned_data.get("username")) if not ret: #校验字段 return self.cleaned_data.get("username") else: raise ValidationError("用户名已注册") -检查数据库中是否有username -如果没有,验证的那个字段,如果有这个username,则返回cleaned_data取到的值,如果已经存在该用户,就走ValidationError,显示error信息 - clean_password(self):(用到了局部钩子) -同上方法验证,但需要注意,这个判断密码不能全是数字 def clean_validCode(self):(用到全局钩子) -前端注册页面 -上一步在后端上传的form对象渲染了username,password,email -生成头像 -<img src="/static/img/default.png" alt="" id="avatar_img"> -<input type="file" class="form-control" id="avatar_file"> (生成一个默认的灰色框头像,如用户点击上传头像,此时应该吧图片遮到上传的文件上面, 这里还有让parent设置成相对定位,children设置绝对定位,还有一点,应该不让灰色框隐藏, 而是让他的透明度为0) -头像预览 -找到头像标签,用到change事件,用一个变量去接受当前拿到的文件,然后对去到该文件的路径,让他付给this.result $("#avatar_file").change(function () { var ele_file=$(this)[0].files[0]; //拿到当前点击的文件 var reader=new FileReader(); //创建一个新对象 reader.readAsDataURL(ele_file); reader.onload=function (){ $("#avatar_img")[0].src=this.result } }); - Ajax提交数据 -使用formdata是因为里面有二进制文件 - 上传二进制文件引入 FormData() - FormData()对象追加键值对 -$("#avatar_file")[0].files[0]) # jquery转DOM对象,取files对象最近一次上传的文件 $.ajax({ url:"/reg/", type:"POST", data:formdata, contentType:false, processData:false, headers:{"X-CSRFToken":$.cookie('csrftoken')}, //为了避免Forbedent禁止 success:function (data) { console.log(data); var data=JSON.parse(data); //能拿到user代表注册成功,跳转到登录页面 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) } }) } 注释:i为出错form组件字段、j为报错 each循环报错信息、新建span标签,给其增加样式(右漂浮,红色) span的内容为报错信息 给出错的input标签后边添加span标签,并使其父级标签has-error 如果i使__all__:将报错添加到重新输入密码后边的span中 -后端注册(views中) -判断是否是is_ajax请求 -接收从前端提交过来的数据 -form验证 实例化对象,然后进行校验,将校验的结果和数据库进行匹配验证,获取校验结果,cleaned_data是一个字典的形式, 如果校验失败,则走errors错误信息的列表 form_obj=forms.RegForm(request.POST) #接收从前端提交来的数据,规则和数据放在一起 if form_obj.is_valid():(开始校验,并获取校验结果) username = form_obj.cleaned_data["username"] password = form_obj.cleaned_data["password"] email = form_obj.cleaned_data.get("email") avatar_img = request.FILES.get("avatar_img") ................ -setting.py 如果只在这里设置 不在前端设置 用户接收不到文件 MEDIA_ROOT=os.path.join(BASE_DIR,"app01","media") MEDIA_URL="/media/" -urls.py # media 配置 from django.views.static import serve from blog import settings url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
3.个人博客(以查询为中心)
1.head_title 2.左侧菜单 -个人信息 -头像 -昵称 #查询当前用户的所有文章 article_list=models.Article.object.filter(user=current_user) -分类归档 from django.db.models import Count, Sum category_list = models.Category.objects.all().filter(blog=current_blog).annotate( c=Count("article__nid")).values_list("title", "c") -标签归档 tag_list = models.Tag.objects.all().filter(blog=current_blog).annotate(c=Count("article__nid")).values_list("title", -日期归档 date_list = models.Article.objects.all().extra( select={"filter_create_date": "strftime('%%Y/%%m',create_time)"}).values_list("filter_create_date").annotate( Count("nid")) 3.内容部分是个人所有文章的渲染 此处渲染的是文章title和文章摘要,发表日期,评论,点赞,阅读数 用到for循环去取,因为title是可点击进入的,所以用<a> -<div class="article_title"><a href="/blog/{{ current_user.username }}/articles/{{ article.nid }}">{{ article.title }}</a></div> 如上,需要先分配url,让其跳转到/blog/下再跟其name等... -还有一点需要注意的是:发表时间的渲染需要过滤date。eg:(发表于 <span>{{ article.create_time|date:"Y-m-d" }}</span> )
4.个人博客——文章的详细内容
-首先点击文章的标题,应该跳转到文章页里面,但是个人博客页的title和左侧菜单应该不变,此处用到继承 1.让(article_detail)页面去继承(homeSite)d的title和左侧菜单 -{% extends "homeSite.html" %} 2.在前端页面中渲染文章标题和文章内容 -标题:<h3 class="text-center">{{ article_obj.title }}</h3> -内容:<div class="article_con">{{ article_obj.articledetail.content|safe }}</div> -渲染文章内容时,应该让在博客园的HTML代码编辑器中渲染之后的标签页代码写入admin中,为了让去显示全部信息,必须在后面加(|safe)
5.文章页的点赞
通过点击“赞” 触发click事件,发送ajax请求,来完成点击一次,可以自加一的操作 1.在前端页面渲染点赞按钮,将点赞按钮图片保存到/static/img 中 -此处需要注意的是urls中的路由分发 urlpatterns = [ url(r'^poll/$', views.poll), ] 以上渲染的代码,通过form打包给button,通过ajax去提交 2.前端页面中ajax $.ajax({ url:"/blog/poll/", type:"post", data:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), article_id:"{{ article_obj.nid }}" #文章的对象 }, success:function(data){ var data=JSON.parse(data); // pollResponse if (data.state){ var val=parseInt($("#digg_count").html())+1; $("#digg_count").html(val) } else if (data.is_repeat){ $(".diggnum_error").html("重复提交").css("color","red") } } }) 3.后端中: def poll(request): #创建一条记录 user_id=request.user.nid article_id=request.POST.get("article_id") articleUp = models.ArticleUp.objects.create(user_id=user_id, article_id=article_id) #找到article_id,在之前的基础上加1.(此处需要引入F) from django.db.models import F 在views.py中: //从数据库过滤出点赞表的对象,如果有这个对象,就让他自加一,如果没有这个对象,先创建一个对象,让给他自加一 其中用到,try异常处理,限制不能让用户点击两次赞 def poll(request): from django.db.models import F #创建一条记录 user_id=request.user.nid article_id=request.POST.get("article_id") pollResponse={"state":True,"is_repeat":None} //作为是否重复提交的判断 if models.ArticleUp.objects.filter(user_id=user_id, article_id=article_id): //从数据库过滤出点赞表的对象 pollResponse["state"]=False pollResponse["is_repeat"]=True else: try: articleUp = 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) except: pollResponse["state"] = False import json return HttpResponse(json.dumps(pollResponse))
6. 实现ajax文章评论
1.给文章评论,点击评论按钮,实现ajax提交 -前端页面中找到提交按钮,绑定click事件 -在urls中添加路由:url(r'^comment/$', views.comment), -在后端中,先取出数据 -文章主键号:article_id = request.POST.get("article_id") -评论内容:comment_content = request.POST.get("comment_content") -user_id:user_id = request.user.nid -在前端中渲染出来头像,评论内容 2.实现ajax子评论 点击“回复”按钮,给文章的评论进行评论 -views中提交评论 -锚点 -$(".comment_list").on("click",".reply_comment_btn",function (){}//找到标签,绑定事件 -添加子评论,必须拿到父评论的id -parent_comment_id = $(this).attr("comment_id") -前端中渲染 // 回复按钮 var parent_comment_id=null; $(".comment_list").on("click",".reply_comment_btn",function () { // 文本框中显示父评论的名字 var parent_comment_username = $(this).attr("conmment_username"); $("#comment_con").val("@" + parent_comment_username + " "); // 获取父评论的comment_id parent_comment_id = $(this).attr("comment_id") })
文章评论前端代码实现:
{% extends "homeSite.html" %} {% block content %} <div class="article_region"> <div class="row"> <h3 class="text-center">{{ article_obj.title }}</h3> <div class="article_con">{{ article_obj.articledetail.content|safe }}</div> </div> </div> <div class="upup row"> <div class="downdown pull-right"> <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span> </div> <div class="diggit pull-right"> <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span> </div> </div> <span class="diggnum_error pull-right"></span> <div class="had_comment_region"> <h5>已发表评论:</h5> <div class="comment_list"> {% for comment in comment_list %} <div class="comment_item"> <div class="row"> <div class="col-md-6"> <img 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" }} </div> <div class="pull-right"> <a href="#comment_con" class="reply_comment_btn" comment_id="{{ comment.nid }}" conmment_username="{{ comment.user.username }}">回复</a> <a href="">支持</a> </div> </div> <div style="background-color: palegoldenrod"> {% if comment.parent_comment_id %} @<a href="">{{ comment.parent_comment.user.username }}</a> : {{ comment.parent_comment.content }} {% endif %} </div> </div> {{ comment.content }} <hr> {% endfor %} </div> </div> <div class="subComment_region"> <h4>发表评论</h4> <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p> <p>评论内容:</p> <form action=""> {% csrf_token %} <p><textarea name="" id="comment_con" cols="50" rows="10"></textarea></p> <input type="button" value="提交评论" class="btn btn-default commentBtn"> </form> </div> <script> //实现ajax点赞 $(".diggit").click(function () { $.ajax({ url:"/blog/poll/", type:"post", data:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), article_id:"{{ article_obj.nid }}" }, success:function (data) { var data=JSON.parse(data); // pollResponse if (data.state){ var val=parseInt($("#digg_count").html())+1; $("#digg_count").html(val) } else if (data.is_repeat){ $(".diggnum_error").html("重复提交").css("color","red") } } }) }) // 实现ajax文章评论 $(".commentBtn").click(function () { var content; if(parent_comment_id){ var index=$("#comment_con").val().indexOf(" "); // 子评论 content= $("#comment_con").val().slice(index+1) }else { content=$("#comment_con").val() } $.ajax({ url:"/blog/comment/", type:"POST", data:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), article_id:"{{ article_obj.nid }}", comment_content:content, parent_comment_id:parent_comment_id }, success:function (data) { console.log(data.create_time.slice(0,19)); s='<div class="comment_item" style="border-bottom: 1px solid grey"><div class="row"> <div class="col-md-6"> <img 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> <div class="pull-right"> <a href="#comment_con" class="reply_comment_btn">回复</a> <a href="">支持</a> </div> </div> <div>'+content+' </div> </div>' $(".comment_list").append(s); $("#comment_con").val("") } }) }); // 实现ajax子评论 // 回复按钮 var parent_comment_id=null; $(".comment_list").on("click",".reply_comment_btn",function () { // 文本框中显示父评论的名字 var parent_comment_username = $(this).attr("conmment_username"); $("#comment_con").val("@" + parent_comment_username + " "); // 获取父评论的comment_id parent_comment_id = $(this).attr("comment_id") }) </script> {% endblock %}
文章评论后端实现:
def comment(request): article_id = request.POST.get("article_id") comment_content = request.POST.get("comment_content") user_id = request.user.nid commentResponse = {} # print(request.POST.get("parent_comment_id"), "=======") if request.POST.get("parent_comment_id"): # 处理子评论 with transaction.atomic(): pid = request.POST.get("parent_comment_id") comment_obj = models.Comment.objects.create(article_id=article_id, user_id=user_id, content=comment_content, parent_comment_id=pid) commentResponse["create_time"] = str(comment_obj.create_time) else: # 处理的文章评论,即根评论 with transaction.atomic(): comment_obj = models.Comment.objects.create(article_id=article_id, user_id=user_id, content=comment_content) models.Article.objects.filter(nid=article_id).update(comment_count=F("comment_count") + 1) commentResponse["create_time"] = str(comment_obj.create_time) print(commentResponse["create_time"]) from django.http import JsonResponse return JsonResponse(commentResponse)
7.后台管理
在后台管理页面中可以添加文章,编辑,删除
添加文章,插件-- kindeditor编辑器的使用
(1).url的分配:
url(r'^backend/addArticle/$', views.addArticle), url(r'^backend/$', views.backendIndex),
(2).前端页面:addArticle.html
-{% extends "backendIndex.html" %} 添加文章页面中得继承后台管理的head和左侧栏 -在addArticle.html页面中利用kindeditor编辑器添加文章 -用form提交 -标题的渲染:<div> <label for="title">标题:</label> <p>{{ article_form.title }}</p> </div> -内容的渲染: <div> <label for="title">内容:</label> <p>{{ article_form.content }}</p> </div> -别忘了还有提交摁钮呦:<p><input type="submit" value="submit"></p> -kindeditor编辑器的插入: -textarea:目的是让textarea的空白框变成kindeditor带有各色功能的编辑框 -引入kindeditor:引入的是js文件,用<script> -<script src="/static/kindeditor/kindeditor-all.js"></script> -http://kindeditor.net/demo.php #用法均可上该网址参考 -<script> KindEditor.ready(function(K) { window.editor = K.create('#id_content',{ "600px", height:"500px", resizeType:0, uploadJson:"/uploadFile/", extraFileUploadParams:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), } }); }) </script>
(3).views中:
def addArticle(request): """添加文章页""" #如果是post请求,实例化创建一个对象,如果是is_valid(),就去form验证title和content, # 然后创建一个content_obj对象 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.create(title=title,desc=content[0:30],create_time=datetime.datetime.now(),user=request.user) models.ArticleDetail.objects.create(content=content,article=article_obj) else: pass return HttpResponse("添加成功") article_form=ArticleForm() return render(request,"addArticle.html",{"article_form":article_form}) def uploadFile(request): """添加文章页的上传文件及图片""" file_obj=request.FILES.get("imgFile") #拼接一个本地的路径,将图片下载下来 file_name=file_obj.name #用file_obj_name这个属性可以直接从对列中取出组合格名字 from django.conf 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(): #chunks: 64*2**10 f.write(i) response={ "error":0, #标识一个状态 "url":"/media/article_uploads/"+file_name+"/" #图片预览的路径 } import json return HttpResponse(json.dumps(response))
(4).xss组件
在添加文章内容时,添加的图片,或者文字内容,在前端页面中渲染不出来,所有用xss组件进行过滤处理,此处过滤相当于白名单处理,就是valid_dict中有的值都可以在前端页面中渲染出来。
使用到bs4模块(pip install bs4)
from bs4 import BeautifulSoup
def filter_xss(html_str): valid_dict = {"p": ["id", "class"], "div": ["id", "class"],"img":["src","alt"]} from bs4 import BeautifulSoup soup = BeautifulSoup(html_str, "html.parser") # soup -----> document ######### 改成dict for ele in soup.find_all(): # 过滤非法标签 if ele.name not in valid_dict: ele.decompose() # 过滤非法属性 else: attrs = ele.attrs # p {"id":12,"class":"d1","egon":"dog"} l = [] for k in attrs: if k not in valid_dict[ele.name]: l.append(k) for i in l: del attrs[i] print(soup) return soup.decode()