一、登录功能
1、采用ajax 提交form表单的方式
2、后台生成随机验证码,登录时提交验证码
3、用PLI库生成随机验证码,置于session中,登录时与前台提交的code进行upeer()的验证
<div class="col-lg-6"> <img height="35" width="250" src="/get_code/" alt=""> </div>
二、首页
1、index.html分别采用头和container的布局
<div class="my_head"> <div class="container-fluid"> <div class="row"> <div class="col-md-3"> <div class="panel panel-danger"> <div class="panel-heading">广告信息</div> <div class="panel-body"> 重金求子 </div> </div> <div class="col-md-6"> <div class="col-md-3">
2、head处理时,如果已经登录,则显示username
{% if request.user.is_authenticated %} <li><a href="/login/">{{ request.user.username }}</a></li> <li><a href="#"></a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多信息 <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="/logout/">退出登录</a></li> </ul> </li> {% else %} <li><a href="/login/">登录</a></li> <li><a href="/register/">注册</a></li> {% endif %}
3、class="media-left"将div分别放于同一行左右侧
class="glyphicon glyphicon-comment"图标的话可以采用bootcss的、也可以用static里的font-awesome的拿来即用
<div class="col-md-6"> {% for articles in articles_list %} <p><h4><a href="">{{ articles.title }}</a></h4></p> <div class="media-left"><img height="60" width="60" src="/media/{{ articles.user.avatar }}" alt=""></div> <div class="media-right"> {{ articles.desc }} </div> <div style="margin-top: 10px"> <span><a href="">{{ articles.user.username }}</a></span>  <span>发布于:{{ articles.create_time|date:'Y-m-d' }}</span>  <span class="glyphicon glyphicon-comment">评论数({{ articles.comment_count}})</span>  <span class="glyphicon glyphicon-thumbs-up">点赞数({{ articles.up_count}})</span>  </div> <hr> {% endfor %} </div>
def index(request): articles_list = models.Article.objects.all() return render(request, 'index.html', locals())
三、注册的头像设计
1、为了使得点击字体或者图片都能发生jquery的change事件,label标签的for属性和input的id属性一样即可
<div class="form-group"> <label for="my_file">头像 <img height="60px" width="60px" src="/static/img/default.png" id="id_img"> </label> <input type="file" id="my_file"> </div>
2、读取图片内容到img标签里
$("#my_file").change(function () { alert(123) //$("#my_file")[0].files[0] 取出文件 var obj=$("#my_file")[0].files[0] //生成一个文件阅读器 var read=new FileReader() //把文件读到我的对象里 read.readAsDataURL(obj) read.onload=function(){ $("#id_img").attr('src',read.result) } {#$("#id_img").attr('aa','bb')#} })
3、同时需要将input文件的字去掉
<style> #my_file { display: none; } </style>
4、form表单有两个功能,一个是校验字段,一个是生成input标签
{% for foo in form_obj %} <div class="form-group"> <label for="">{{ foo.label }}</label> {{ foo }} <span class="errors"></span> </div> {% endfor %}
5、1和2的方式会在form里面直接提交form表单,此处想用ajax的方式提交,采用3的方法
{# 1、 <input type="submit">#} {# 2、 <button></button>#} 3、 <input type="button" id="id_button" class="btn btn-success" value="注册">
四、form组件局部钩子和全局钩子使用
1、在view.py里创建forms类,有全局和局部钩子
from django import forms from django.forms import widgets class RegForms(forms.Form): name = forms.CharField(max_length=20, min_length=2, label='用户名', widget=widgets.TextInput(attrs={'class': 'form-control'}), error_messages={'max_length': '太长了', 'min_length': '太短了'} ) pwd = forms.CharField(max_length=20, min_length=2, label='密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'}), error_messages={'max_length': '太长了', 'min_length': '太短了'} ) re_pwd = forms.CharField(max_length=20, min_length=2, label='确认密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'}), error_messages={'max_length': '太长了', 'min_length': '太短了'} ) email = forms.EmailField(label='邮箱', widget=widgets.EmailInput(attrs={'class': 'form-control'}), ) def clean_name(self): name = self.cleaned_data.get('name') user = models.UserInfo.objects.filter(username=name).first() if user: raise ValidationError('用户已经存在') else: return name def clean(self): pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd == re_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致')
2、form校验了request.post之后存在两种数据:
# form_obj.cleaned_data # form_obj.errors
def register(request): form_obj = RegForms() back_msg = {} if request.is_ajax(): name = request.POST.get('name') pwd = request.POST.get('pwd') re_pwd = request.POST.get('re_pwd') email = request.POST.get('email') myfile = request.FILES.get('myfile') print(myfile) print(re_pwd) form_obj = RegForms(request.POST) if form_obj.is_valid(): # form_obj.cleaned_data # form_obj.errors if myfile: user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email, avatar=myfile) else: user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email) back_msg['user'] = name back_msg['msg'] = '注册成功' else: back_msg['msg'] = form_obj.errors print(form_obj.errors) print(type(form_obj.errors)) return JsonResponse(back_msg) return render(request, 'register.html', {'form_obj': form_obj})
3、ajax提交文件的话,用formdata
1、each函数,循环
2、if(index=="__all__")判断是否为全局钩子,
3、加class=has-error的属性,即被错误提示选中
4、setTimeout(function () 定时函数执行某件事
$("#id_button").click(function () { var formdata=new FormData() var tt=$("#my_form").serializeArray() $.each(tt,function (index,value) { //console.log(value.name) //console.log(value.value) formdata.append(value.name,value.value) }) formdata.append('myfile',$("#my_file")[0].files[0]) // console.log(tt) $.ajax({ url:'', type:'post', processData:false, contentType:false, data:formdata, success:function (data) { //console.log(data) if(data.user){ location.href='/login/' }else{ $.each(data.msg,function (index,value) { console.log(index) console.log(value) if(index=="__all__"){ $("#id_re_pwd").next().text(value[0]) } $("#id_"+index).next().text(value[0]).parent().addClass('has-error') //$("#id_"+index).next().parent().addClass('has-error') }) setTimeout(function () { $(".form-group").removeClass('has-error') $('span').text("") },1000) } } }) })
五、注册
1、var tt=$("#my_form").serializeArray() 自动的将my_form里的name和value打包起来
2、传文件的话,必须
processData:false, contentType:false,否则会以url-encoded的方法传送了
$("#id_button").click(function () { var formdata=new FormData() var tt=$("#my_form").serializeArray() $.each(tt,function (index,value) { //console.log(value.name) //console.log(value.value) formdata.append(value.name,value.value) }) formdata.append('myfile',$("#my_file")[0].files[0]) // console.log(tt) $.ajax({ url:'', type:'post', processData:false, contentType:false, data:formdata, success:function (data) { //console.log(data) if(data.user){ location.href='/login/' }else{ $.each(data.msg,function (index,value) { console.log(index) console.log(value) if(index=="__all__"){ $("#id_re_pwd").next().text(value[0]) } $("#id_"+index).next().text(value[0]).parent().addClass('has-error') //$("#id_"+index).next().parent().addClass('has-error') }) setTimeout(function () { $(".form-group").removeClass('has-error') $('span').text("") },1000) } } }) })
4、create生成数据时,avatar=myfile,即直接传入文件对象即可
def register(request): form_obj = RegForms() back_msg = {} if request.is_ajax(): name = request.POST.get('name') pwd = request.POST.get('pwd') re_pwd = request.POST.get('re_pwd') email = request.POST.get('email') myfile = request.FILES.get('myfile') print(myfile) print(re_pwd) form_obj = RegForms(request.POST) if form_obj.is_valid(): # form_obj.cleaned_data # form_obj.errors if myfile: user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email, avatar=myfile) else: user = models.UserInfo.objects.create_user(username=name, password=pwd, email=email) back_msg['user'] = name back_msg['msg'] = '注册成功' else: back_msg['msg'] = form_obj.errors print(form_obj.errors) print(type(form_obj.errors)) return JsonResponse(back_msg) return render(request, 'register.html', {'form_obj': form_obj})
5、models里加入此句的意思 upload_to='avatars/', default="/avatars/default.png",上传到MEDIA_ROOT + avatars路径,前面不能再加media,否则路径多了一层media的路径
settings.py # 设置用户上传头像的根路径 MEDIA_ROOT=os.path.join(BASE_DIR,'media')
6、默认图片在media/avatars/default.png
class UserInfo(AbstractUser): """ 用户信息 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/', default="/avatars/default.png") # auto_now_add=True ''' auto_now_add 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。 auto_now 配置上auto_now=True,每次更新数据记录的时候会更新该字段。 ''' create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE) def __str__(self): return self.username
六、media文件夹的配置
1、static文件夹下的内容外部人都可以通过url访问到
STATIC_URL = '/static/' STATICFILES_DIRS=[ os.path.join(BASE_DIR,'static'), ]
# 设置用户上传头像的根路径
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
2、用户上传的图片放在static不合适,需要一个专门的media的文件夹存储,直接127.0.0.1/media/avatars/default.png就可以访问到了
from django.views.static import serve
#def serve(request, path, document_root=None, show_indexes=False):
url(r'^media/(?P<path>.*)', serve,{'document_root':settings.MEDIA_ROOT}),
慎用此方法,如不小心,代码会暴露被外部访问到
3、如此,即可在首页index.html里将路径改为 src="/media/{{ articles.user.avatar }},即可访问
{% for articles in articles_list %} <p><h4><a href="">{{ articles.title }}</a></h4></p> <div class="media-left"><img height="60" width="60" src="/media/{{ articles.user.avatar }}" alt=""></div> <div class="media-right"> {{ articles.desc }} </div> <div style="margin-top: 10px"> <span><a href="">{{ articles.user.username }}</a></span>  <span>发布于:{{ articles.create_time|date:'Y-m-d' }}</span>  <span class="glyphicon glyphicon-comment">评论数({{ articles.comment_count}})</span>  <span class="glyphicon glyphicon-thumbs-up">点赞数({{ articles.up_count}})</span>  </div> <hr> {% endfor %}
七、分组查询
1、views.py
url(r'^(?P<username>\w+)/(?P<condition>tag|category|time|\w+)/(?P<search>.*)', views.homesite), url(r'^(?P<username>\w+)/', views.homesite),
2、后台数据分析
2.1 blog = user.blog 此为查找时的对象
2.2 分组查询1 group by谁,用谁做基表 # 2 filter在前,表示查询 ,filter在后,表示过滤,having # 3 values在前,表示group by 在后,取字段
def homesite(request, username, **kwargs): print(kwargs) username=username user = models.UserInfo.objects.filter(username=username).first() if not user: return render(request, 'errors.html') blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: condition=kwargs.get('condition') search=kwargs.get('search') if condition=='tag': article_list = models.Article.objects.filter(user=user).filter(tags__title=search) elif condition=='category': article_list = models.Article.objects.filter(user=user).filter(category__title=search) elif condition=='time': ll=search.split('-') article_list=models.Article.objects.filter(user=user).filter(create_time__year=ll[0],create_time__month=ll[1]) else: return render(request, 'errors.html') # print(article_list) from django.db.models import Count # 查询每个标签下的文章数(分组查询) # 分组查询1 group by谁,用谁做基表 # 2 filter在前,表示查询 ,filter在后,表示过滤,having # 3 values在前,表示group by 在后,取字段 # 查询当前站点下每个标签下的文章数(分组查询) tag_count = models.Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list('title', 'c') # 查询当前站点下每个分类下的文章数 category_count = models.Category.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title', 'c') from django.db.models.functions import TruncMonth # 查询当前站点每个月份下的文章数 # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time')) # for i in time_count: # print(i.title) # print(i.y_m) time_count=models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate( c=Count('y_m')).values_list('y_m', 'c') print(tag_count) print(category_count) print(time_count) # 统计每个出版社书籍个数 # Publish.object.all().annotate(Count('book__title')) return render(request, 'homesite.html', locals())
2、homesite.html
1、用admin添加数据
2、{{ foo.0 }}({{ foo.1 }}) ------------- xxxxxxxxxxx(xx)
<div class="container-fluid" style="margin-top: 10px"> <div class="row"> <div class="col-md-3"> <div class="panel panel-danger"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for foo in tag_count %} <p><a href="/{{ username }}/tag/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-info"> <div class="panel-heading">随笔分类</div> <div class="panel-body"> {% for foo in category_count %} <p><a href="/{{ username }}/category/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">随笔档案</div> <div class="panel-body"> {% for foo in time_count %} <p><a href="/{{ username }}/time/{{ foo.0|date:'Y-m' }}">{{ foo.0|date:'Y-m' }}({{ foo.1 }})</a></p> {% endfor %} </div> </div> </div>
八、个人站点路由设计
将此条至于最下方的目的,前面都匹配不了的时候,到此可以匹配成功进入视图函数,有则看,无资源则显示404
url(r'^(?P<username>\w+)/', views.homesite),
九、个人站点过滤
1、总的设计
def homesite(request, username, **kwargs): print(kwargs) username=username user = models.UserInfo.objects.filter(username=username).first() if not user: return render(request, 'errors.html') blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: condition=kwargs.get('condition') search=kwargs.get('search') if condition=='tag': article_list = models.Article.objects.filter(user=user).filter(tags__title=search) elif condition=='category': article_list = models.Article.objects.filter(user=user).filter(category__title=search) elif condition=='time': ll=search.split('-') article_list=models.Article.objects.filter(user=user).filter(create_time__year=ll[0],create_time__month=ll[1]) else: return render(request, 'errors.html') # print(article_list) from django.db.models import Count # 查询每个标签下的文章数(分组查询) # 分组查询1 group by谁,用谁做基表 # 2 filter在前,表示查询 ,filter在后,表示过滤,having # 3 values在前,表示group by 在后,取字段 # 查询当前站点下每个标签下的文章数(分组查询) tag_count = models.Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list('title', 'c') # 查询当前站点下每个分类下的文章数 category_count = models.Category.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title', 'c') from django.db.models.functions import TruncMonth # 查询当前站点每个月份下的文章数 # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time')) # for i in time_count: # print(i.title) # print(i.y_m) time_count=models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate( c=Count('y_m')).values_list('y_m', 'c') print(tag_count) print(category_count) print(time_count) # 统计每个出版社书籍个数 # Publish.object.all().annotate(Count('book__title')) return render(request, 'homesite.html', locals())
2、按时间分类,用TruncMonth截断年月,会在原表的基础上在加一条时间为年月的数据,再以此分类(group by谁,用谁做基表)
from django.db.models.functions import TruncMonth # 查询当前站点每个月份下的文章数 # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time')) # for i in time_count: # print(i.title) # print(i.y_m) time_count=models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate( c=Count('y_m')).values_list('y_m', 'c')
3、分类取得数据,页面展示article_list数据,只会显示符合的文章show
if kwargs: condition=kwargs.get('condition') search=kwargs.get('search') if condition=='tag': article_list = models.Article.objects.filter(user=user).filter(tags__title=search) elif condition=='category': article_list = models.Article.objects.filter(user=user).filter(category__title=search) elif condition=='time': ll=search.split('-') article_list=models.Article.objects.filter(user=user).filter(create_time__year=ll[0],create_time__month=ll[1]) else: return render(request, 'errors.html')
4、相应的前台html是如下,加入a连接和url的处理,可以看得相应的数据
<div class="panel panel-danger"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for foo in tag_count %} <p><a href="/{{ username }}/tag/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-info"> <div class="panel-heading">随笔分类</div> <div class="panel-body"> {% for foo in category_count %} <p><a href="/{{ username }}/category/{{ foo.0 }}">{{ foo.0 }}({{ foo.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">随笔档案</div> <div class="panel-body"> {% for foo in time_count %} <p><a href="/{{ username }}/time/{{ foo.0|date:'Y-m' }}">{{ foo.0|date:'Y-m' }}({{ foo.1 }})</a></p> {% endfor %} </div> </div>
十、后台管理页面
1、布局上还是采用了上为header,左三右九的布局
<style>
.home_head {
height: 60px;
background: #1b6d85;
}
</style>
<div class="home_head"></div> <div class="container-fluid" style="margin-top: 10px"> <div class="row"> <div class="col-md-3"。。。。。。。。。。。。 <div class="col-md-9" </div> </div> </body>
2、中间采用bootstrap的标签页的方式,达到点击之后会用切换content和header标签的效果
<div class="col-md-9"> <div> <!-- Nav tabs --> <ul class="nav nav-tabs" role="tablist"> <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab" data-toggle="tab">文章</a></li> <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">随笔</a></li> <li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">交友</a></li> <li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">园子</a></li> </ul> <!-- Tab panes --> <div class="tab-content"> <div role="tabpanel" class="tab-pane active" id="home"> {% block back_content %} {% endblock %} </div> <div role="tabpanel" class="tab-pane" id="profile">随笔</div> <div role="tabpanel" class="tab-pane" id="messages">...</div> <div role="tabpanel" class="tab-pane" id="settings">...</div> </div> </div>
3、需要用户登录了之后才能进入后台用户管理界面,故login_required装饰器判断是否登录,未登录则直接跳转到login_url='/login/'界面
from django.contrib.auth.decorators import login_required @login_required(login_url='/login/') def back_home(request): article_list=models.Article.objects.filter(user=request.user) return render(request,'back/back_home.html',locals())
4、左侧采用collapse方式,点击会展开
<div class="col-md-3"> <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingOne"> <h4 class="panel-title"> <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> 操作 </a> </h4> </div> <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne"> <div class="panel-body"> <a href="/addarticle/">添加文章</a> </div> <div class="panel-body"> <a href="">添加随笔</a> </div> </div> </div> </div>
5、用block 导入基准html
{% extends 'back/back_base.html' %} {% block back_content %} <table class="table table-striped table-hover"> <thead> <tr> <th>文章标题</th> <th>评论数</th> <th>点赞数</th> <th>操作</th> <th>操作</th> </tr> </thead> <tbody> {% for article in article_list %} <tr> <td>{{ article.title }}</td> <td>{{ article.comment_count }}</td> <td>{{ article.up_count }}</td> <td><a href="">修改</a></td> <td><a href="">删除</a></td> </tr> {% endfor %} </tbody> </table> {% endblock %}
十一、后台管理保存文章
1、在html页面使用了Kindedit编辑器,只需将库包至于static目录下即可,需保证
<textarea name="content" id="editor_id" cols="50" rows="10"></textarea>的id与kindedit一样,即可被调用
{% extends 'back/back_base.html' %} {% block back_content %} <div> <form action="" method="post"> {% csrf_token %} <div class="text-info"><h4>添加文章</h4></div> <p>标题</p> <p><input type="text" class="form-control" name="title"></p> <p>内容(Kindedit编辑器,不支持拖放/粘贴上传图片)</p> <p> <textarea name="content" id="editor_id" cols="50" rows="10"></textarea> </p> <p> <button class="btn btn-primary">提交</button> </p> </form> </div> <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script> <script> KindEditor.ready(function (K) { window.editor = K.create('#editor_id', { width: '100%', height: '600px', resizeType: 0, } ); }); </script> {% endblock %}
2、上传文字需要用bs4清洗,html显示的时候只需要content内容,但是详情页面的时候需要html样式等,故存储str(content)
# 安装 pip3 install lxml
# pip3 install BeautifulSoup4
@login_required(login_url='/login/')
def addarticle(request):
if request.method=='POST':
title=request.POST.get('title')
content=request.POST.get('content'
)
# content='<p>sddd</p><a>oooo</a><script>alert(123)</script>'
#生成一个soup的对象,传两个参数,第一个是要解析的html,第二个是使用的解析器
soup=BeautifulSoup(content,'html.parser')
print(str(soup))
# 拿到html内的文本内容
desc=soup.text[0:150]+'...'
# 查找html内所有的标签,放到一个列表里
ll=soup.find_all()
# print(ll) #[obj,obj...]
for tag in ll:
print(tag.name)
print(type(tag))
# tag.name 标签的名称
if tag.name=='script':
# 从整个html文档里删掉当前标签
tag.decompose()
# print(ll)
# aa=soup.find(name='script')
# print(aa)
# print(str(soup)) #<p>sddd</p><a>oooo</a><script>alert(123)</script>
models.Article.objects.create(title=title,content=str(soup),desc=desc,user=request.user)
return redirect('/backhome/')
return render(request,'back/article_add.html')
3、在back_base界面, <a href="/addarticle/">添加文章</a>关联上述所诉逻辑
<div class="col-md-3"> <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> <div class="panel panel-default"> <div class="panel-heading" role="tab" id="headingOne"> <h4 class="panel-title"> <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne"> 操作 </a> </h4> </div> <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne"> <div class="panel-body"> <a href="/addarticle/">添加文章</a> </div> <div class="panel-body"> <a href="">添加随笔</a> </div> </div> </div> </div>
十二、点赞、点踩
1、articledetail页面的设计,继承自base.html,评论html直接引用自博客园的html和css样式
{% extends 'base.html' %} {% block detail %} <style> #div_digg { float: right; margin-bottom: 10px; margin-right: 30px; font-size: 12px; width: 125px; text-align: center; margin-top: 10px; } .diggit { float: left; width: 46px; height: 52px; background: url(/static/img/upup.gif) no-repeat; 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; } .clear { clear: both; } </style> <div> <p><h4 class="text-center">{{ article.title }}</h4></p> <p>{{ article.content|safe }}</p> <div class="clearfix"> <div id="div_digg"> <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article.up_count }}</span> </div> <div class="buryit action"> <span class="burynum" id="bury_count">{{ article.down_count }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> </div>
2、is_up = $(this).hasClass('diggit')结果要么是true或者false,
var obj = $(this).children('span'),当点击时要么是点up,down的,this代指diggit、buryit事件,这句话的意思是obj为其子节点为span的对象
<script> var par_id = ''; $(".action").click(function () { var is_up = $(this).hasClass('diggit') //alert(is_up) var obj = $(this).children('span') $.ajax({ url: '/diggit/', type: 'post', //谁对那篇文章点赞或点踩 data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'article_id':{{ article.pk }}, 'is_up': is_up }, success: function (data) { console.log(data) $(".diggword").text(data.msg) if (data.status == 1) { obj.text(Number(obj.text()) + 1) } } }) })
3、articledetail页面展示,因为展示方法重复了,可以把下面重用的包装成共用函数
url(r'^(?P<username>\w+)/article/(?P<pk>\d+)', views.article_detail),
url(r'^diggit/', views.diggit),
def article_detail(request,username,pk): # 正常情况下应该有一堆安全性校验 # user=models.UserInfo.objects.filter(username=username).first() blog=user.blog article=models.Article.objects.filter(pk=pk).first() # 获取当前文章下所有评论 comment_list=models.Comment.objects.filter(article_id=pk) from django.db.models import Count # 查询每个标签下的文章数(分组查询) # 分组查询1 group by谁,用谁做基表 # 2 filter在前,表示查询 ,filter在后,表示过滤,having # 3 values在前,表示group by 在后,取字段 # 查询当前站点下每个标签下的文章数(分组查询) # tag_count = models.Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values_list('title', 'c') tag_count = models.Tag.objects.all().values('pk').filter(blog=blog).annotate(c=Count("article__title")).values_list( 'title', 'c') # 查询当前站点下每个分类下的文章数 category_count = models.Category.objects.filter(blog=blog).annotate(c=Count('article__nid')).values_list('title', 'c') from django.db.models.functions import TruncMonth # 查询当前站点每个月份下的文章数 # time_count=models.Article.objects.annotate(y_m=TruncMonth('create_time')) # for i in time_count: # print(i.title) # print(i.y_m) time_count = models.Article.objects.filter(user=user).annotate(y_m=TruncMonth('create_time')).values( 'y_m').annotate( c=Count('y_m')).values_list('y_m', 'c') return render(request,'article_detail.html',locals())
4、注释的时候将logic写清楚
post传过来的都是str类型的,需要转化成python认识的bool类型,通过json反序列化,is_up=json.loads(is_up)
import json from django.db.models import F def diggit(request): back_msg={'status':None,'msg':None} if request.user.is_authenticated: # 先查询是否已经点过 # 如果没有点过,去点赞表存数据 # 去文章表修改点赞数据 article_id=request.POST.get('article_id') # 注意: is_up=request.POST.get('is_up') print(type(is_up)) is_up=json.loads(is_up) ret=models.ArticleUpDown.objects.filter(user=request.user,article_id=article_id) if not ret: models.ArticleUpDown.objects.create(user=request.user,article_id=article_id,is_up=is_up) if is_up: models.Article.objects.filter(pk=article_id).update(up_count=F('up_count')+1) back_msg['status']=1 back_msg['msg'] = '点赞成功' else: models.Article.objects.filter(pk=article_id).update(down_count=F('down_count') + 1) back_msg['status'] = 1 back_msg['msg'] = '点踩成功' else: back_msg['status'] = 0 back_msg['msg'] = '您已经点过了' else: back_msg['status'] = 0 back_msg['msg'] = '您没有登录' return JsonResponse(back_msg)
十三、评论(render显示和ajax显示)
1、提交评论
$(".btn_submit").click(function () { var content = $("#comment_content").val() if (par_id) { var num = content.indexOf('\n') + 1 content = content.slice(num) alert(content) } $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'article_id':{{ article.pk }}, 'comment': content, 'par_id': par_id }, success: function (data) { $("#comment_content").val("") console.log(data)
为了确保数据写入,创建事务with transaction.atomic():,失败则回滚
def comment(request): back_msg={'status':False,'msg':None} if request.user.is_authenticated: # 评论表添加数据 # 文章表,修改评论个数 article_id=request.POST.get('article_id') comment=request.POST.get('comment') par_id=request.POST.get('par_id') # 事务 with transaction.atomic(): ret=models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_comment_id=par_id) models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) back_msg['status']=True back_msg['user_name']=ret.user.username back_msg['time']=ret.create_time.strftime('%Y-%m-%d') back_msg['content']=ret.content back_msg['msg']='评论成功' else: back_msg['status'] = False back_msg['msg'] = '您没有登录' return JsonResponse(back_msg)
2、render显示,刷新界面才显示评论更新上传了
<div> <ul class="list-group comment_list"> {% for comment in comment_list %} {# #17楼 2018-02-25 18:47 隔壁古二蛋 #} <li class="list-group-item"> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.create_time|date:'Y-m-d' }}</span> <span>{{ comment.user.username }}</span> <span><a class="pull-right my_reply" user="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span><p>{{ comment.content }}</p> </li> {% endfor %} </ul> </div>
3、提交根评论及ajax显示,及render显示,
{# 评论 #} <div> <p>发表评论</p> <p> 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="刘清政"> </p> <p>评论内容</p> <p><textarea name="" id="comment_content" cols="60" rows="10"></textarea></p> <button class="btn btn-primary btn_submit">回复</button> </div>
es6字符串替换,<span>${ time }</span>,需要什么比如time,根据后台去取
if (data.status) { var username = data.user_name; var time = data.time; var content = data.content var par_name=data.par_name var par_content=data.par_content var ss='' if(par_name){ ss = ` <li class="list-group-item"> <span>${ time }</span> <span>${ username}</span> <div class="well"> <span>@ ${par_name}</span> <p>${par_content}</p> </div> <p>${ content }</p> </li> ` }else{ ss = ` <li class="list-group-item"> <span>${ time }</span> <span>${ username}</span> <p>${ content }</p> </li> ` } $(".comment_list").append(ss) } } }) })
后台
from django.db import transaction def comment(request): back_msg={'status':False,'msg':None} if request.user.is_authenticated: # 评论表添加数据 # 文章表,修改评论个数 article_id=request.POST.get('article_id') comment=request.POST.get('comment') par_id=request.POST.get('par_id') # 事物 with transaction.atomic(): ret=models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_comment_id=par_id) models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) if par_id: back_msg['par_name']=ret.parent_comment.user.username back_msg['par_content']=ret.parent_comment.content back_msg['status']=True back_msg['user_name']=ret.user.username back_msg['time']=ret.create_time.strftime('%Y-%m-%d') back_msg['content']=ret.content back_msg['msg']='评论成功' else: back_msg['status'] = False back_msg['msg'] = '您没有登录' return JsonResponse(back_msg)
4、子评论,点击回复触发事件,同时将要用的父评论ID等信息直接放置于attr属性上,方便调用
<ul class="list-group comment_list"> {% for comment in comment_list %} {# #17楼 2018-02-25 18:47 隔壁古二蛋 #} <li class="list-group-item"> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.create_time|date:'Y-m-d' }}</span> <span>{{ comment.user.username }}</span> <span><a class="pull-right my_reply" user="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span> {% if comment.parent_comment %} <div class="well"> <span>@{{ comment.parent_comment.user.username }}</span> <p>{{ comment.parent_comment.content }}</p> </div>
使得点击回复之后直接focus到textarea标签,写响应子评论
父评论par_id,定义全局,使得后面都可调用
$(".my_reply").click(function () { var name = "@" + $(this).attr('user') + '\n' par_id = $(this).attr('comment_id') $("#comment_content").focus() $("#comment_content").val(name) })
5、判断是否为子评论,若是,则截取第一段内容,将后面的保存在content里,若不是,则不处理
$(".btn_submit").click(function () { var content = $("#comment_content").val() if (par_id) { var num = content.indexOf('\n') + 1 content = content.slice(num) #slice去索引之后的值 alert(content) }
判断是否有父评论,有则加@父id+comment
<ul class="list-group comment_list"> {% for comment in comment_list %} {# #17楼 2018-02-25 18:47 隔壁古二蛋 #} <li class="list-group-item"> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.create_time|date:'Y-m-d' }}</span> <span>{{ comment.user.username }}</span> <span><a class="pull-right my_reply" user="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span> {% if comment.parent_comment %} <div class="well"> <span>@{{ comment.parent_comment.user.username }}</span> <p>{{ comment.parent_comment.content }}</p> </div> {% endif %} <p>{{ comment.content }}</p>
同时再做个ajax显示功能,及一直带有render的显示功能,需要什么参数,需要什么参数,去后台返回
$(".btn_submit").click(function () { var content = $("#comment_content").val() if (par_id) { var num = content.indexOf('\n') + 1 content = content.slice(num) alert(content) } $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'article_id':{{ article.pk }}, 'comment': content, 'par_id': par_id }, success: function (data) { $("#comment_content").val("") console.log(data) if (data.status) { var username = data.user_name; var time = data.time; var content = data.content var par_name=data.par_name var par_content=data.par_content var ss='' if(par_name){ ss = ` <li class="list-group-item"> <span>${ time }</span> <span>${ username}</span> <div class="well"> <span>@ ${par_name}</span> <p>${par_content}</p> </div> <p>${ content }</p> </li> ` }else{ ss = ` <li class="list-group-item"> <span>${ time }</span> <span>${ username}</span> <p>${ content }</p> </li> ` } $(".comment_list").append(ss) } } }) })
6、总的,有根评论、子评论的ajax、render显示功能
{% extends 'base.html' %} {% block detail %} <style> #div_digg { float: right; margin-bottom: 10px; margin-right: 30px; font-size: 12px; width: 125px; text-align: center; margin-top: 10px; } .diggit { float: left; width: 46px; height: 52px; background: url(/static/img/upup.gif) no-repeat; 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; } .clear { clear: both; } </style> <div> <p><h4 class="text-center">{{ article.title }}</h4></p> <p>{{ article.content|safe }}</p> <div class="clearfix"> <div id="div_digg"> <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article.up_count }}</span> </div> <div class="buryit action"> <span class="burynum" id="bury_count">{{ article.down_count }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> </div> {# 评论列表#} <div> <ul class="list-group comment_list"> {% for comment in comment_list %} {# #17楼 2018-02-25 18:47 隔壁古二蛋 #} <li class="list-group-item"> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.create_time|date:'Y-m-d' }}</span> <span>{{ comment.user.username }}</span> <span><a class="pull-right my_reply" user="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span> {% if comment.parent_comment %} <div class="well"> <span>@{{ comment.parent_comment.user.username }}</span> <p>{{ comment.parent_comment.content }}</p> </div> {% endif %} <p>{{ comment.content }}</p> </li> {% endfor %} </ul> </div> {# 评论 #} <div> <p>发表评论</p> <p> 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="刘清政"> </p> <p>评论内容</p> <p><textarea name="" id="comment_content" cols="60" rows="10"></textarea></p> <button class="btn btn-primary btn_submit">回复</button> </div> </div> <script> var par_id = ''; $(".action").click(function () { var is_up = $(this).hasClass('diggit') //alert(is_up) var obj = $(this).children('span') $.ajax({ url: '/diggit/', type: 'post', //谁对那篇文章点赞或点踩 data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'article_id':{{ article.pk }}, 'is_up': is_up }, success: function (data) { console.log(data) $(".diggword").text(data.msg) if (data.status == 1) { obj.text(Number(obj.text()) + 1) } } }) }) $(".btn_submit").click(function () { var content = $("#comment_content").val() if (par_id) { var num = content.indexOf('\n') + 1 content = content.slice(num) alert(content) } $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': '{{ csrf_token }}', 'article_id':{{ article.pk }}, 'comment': content, 'par_id': par_id }, success: function (data) { $("#comment_content").val("") console.log(data) if (data.status) { var username = data.user_name; var time = data.time; var content = data.content var par_name=data.par_name var par_content=data.par_content var ss='' if(par_name){ ss = ` <li class="list-group-item"> <span>${ time }</span> <span>${ username}</span> <div class="well"> <span>@ ${par_name}</span> <p>${par_content}</p> </div> <p>${ content }</p> </li> ` }else{ ss = ` <li class="list-group-item"> <span>${ time }</span> <span>${ username}</span> <p>${ content }</p> </li> ` } $(".comment_list").append(ss) } } }) }) $(".my_reply").click(function () { var name = "@" + $(this).attr('user') + '\n' par_id = $(this).attr('comment_id') $("#comment_content").focus() $("#comment_content").val(name) }) </script> {% endblock %}
from django.db import transaction def comment(request): back_msg={'status':False,'msg':None} if request.user.is_authenticated: # 评论表添加数据 # 文章表,修改评论个数 article_id=request.POST.get('article_id') comment=request.POST.get('comment') par_id=request.POST.get('par_id') # 事物 with transaction.atomic(): ret=models.Comment.objects.create(user=request.user,article_id=article_id,content=comment,parent_comment_id=par_id) models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) if par_id: back_msg['par_name']=ret.parent_comment.user.username back_msg['par_content']=ret.parent_comment.content back_msg['status']=True back_msg['user_name']=ret.user.username back_msg['time']=ret.create_time.strftime('%Y-%m-%d') back_msg['content']=ret.content back_msg['msg']='评论成功' else: back_msg['status'] = False back_msg['msg'] = '您没有登录' return JsonResponse(back_msg)