项目-博客园
1.建表部分
这里使用了抽象类,抽象类是一个自带的数据库,但是有一些信息不完善,可以补充需要的信息制作更完善的数据库和ORM。
谁使用更抽象类,就要继承AbstractUser。抽象类有很多好处,django内部封装了很多方法可以直接操作数据,很方便。
创建的信息为了做到查询效率最好,做了一部分的分表操作,所以在使用中涉及到正查和反查(跨表)(后面再做叙述)。
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser 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") create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) blog = models.OneToOneField(to='Blog', to_field='nid', null=True) def __str__(self): return self.username class Blog(models.Model): """ 博客信息 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='个人博客标题', max_length=64) site = models.CharField(verbose_name='个人博客后缀', max_length=32, unique=True) theme = models.CharField(verbose_name='博客主题', max_length=32) def __str__(self): return self.title class Category(models.Model): """ 博主个人文章分类表 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类标题', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid') def __str__(self): return self.title class Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid') def __str__(self): return self.title class Article(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') create_time = models.DateTimeField(verbose_name='创建时间') comment_count = models.IntegerField(default=0) up_count = models.IntegerField(default=0) down_count = models.IntegerField(default=0) category = models.ForeignKey(to='Category', to_field='nid', null=True) user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid') tags = models.ManyToManyField( to="Tag", through='Article2Tag', through_fields=('article', 'tag'), ) def __str__(self): return self.title class ArticleDetail(models.Model): """ 文章详细表 """ nid = models.AutoField(primary_key=True) content = models.TextField() article = models.OneToOneField(to='Article', to_field='nid') class Article2Tag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid') tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid') class Meta: unique_together = [ ('article', 'tag'), ] def __str__(self): v = self.article.title + "--" + self.tag.title return v class ArticleUpDown(models.Model): """ 点赞表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True) article = models.ForeignKey("Article", null=True) is_up = models.BooleanField(default=True) class Meta: unique_together = [ ('article', 'user'), ] class Comment(models.Model): """ 评论表 """ nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid') user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid') content = models.CharField(verbose_name='评论内容', max_length=255) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) parent_comment = models.ForeignKey('self', null=True) def __str__(self): return self.content
2.Reg部分
前端页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> form{ margin-top: 100px; } #avatar{ display: none; } </style> </head> <body> <div class="container" > <div class="row"> <div class="col-md-8 col-md-offset-2"> <form action="" novalidate> {% for i in form %} <div class="form-group"> <label for="">{{ i.label }}</label> <div> {{ i }} <span class="error"></span> </div> </div> {% endfor %} <div class="form-group"> <label for="avatar">头像<img width="100px" height="100px" src="/static/img/default.png" class="avatar" alt=""></label> <input type="file" class="btn btn-default btn-group" value="上传头像" id="avatar"> </div> <div class="form-group"> <input type="button" class="btn btn-default btn-group reg_btn" value="提交"> </div> </form> </div> </div> </div> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.2.1.min.js"></script> <script> $('#avatar').change(function () { var choose_file = $(this)[0].files[0]; var reader = new FileReader(); reader.readAsDataURL(choose_file); reader.onload = function () { $('.avatar').attr('src',reader.result) } }) $(".reg_btn").click(function () { var formdata=new FormData(); formdata.append("name",$("#id_name").val()); formdata.append("pwd",$("#id_pwd").val()); formdata.append("repeat_pwd",$("#id_repeat_pwd").val()); formdata.append("email",$("#id_email").val()); formdata.append("avatar",$("#avatar")[0].files[0]); formdata.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val()); $.ajax({ url:"", type:"post", processData:false, contentType:false, data:formdata, success:function (data) { //console.log(data) if (data.user){ // 注册成功 location.href="/login/" } else {// 注册失败 console.log(data.error_dict); // q清空错误信息 $("form span").html(""); $("form .form-group").removeClass("has-error") // 加载错误信息 $.each(data.error_dict,function (field,error_list) { if(field=="__all__"){ $("#id_repeat_pwd").next().html(error_list[0]).css("color",'red'); $("#id_repeat_pwd").parent().addClass("has-error") } $("#id_"+field).next().html(error_list[0]).css("color",'red'); $("#id_"+field).parent().addClass("has-error") }) } } }) }) </script> </body> </html>
Views部分
其中涉及到几个关键点:
0.使用ajax方法,不用刷新页面。全部校验合格以后才跳转。
1. 使用form表单做前端验证方便快捷,只需在框架下写出应满足的规则即可。
2. 除class定义了基本方法静态属性以后,还可以自定义内部方法。此处用到的是局部校验和全局校验,分别写两个函数(钩子-全局钩子和局部钩子)。
其中一个是局部钩子,把form表单中的一个数据和后台比对,另一个是全局钩子,用以比较前后输入的数据是否一致。
这两个钩子就是class类的内部方法,在前台把数据打包封装好传过来以后,用request.POST实例化一个对象,然后在使用实例化对象is_valid()的时候类的内部会执行校验,
不但会校验自身属性的要求,还会校验自定义方法(局部钩子,全局钩子)。错误的话把错误放在error.message里面,传回去显示在界面上。
3.前台数据传输,没有文件的时候就好说,把数据放在字典里或者直接使用serialize()方法直接传输就好,有文件就要使用一下方法了。
注意的要点是:
1.前端使用form表单的serialize()方法时,发送的数据的key是标签文件的name属性,这个key值必须和form表单的对应项名字一样,否则会报错。
2.还有就是传输带有文件的数据时,要使用Formdata方法,在提交数据的时候必须实例化一个formdata对象,即var formdata = new FormData(),然后要传输的数据都formdata.append(key,value) value使用Jquery获取val().
3.在ajax提交数据的时候必须要有session数据,首先要在前端页面上写上{{csrf_token}}提交一次,获取csrf的数据值,然后把他也作为一个数据append到formdata数据中。
4.在ajax传输配置里面还要加上processData:false,contentType:false,这两项要不传输会报错。
5.没有csrf_token的时候,写上{%csrf_token%},在html页面中就能获取到含有csrf_token的那句话,根据他的格式获取csrf_token.
6.cleaned_data(没有问题的数据以key,value的形式都存在这里面) error(存的都是有问题信息的key value)
7.error_dict里面有可能有好几个错误,所以取[0],保证能拿到一个错误。
8.出现了全局错误(两次密码不匹配) 会放到error里面 其key的名字是__all__ error[''__all__'']可以取到其错误,错误提示是自己定义的抛出的错误。
9.做的更完善的方法就是每一个选项能校验的都做局部校验,但是写起来就比较麻烦。(实际项目中好的项目是每个都校验)
10.因为用的是auth,所以创建用户使用create_user(信息)创建。
11.运用label标签中的关联选项(for),点击label标签就等于点击了for关联的那个input标签(input标签可以隐藏)。
12.上传文件使用var reader = FileReader()创建对象,reader.readAsDataURL(choose_file);读取文件路径。reader.onload = function () {$('.avatar').attr('src',reader.result)} 根据读取的结果上传图片(onload是加载完以后执行的)。
13.后端接收文件的时候是request.FILES.get(图片文件的名字-传输字典的key)。后端存的是文件本地存储的地址。
14.文件的接收还要配置环境MEDIA_ROOT=os.path.join(BASE_DIR,"blog","media")=======MEDIA_URL="/media/" 意思是把media的路径定位到项目文件的blog的media文件下,然后访问/media/就等于是访问media环境路径下的文件,根据后边的值取找对应的文件。url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), url索引路径按照这个配。并不是执行那个函数,而是取找相应的文件。这样的配置完的路径就可以直接从外部访问,等价于开放。其他的外部则访问不到。之前的媒体文件默认上传到根目录,有了这条就上传到这里面了。
15.在models里创建头像文件的时候默认了上传到avatars/ 所以自然就会生成一个文件夹 然后装上传的文件。有默认值,有新值以后覆盖。
16.create_user(avatar=avatar)一步操作完成了两件事1.把文件路径读取出来 2.把文件对象上传到models里制定的文件下。(原因就是他的格式是FileFiled 直接用他的对象名就可以完成这个两个操作)直接读取文件的操作应该是打开文件 写文件 文件关闭。这一步被涵盖了。
class RegForm(forms.Form): name = forms.CharField(label='用户名',max_length=32,widget=widgets.TextInput(attrs={'class':'form-control'})) pwd = forms.CharField(label='密码',max_length=32,widget=widgets.PasswordInput(attrs={'class':'form-control'})) repeat_pwd = forms.CharField(label='确认密码',max_length=32,widget=widgets.PasswordInput(attrs={'class':'form-control'})) email = forms.CharField(label='邮箱',max_length=32,widget=widgets.EmailInput(attrs={'class':'form-control'})) def clean_name(self): val = self.cleaned_data.get('name') ret = UserInfo.objects.filter(username=val) if not ret: return val else: raise ValidationError('该用户已存在') def clean(self): if self.cleaned_data.get("pwd")==self.cleaned_data.get("repeat_pwd"): return self.cleaned_data else: raise ValidationError("两次密码不一致!") def reg(request): if request.method == 'POST': dic = {'user':None,'error_dict':None} print(request.POST) form = RegForm(request.POST) if form.is_valid(): print(form.cleaned_data) print(request.FILES) dic['user'] = form.cleaned_data['name'] user = form.cleaned_data.get("name") pwd = form.cleaned_data.get("pwd") email = form.cleaned_data.get("email") avatar = request.FILES.get("avatar") if avatar: UserInfo.objects.create_user(username=user,password=pwd,email=email,avatar=avatar) else: UserInfo.objects.create_user(username=user,password=pwd,email=email) else: print(form.errors) dic['error_dict'] = form.errors return JsonResponse(dic) form = RegForm() return render(request,'reg.html',{'form':form})
3.Login部分
前端页面
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.2.1.min.js"></script> <style> .container{ margin-top: 100px; } </style> </head><body> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="" id="form"> <div class="form-group"> <label for="user">用户名</label> <input type="text" class="form-control" id="user" name="user" placeholder="Username"> </div> <div class="form-group"> <label for="pwd">密码</label> <input type="password" class="form-control" id="pwd" name="pwd" placeholder="Password"> </div> <div class="form-group"> <label for="pwd">验证码</label> <div class="row"> <div class="col-md-6"> <input type="text" class="form-control" id="valid_code" name="valid_code"> </div> <div class="col-md-6"> <img width="250" height="40" id="image" src="/get_valid_img/" alt=""> </div> </div> </div> <input type="button" class="btn btn-default login_btn" value="提交"><span class="error" style="color: red;margin-left: 20px"></span> </form> </div> </div> </div> <script> $('#image').click(function () { this.src += '?' $('this')[0].src += '?' }) $('.login_btn').on('click',function () { $.ajax({ url:'/login/', type:'POST', data: $('#form').serialize(), success:function (data) { if(data.status){ location.href = '/index/' }else{ $('.error').text('验证码错误,请重新输入') } } }) }) </script> </body> </html>
views
前端所采用的为ajax提交验证方法。没有很多亮点,就是前端提交数据可数据库比对。
其中唯一的亮点就是使用PIL模块生成随机验证码,在另一篇文章已经列出,此处不再赘述。
1.在没有的登陆的时候,传输数据里面会有.user(有值) 在没有登陆的时候是匿名用户或者空,登陆成功以后就变成用户名了。(cookie-session的作用)django自动生成的session。在数据库中存有session,用以比对。(使用的auth框架,所以才会有user的默认值)
2.session可以作为服务端和客户端的加密联络方式,使用request.session['key']='value' 等于给了一个字典{‘key’,‘value’},可以用来验证。没有数据沟通的情况下可以用session来实现前后端沟通。
3.在登陆验证的时候,拿前端用户输入的内容和刚才后端传给前端的session值对比,比对成功才有可能登陆成功。
4.其中登陆校验使用的是auth的内部方法 user = auth.authenticate(username=user,password=pwd) 若果返回的对象(实例化成功),则user是一个实例化对象,要不就为空,然后auth.login(request,user) 证明登陆成功,在后台的session注册,保持一段登陆时间。
def login(request): if request.is_ajax(): print(request.POST.get('valid_code')) print(request.POST.get('user')) print(request.POST.get('pwd')) valid_code = request.POST.get('valid_code') user = request.POST.get('user') pwd = request.POST.get('pwd') ret = {'status':False,'message':None} valid_str = request.session.get('valid_str') if valid_code.lower() == valid_str.lower(): # 从数据库中拿用户数据 user = auth.authenticate(username=user,password=pwd) if user: ret['status'] = True auth.login(request,user) else: ret['message'] = '用户名或密码错误,请重新输入' else: ret['message'] = '验证码错误,请重新输入' return JsonResponse(ret) return render(request,'login.html')
4.index部分
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <script src="/static/jquery-3.2.1.min.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <style> .article_item .desc{ margin-left: -40px; font-size: 13px; text-align: justify; } .content{ margin-top: 5px; } </style> </head> <body> <nav class="navbar navbar-inverse"> <div class="container-fluid"> <!-- 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="#">Link <span class="sr-only">(current)</span></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.username %} <li><a href="#">{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> {# <li><a href="#">注销</a></li>#} <li><a href="/out/">注销</a></li> <li><a href="#">修改密码</a></li> <li><a href="#">更换头像</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} <li><a href="/login/">登录</a></li> <li><a href="">注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> <div class="row"> <div class="col-md-2"> <div class="panel panel-primary"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-warning"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> <div class="col-md-7"> <div class="article_list"> {# 循环展示数据库中的数据#} {% for article in article_list %} <div class="article_item"> {# 标题属性#} <div><h5><a href="/{{ article.user.username }}/articles/{{ article.nid }}">{{ article.title }}</a></h5></div> {# 文章地址格式 username/articles/nid#} <div class="row"> {# 获取头像属性 注意要配media环境变量 从对象拿出来的只是相对路径#} {# upload_to 的地址外加文件名就是对象获取到的路径#} <div class="col-md-2"><img width="60" height="60" src="/media{{ article.user.avatar }}" alt=""></div> <div class="col-md-9 desc"> {# 获取描述属性#} <p>{{ article.desc }}</p> </div> </div> <div class="small content"> {# 获取用户名属性#} <span><a href="">{{ article.user.username }}</a>发布于</span> {# 获取时间属性 |date:'Y-m-d' (确定展示方式)#} <span>{{ article.create_time|date:"Y-m-d" }}</span> <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>赞({{ article.up_count }}) </div> <hr> </div> {% endfor %} </div> </div> <div class="col-md-3"> <div class="panel panel-danger"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> </body> </html>
views
这部分最简单,把所有的文章都取出来发给前端展示就行了。
{{对象.属性}} 就可展示变量数据了。前端展示和后端展示一样,获取了一个对象.属性(正向)对象.表名__set(反向).first(取到一个对象).属性.属性(如果是外联的话)。和后端查询基本一样。
def index(request): article_list = Article.objects.all() return render(request,"index.html",{"article_list":article_list})
logout views
这里面注销方法实际已经被做成了logout模块,只需要直接引用,然后把request作为参数穿进去就可以了。
特别注意!!!自定义函数名和logout名字不能起的一样,否则会陷入无限递归中。
注销返回登陆页面即可。
def out(request): logout(request) # 两种都可以 return redirect('/login/') return render(request,'login.html')
5.homesite部分
展示两个部分,一个是文章的分类信息(信息分类)。一个是文章内容(获取信息)。
然后在的分类和标题里面(每个分类实体)给出链接(采用了路径和数据拼接,生成新路径的形式)。点击不同链接返回不同的页面内容。
base模板
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> {% load exercise_tyags%} <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css"> <link rel="stylesheet" href="/static/css/article_detail.css"> <script src="/static/jquery-3.2.1.min.js"></script> <style> *{ margin: 0; } .header{ width: 100%; height: 50px; background-color: #336699; line-height: 50px; color: white; font-size: 16px; } .header .title{ margin-left: 20px; } .comment_tree_item{ margin-left: 20px; } </style> </head> <body> <div class="header"> <p class="title">{{ blog.title }}</p> </div> <div class="container"> <div class="col-md-3"> {% get_menu username%} </div> <div class="col-md-8"> {% block content %} {% endblock content%} </div> </div> </body> </html>
扩展base内容 {% get_menu username%} (查询方法见下面)
from django import template register = template.Library() #把旗下的动作作为template.Library里面的方法 from ..models import * @register.inclusion_tag('menu.html') #方法所需要的文件 def get_menu(username): user = UserInfo.objects.filter(username=username).first() blog = user.blog from django.db.models import Count cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article")).values_list("title", "c") print(cate_list) tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article")).values_list("title", "c") print(tag_list) date_list = Article.objects.filter(user=user).extra( select={"create_ym": "DATE_FORMAT(create_time,'%%Y-%%m')"}).values("create_ym").annotate( c=Count("nid")).values_list("create_ym", "c") print(date_list) #最后试一下看看能否换成locals() return {"username":username,"cate_list":cate_list,"tag_list":tag_list,"date_list":date_list}
<div class="menu"> <div class="panel panel-info"> <div class="panel-heading">我的分类</div> <div class="panel-body"> {% for cate in cate_list %} <p><a href="/blog/{{ username }}/cate/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading">标签分类</div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/blog/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">日期归档</div> <div class="panel-body"> {% for date in date_list %} <p><a href="/blog/{{ username }}/achrive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div>
扩展base内容 {% block content %} {% endblock content %}
def homesite(request,username,**kwargs): user = UserInfo.objects.filter(username=username).first() if not user: return HttpResponse('该页面不存在') blog = user.blog if not kwargs: article_list=Article.objects.filter(user=user) else: condition = kwargs.get("condition") param = kwargs.get("param") if condition == "cate": article_list = Article.objects.filter(user=user).filter(category__title=param) elif condition == "tag": article_list = Article.objects.filter(user=user).filter(tags__title=param) else: year, month = param.split("-") article_list = Article.objects.filter(user=user).filter(create_time__year=year, create_time__month=month) return render(request, "homesite.html", locals())
{% extends "base.html" %} {% block content %} <div class="article_list"> {% for article in article_list %} <div class="article_item"> <div><h5><a href="">{{ article.title }}</a></h5></div> <div class="row"> <div class="col-md-9 desc"> <p>{{ article.desc }}</p> </div> </div> <div class="small"> 发布于 <span>{{ article.create_time|date:"Y-m-d" }}</span> <span class="glyphicon glyphicon-comment"></span>评论({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>赞({{ article.up_count }}) </div> <hr> </div> {% endfor %} </div> {% endblock content %}
具体使用思路:
第一步
在最上面{% load exercise_tyags %}加载自己写好的方法的那个文件名(已经注册到template.Library中)
第二步
{% 函数名 函数返回的变量 %} 用以调用注册方法函数的返回数据
第三步
{% block content %}
要填充的内容
{% endblock content%}
第四步
在要填充的文件最上方填写{% extends "base.html" %} extends+母版文件的带后缀的名称
{% block content %}
填充的内容
{% endblock content%}
用这个和base的代码匹配 渲染出整体的效果
整体思路
1 在app文件中新建一个templatetags文件
2里面新建一个py文件 包含封装的一部分views文件
3 引用template.Library 把它做成装饰器 后面含有要渲染的文件名(html文件)
4 装饰完一个函数 可以返回这个函数和接收数据的html文件
-----------------------------------------------------------------
以上部分可以作为一个单独整体直接调用
5 制作一个base文件 存放网页版式的基本框架 内部含有各种接口 可以让其他文件填充进来
含有的两种填充方式
1 {% get_menu username%} 从template.library里自己制作的函数外加html和数据填充过来
2 {% block content %}
第三方模板 最上写上{% extends "base.html" %} 拼接模板数据生成页面
{% endblock content%}
-------------------------------------------------------------------
博客的这一部分牵涉到了相关数据的查询(分类-聚合):
ORM查询(如果设有related_name的反向可以按照related_name查询) 尽量多使用数据库的内部方法 比如filter 少使用对象的属性 1 filter方法 后面跟上筛选的对象(数据库key=要匹配对象) 2 使用filter方法返回的是一个queryset对象 使用.first()获取到对象 匹配不上则返回拿到None 3 正向查询使用自己类下的.属性.属性的属性获取到对象 如果是反向查询则.属性(小写表明)_set.属性的属性获取到对象 4 如果是一对一的查询 正向方法直接.属性 反向查询不用加.set 因为对象唯一 5 如果查询到的对象是一个集合 其中有a,b,c属性 要取的话使用values() (返回一个字典)或 value_list ()(返回一个元组)里面填上要看的属性 正向查询values or values_list(‘自己表格的属性(外联到外表)_外表的属性值’)(正向用字段) 反向查询values or values_list(‘别人表格的表明(外联到外表)_外表的属性值’)(反向用表名) 5.5 查询自己 自己表名.object.values(‘以谁分组就写谁’).annotate(名字=计算方法(甲酸对象)).values(要看的对象) 6 重要属性annotate 其实质是在之前的对象基础上(xxx=计算方法(count avg)(数据的一个属性))然后给给自己加一个属性xxx=计算结果 后面.values()可以查看这个新设置的属性(其本质是给annotate之前筛选显示筛选结果打上annotate分组计算的标签) 7 重要属性xxx.objects.all().aggregate(自己命名的属性名=计算方法(‘对象的属性’)) 把所有东西聚合 然后根据某一计算方法计算其中某一共有属性的值 返回结果是自己命名的属性名和计算结果 8 注意 在filter查询中 filter(xxx=???)其中xxx要是models中的对象则后面要跟一个对象 如果xxx是数据库表头中的一个量 则???可以是直接的数据(跟数据库中一样就可以查到) 9 使用filter(表名__属性) 不使用filter就反向表名__set(也可以是related name).属性 10 .extra是自主创建一条SQL语句,一般适用于ORM查询语句不能翻译成SQL的情况。查询完以后变成查询结果的一条属性。
6.点赞部分
点赞的原理是前端ajax获取点击的内容,传输到后台,和数据库比对。看是否点赞,然后返回状态信息给前端。最后在指定位置上显示状态信息。
1.|safe是保证文章内容显示如果有html语言的话也正常显示(在存入数据库的时候已经做过安全处理)。
2.点赞的支持和反对内容都放在一个大标签中。支持是一个类,反对是一个类。通过获取其中一个class属性(有是True,没有是False)。这作为一个条件为后边的判断做依据。
3.然后根据True和False对支持和反对数据做修改。(其中对数据修改需要使用F函数,而且还要用到事件(transaction)-以保证数据库的数据确实被修改过以后才给前端返回。(没有数据,创建数据,对数据操作))
4.出现错误给前端(已经存在这个数据),获取这个错误的支持反对值,然后前端根据这个错误信息来判断是支持过了还是反对过了,给客户显示。
5.前后端信息交互使用Json化的字典。
前端(包含js)部分
<h3 class="text-center">{{ article.title }}</h3> <div class="content"> {{ article.articledetail.content|safe }} </div> <div class="clearfix"> <div id="div_digg"> <div class="diggit digg"> <span class="diggnum" id="digg_count">{{ article.up_count }}</span> </div> <div class="buryit digg"> <span class="burynum" id="bury_count">{{ article.down_count }}</span> </div> </div> </div> <div id="digg_word" class="pull-right"></div> <div id="info" article_id="{{ article.pk }}" username="{{ request.user.username }}"></div>
$('#div_digg .digg').click(function(){ {#最妙的就是这块 获取其中一个标签的标签 能获取到就是True 不能就是 False#} {#在相同中有不同就是最大的关键点#} {#True or False表示了一种状态 同时又可以作为条件语句的判断条件 高!!!#} {#获得结果是True or False#} var up = $(this).hasClass('diggit') var article_id= $('#info').attr('article_id') {#alert(123)#} $.ajax({ url: '/poll/', type: 'post', data: { up:up, article_id:article_id, csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val()}, success: function(data){ {#后边应该回过来的数据是:#} {#1 这个文章是选中还是没选中#} {#选过:#} {#选中的是哪个 是点赞还是踩灭(这个需要一个返回的字典中的另一个key value才能实现)#} {#没选过:给相应的部分+1 or -1#} {#{status:True or False(有没有) up_down:(是什么)}#} console.log(123); console.log(data); if (data.state){ if (data.up_down){ var val = parseInt($('#digg_count').text()) + 1 $('#digg_count').val(val) } else{ var val = parseInt($('#bury_count').text()) + 1 $('#bury_count').text(val) } }else{ console.log(data.first_operate); if(data.first_operate){ $("#digg_word").html("您已经推荐过").css({"color":"red","margin-right":"30px"}) }else{ $("#digg_word").html("您已经反对过").css({"color":"red","margin-right":"30px"}); } } } {#要有文章的id 作者的id 赞True or False#} }) })
后端部分
def poll(request): print(request.POST) is_up=json.loads(request.POST.get("up")) print(is_up) article_id=request.POST.get("article_id") user_id=request.user.pk res={"state":True} from django.db import transaction try: with transaction.atomic(): ArticleUpDown.objects.create(is_up=is_up,article_id=article_id,user_id=user_id) if is_up: Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1) else: Article.objects.filter(pk=article_id).update(down_count=F("down_count")+1) except Exception as e : res["state"]=False res["first_operate"]=ArticleUpDown.objects.filter(article_id=article_id,user_id=user_id).first().is_up return JsonResponse(res)
7.评论部分
基本(评论楼)
1.注意使用pid(父评论id)根据他可以确定评论的关系。有pid的,在评论渲染的时候可以反映出之前是对谁评论的。
2.不点击回复,pid=空,点击了以后,获取当前的评论id最为pid。同时要@用户名,以显示回复该评论。
3.在提交评论的时候,需要防范用户删除了@用户名,但是还保留pid的情况。验证有否有@用户名,没有则设pid为空。有则不变。
4.注意写入评论的时候还要判断是不是回复的内容,是的话要去除@用户名。
5.后端在创建评论的时候,还要用到事件(transcation)保证评论写入了以后再给文章评论数+1。
6.给前端返回数据的时候不能回复对象,要把对象里面的属性一一拿出来,放在字典里然后序列化以后传给前端(这样才能传输)。
7.文章的楼层直接看<li>标签的长度(看看谁包含着<li>标签,就获取它的长度)。
8.前端文本替换用<标签种类>xxxxxxxxxxx'+内容+'xxxxxxxxxxx<标签种类>,就可以添加或替换原有内容了。
9.把新创建的含有后端数据内容的标签添加到原来评论里(使用append方法),就可以随评随增了。(同时要清pid,要不下次评论等于默认有pid了)
评论输入:
<div class="comment_region"> <div class="row"> <div class="col-md-7"> <p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"></p> <p>评论内容:</p> <textarea name="" id="comment_text" cols="60" rows="10"></textarea> <button class="btn btn-default pull-right comment_btn">提交</button> </div> </div> </div>
评论渲染:
<ul class="list-group comment_list"> {% for comment in comment_list %} <li class="list-group-item comment_item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> <a class="pull-right reply" pk="{{ comment.pk }}" username="{{ comment.user.username }}">回复</a> </div> {% if comment.parent_comment_id %} <div class="parent_comment_info well"> <a href="">@{{ comment.parent_comment.user.username }}</a> <span>{{ comment.parent_comment.content }}</span> </div> {% endif %} <div> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul>
前端JS:
var pid=""; $('.comment_btn').click(function () {if("{{request.user.username}}"){var article_id = $('#info').attr('article_id') if ($("#comment_text").val()[0]!=="@"){ pid="" } if(pid){ var index = $('#comment_text').val().indexOf('\n'); var content = $('#comment_text').val().slice(index+1); }else{ var content = $('#comment_text').val(); } $.ajax({ url: '/comment/', type: 'post', data:{ article_id : article_id, content:content, pid:pid, csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val() }, success:function (data) { {#返回的只有一个字典res = {"state": True}#} {#表明评论写入数据库成功#} {#清掉之前的评论#} console.log(123); console.log(data); if (data.state){ // 提交功能 {#获取楼层这个比较妙 直接看for循环内容的长度!!!#} {#等于是看 <li></li>的个数 也就是长度#} var floor = $('.comment_list .comment_item').length + 1; var ctime = data.time; var username = $('#info').attr('username'); var content = data.content; var s = '<li class="list-group-item comment_item"> <div> <a href="">#'+floor+'楼</a> <span>'+ctime+'</span> <a href="">'+username+'</a></div> <div> <p>'+content+'</p> </div> </li>'
$('.comment_list').append(s) // 清空 $("#comment_text").val(""); pid='' }else{ // 提交失败 {#可以显示提交失败 在评论下方#} } } }) }else{ location.href = '/login/' } }) {#执行什么事件主要是根据点击不同按钮绑定的ajax的事件不同而不同的#} {#点击回复按钮的时间#} {# comment_item 是一个总得标签 reply是其中一个按钮选项#} $(".comment_item .reply").click(function () { $("#comment_text").focus(); var val = '@'+$(this).attr('username')+'\n'; $("#comment_text").val(val); pid = $(this).attr('pk') })
后端:
def comment(request): print(request) article_id = request.POST.get("article_id") content = request.POST.get("content") pid = request.POST.get("pid") user_id = request.user.pk # 其中user_id比较特殊 只用其中pk就可代指user_id res = {"state": True} with transaction.atomic(): # 创建完返回的对象是ORM生产的类对象 Jquery查找到的是Queryset对象 所能调用的方法不一样! if not pid: # 提交根评论 obj = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, ) print(obj,type(obj)) print(obj.content) else: # 提交子评论 obj = Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) print(obj, type(obj)) print(obj.content) Article.objects.filter(pk=article_id).update(comment_count=F("comment_count") + 1) #时间是datetime.xxx的类型 要转换 res['time'] = obj.create_time.strftime('%Y-%m-%d %H:%M') # content是str格式 res['content'] = obj.content return JsonResponse(res)
进阶(评论树)
评论树和评论楼的差别就表现在可以不断的繁衍子评论,然后有层级的显示。
前端JS:
{#一个函数被括号括起来了 后面再加一个括号 就是执行这个匿名函数#} (function () { {#向后台的view提交信息有两种方式 #} {# 1是从url里面提交 拼接使用的参数 (参数) 但是再ajax的提交里面不用使用()括起来#} {# 2是从data里面传输 这个可以从request.data.get('对应的数据名')#} {#可以没有提交方式(默认get)数据 #} $.ajax({ url:"/tree/"+$("#info").attr("article_id"), success:function (comment_list) { var comment_html=""; {#循环展示数据 传过来的数据是一个列表里面套了很多字典#} $.each(comment_list,function (index,comment) { console.log(comment); {#把一个列表里的每一个字典都拿出来 然后把每一个字典里的元素都创建一个变量接收#} var username=comment.user__username; var content=comment.content; var pk=comment.pk; var pid=comment.parent_comment_id; s='<div class="comment_tree_item" id='+pk+'><span>'+username+'</span> <span>'+content+'</span> </div>' if(pid){ $("#"+pid).append(s); }else { $(".comment_tree").append(s); } }) } }) })();
方法 :
1.在渲染每一层级的的标签的时候都加上一个class和id属性(插入评论)。
2.在style中设置含有该class属性margin or padding-left为一个值。
3.判断-只要是有pid的,就在pid的div标签中塞入子标签。
4.因为子标签也有偏移属性,父标签已经偏移,所以子标签等于在父标签的基础上附加了一个偏移。从而有了层级属性。
8.文章内容添加部分
(等待补全)