1、建表阶段:
关系图:
解释:
这个表总共有十张表: 用户表(userinfo): 建表原因:保存用户的信息。继承了django自带的AbstractUser用户信息表 字段: nickname(昵称) telephone(手机号) avatar(头像) create_time(创建时间) 博主个人站点表(Blog): 建表原因:为了显示个人的所有博客信息 字段: title(个人博客标题) site(个人博客后缀) theme(个人博客主题) user(对应用户)---------和用户表一对一 文章分类表(Category): 建表原因:为了把文章细分成类 字段: title(文章标题) blog(所属博客)---------和博客表多对一 文章表(Article): 建表原因:存储个人文章的信息 字段: title(文章标题) desc(文章描述) read(文章阅读量) comment_count(文章评论量) up_count(文章置顶) down_count(文章置末尾) create_time(文章创建时间) category(文章类型)----和类型表多对一 user(所属用户)--------和用户表多对一 tags(标签)------------和标签表多对多 文章详情表(ArticleDetail): 建表原因:存储个人文章的详细信息 content(文章类容) article(所属文章)----------和文章表示一对一 评论表(Comment): 建表原因:对文章的评论 content(评论类容) create_time(创建时间) up_count(评论量) user(评论者)--------------和用户多对一 article(评论文章)---------和文章表多对一 parent_comment(父级评论)--和自己自关联(为的是能自己评论自己,可以评论别人的评论) 评论点赞表(CommentUp): 建表原因:对评论的点赞 user(点赞的用户)------------和用户表多对一 comment(被点赞评论)----------和评论表多对一 文章点赞表(ArticleUp): 建表原因:对文章的点赞 user(点赞的用户)------------和用户表多对一 comment(被点赞文章)----------和文章表多对一 标签表(Tag): title(标签名称) bolg(所属博客)--------------和博客表多对一 文章和标签第二张表(Article2Tag): 建表原因:为了形成文章和标签的多对多关系,让后期添加数据更方便 article(文章)---------------和文章表多对一 tag(标签)--------------------和标签表多对一
数据库代码实现:
from django.db import models from django.contrib.auth.models import AbstractUser #admin 表中的有些字段就不会显示 # Create your models here. class UserInfo(AbstractUser): # settings: AUTH_USER_MODEL = "blog.UserInfo" """ 用户信息 """ nid = models.BigAutoField(primary_key=True) nickname = models.CharField(verbose_name='昵称', max_length=32) telephone = models.CharField(max_length=11, blank=True, null=True, unique=True, verbose_name='手机号码') avatar = models.FileField(verbose_name='头像', upload_to='avatar', default="/avatar/default.png") create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) class Meta: verbose_name_plural = "用户信息表" def __str__(self): return self.username #这里可以看做是验证成功返回的值 class Blog(models.Model): """ 站点信息 """ nid = models.BigAutoField(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) user = models.OneToOneField(to='UserInfo', to_field='nid') class Meta: verbose_name_plural = "站点信息表" 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 Meta: verbose_name_plural="文章分类表" class Article(models.Model): ''' 文章表,最主要的一张表 ''' nid = models.BigAutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') #外键用更加详细的描述 read_count = models.IntegerField(default=0) comment_count = models.IntegerField(default=0) up_count = models.IntegerField(default=0) down_count = models.IntegerField(default=0) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) # auto_now_add 当前的时间 category = models.ForeignKey(verbose_name='文章类型', 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'), ) type_choices = [ (1, "编程语言"), (2, "软件设计"), (3, "前端"), (4, "操作系统"), (5, "数据库"), ] article_type_id = models.IntegerField(choices=type_choices, default=None) class Meta: verbose_name_plural = "文章表" def __str__(self): return self.title class ArticleDetail(models.Model): """ 文章详细表 """ nid = models.AutoField(primary_key=True) content = models.TextField(verbose_name='文章内容', ) article = models.OneToOneField(verbose_name='所属文章', to='Article', to_field='nid') #每个文章的简介只是对应一片文章 class Meta: verbose_name_plural = "文章详细表" def __str__(self): return self.content class Comment(models.Model): """ 评论表 """ nid = models.BigAutoField(primary_key=True) content = models.CharField(verbose_name='评论内容', max_length=255) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) up_count = models.IntegerField(default=0) user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid') #评论表是由多个人组件起来的 article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid') #评论的是文章 parent_comment = models.ForeignKey('self', blank=True, null=True, verbose_name='父级评论') #自关联,父级评论 class Meta: verbose_name_plural = "评论表" def __str__(self): return self.content class CommentUp(models.Model): """ 评论点赞表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True) #用户点赞 comment = models.ForeignKey("Comment", null=True) #对评论点赞 class Meta: verbose_name_plural = "评论点赞表" class ArticleUp(models.Model): """ 文章点赞表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True) #用户点赞 article = models.ForeignKey("Article", null=True) #文章点赞 class Meta: verbose_name_plural = "文章点赞表" 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') #多个标签属于一个博客,, class Meta: verbose_name_plural = "标签表" def __str__(self): return self.title 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'), ]
2、登录界面的实现:
界面显示:
前段代码示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>博客登录页面</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css/"> </head> <body> <div class="container col-md-offset-3 col-sm-6 " > <div class="panel panel-primary " style="margin-top:80px"> <div class="panel-heading">请登录</div> <div class="panel-body"> <form class="form-horizontal" method="post"> {% csrf_token %} <div class="form-group "> <label for="username" class="col-sm-3 control-label">账号:</label> <div class="col-sm-8"> <input type="text" class="form-control" id="username" placeholder="账号" > </div> </div> <div class="form-group"> <label for="password" class="col-sm-3 control-label">密码:</label> <div class="col-sm-8"> <input type="password" class="form-control" id="password" placeholder="密码" > </div> </div> <div class="form-group"> <label for="verify" class="col-sm-3 control-label">验证码:</label> <div class="col-sm-3"> <input type="text" class="form-control" id="verify" placeholder="验证码"> </div> <div class="col-md-4"> <img class="validCode_img" src="/get_validCode_img/" alt="" width="200px" height="35px"> </div> </div> <div id="qq" class="col-md-offset-4"></div> <dic class="error"></dic> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="button" class="btn btn-primary login_btn" style="margin-left: 70px">登录</button> <a href="/reg/" type="button" class="btn btn-info" id="refBtn">注册</a> </div> </div> </form> </div> </div></div> <script src="/static/jquery-3.2.1.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $(".login_btn").click(function () { $.ajax({ url:"/login/", type:"POST", headers: {"X-CSRFToken": $.cookie('csrftoken')}, data:{ "username":$("#username").val(), "password":$("#password").val(), "validCode":$("#verify").val() }, success:function (data) { console.log(data); var response=JSON.parse(data); if (response["is_login"]){ location.href="/index/" } else { $(".error").html(response["error_msg"]).css("color","red") } } }); {#点击图片刷新验证码#} $(".validCode_img").click(function () { $(this)[0].src+="?"; })}) </script> </body> </html>
后台登录代码示例:
def login(request): if request.method == "POST": print(request.POST) username=request.POST.get("username") password=request.POST.get("password") volidCode=request.POST.get("validCode") login_response={"is_login":False,"error_msg":None} if volidCode.upper()==request.session.get("keepValidCode").upper(): #对比验证码 user = auth.authenticate(username=username,password=password) #对比账户和密码 if user: #如果user正确 login_response["is_login"]=True #就把login_response的值改为true auth.login(request,user) #应为继承了uesrinfo的表所以就用 else: login_response["error_msg"] = "用户名或密码错误" else: login_response["error_msg"] = '验证码错误' import json return HttpResponse(json.dumps(login_response)) else: return render(request, "login.html")
后台验证码的代码示例:
def get_validCode_img(request): from io import BytesIO import random #导入的画笔包ImageDraw,字体包ImageFont from PIL import Image,ImageDraw,ImageFont 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,"RGB") font=ImageFont.truetype("app01/static/font/kumo.ttf",25) valid_list=[] for i in range(5): random_num = str(random.randint(0, 9)) random_lower_zimu = chr(random.randint(65, 90)) random_upper_zimu = chr(random.randint(97, 122)) random_char = random.choice([random_num, random_lower_zimu, random_upper_zimu]) draw.text([5 + i * 24, 10], random_char, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), font=font) valid_list.append(random_char) f = BytesIO() img.save(f, "png") data = f.getvalue() valid_str = "".join(valid_list) print(valid_str) request.session["keepValidCode"] = valid_str return HttpResponse(data)
流程客户端请求服务端的流程图:
流程:
流程: 1、客户端向————(第一次git请求)————>服务端返回登录页 2、延验码-----src(url再次请求服务端)---->服务端执行验证码函数,生成验证码,保存到session中 3、用户输入内容---点击提交发送agax请求---(post)------>服务端从ruquest中去agax请求的信息, 从sessing中取出验证码进行和agax中的信息进行匹配 成功-----进行下一步登录验证 失败-----记录错误信息 | | | | 验证用户信息的状态 用字典的形式保存到login_response | 正确用:"is_login":true保存状态写入session 错误:返回一个字段的字符串 最后 return login_response
使用到的知识点;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1,用ajax提交数据 $(".log_btn").click(function(){ $.ajax({ url:url路径 type:访问状态 headers: {"X-CSRFToken": $.cookie('csrftoken')},#跨站请求访问 返回的数据 data:{ 字典名:$(".字段标识名").val() } 接收数据 sussecc:funsess(data){ 函数 } }) } 2、图片刷新(写在sussecc函数中) 原理:给图片绑定一个点击事件:每次给src+?就会再次发送一次get请求,就起到了刷星的作用 dom对象 $(".validCode_img").click(function () { $(this)[0].src+="?";
3、注册功能实现
代码示例
前端代码示例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册页面</title> </head> <script src="/static/jquery-3.2.1.js"></script> <script src="/static/bootstrap-3.3.7/js/bootstrap.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/reg.css"> <script src="/static/jquery.cookie.js"></script> <body> <div class="container"> <div class="row"> <div class="col-md-5 col-md-offset-3"> <form> {% csrf_token %} <div class="form-group"> <label for="username">用户名</label> {{ form_obj.username }} </div> <div class="form-group"> <label for="password">密码</label> {{ form_obj.password }} </div> <div class="form-group"> <label for="password">确认密码</label> {{ form_obj.repeat_pwd }} </div> <div class="form-group"> <label for="email">邮箱</label> {{ form_obj.email }} </div> <div class="form-group avatar"> <label for="avatar">头像选择</label> <img src="/static/img/lll.jpg" alt="" id="avatar_img"> <input type="file" class="form-control" id="avatar_file" > </div> <input type="button" value="提交" class="btn btn-primary" id="subBtn"><span class="error"></span> </form> </div> </div> </div> <script> // 头像预览 $("#avatar_file").change(function () { var ele_file = this.files[0]; //this.files var reader=new FileReader(); reader.readAsDataURL(ele_file);//获取当前选择的图片路径 reader.onload=function () { $("#avatar_img")[0].src=this.result } }); {# 提交数据#} $("#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("avatar_img",$("#avatar_file")[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); if (data.user){ location.href="/login/" } else { console.log(data.errorsList); $(".pull-right").html("").parent().removeClass("has-error"); //去除前面一次的错误信息 //parent().removeClass("has-error");去除input框的颜色 $.each(data.errorsList,function (i,j) { console.log(i,j); // i j // username ["用户名不能为空"] // password ["密码不能为空"] // repeat_pwd ["验证密码不能为空"] // email ["邮箱不能为空"] $span=$("<span>"); $span.addClass("pull-right").css("color","red"); {# 添加一个span标签#} $span.html(j[0]); {#把log的值j写入span中#} $("#id_"+i).after($span).parent().addClass("has-error") //#id_"+i找到当前的错误的input框 //parent().addClass("has-error")给错误的input框加一个红色的颜色 if (i=="__all__"){ $("#id_repeat_pwd").after($span) //全局钩子的错误信息 } }) } } }) }) </script> </body> </html>
后台代码示例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def reg(request): if request.method == "POST": #若果是post请求 #进入RegForm中验证格式是否错误 form_obj=RegForm(request.POST) regResponse={"user":None,"errorsList":None} # if form_obj.is_valid(): # cleaned_data 正确的信息 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") user_obj=models.UserInfo.objects.create_user(username=username,password=password,email=email,avatar=avatar_img,nickname=username) regResponse["user"]=user_obj.password else: regResponse["errorsList"]=form_obj.errors print("&&&&&&&&&&&&&&&&&&&&",form_obj.errors) print("===================",form_obj.cleaned_data) # #<ul class="errorlist"><li>email<ul class="errorlist"><li>邮箱不能为空</li></ul></li></ul> import json return HttpResponse(json.dumps(regResponse)) else: form_obj=RegForm() return render(request,"reg.html",{"form_obj":form_obj}) #如果是get请求将返回一个页面,并且请from生成的标签返回
后台生成form表单代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import forms from django.forms import widgets,ValidationError from app01 import models class RegForm(forms.Form): #账户 required=TRUE必须要填的 error_messages错误提示信息 username=forms.CharField(max_length=12,min_length=2,required=True, error_messages={"required":"用户名不能为空","min_length":"用户名必须大于五位",}, widget=widgets.TextInput(attrs={"class":"form-control","placeholder":"username"}) ) #密码 password=forms.CharField(min_length=6,required=True, error_messages={"required": "密码不能为空", "max_length": "用户名必须大于五位", }, widget=widgets.PasswordInput( attrs={"class": "form-control", "placeholder": "password"} )) #确认密码 repeat_pwd=forms.CharField(min_length=6, error_messages={"required": "验证密码不能为空", "max_length": "密码名必须大于六位", }, widget=widgets.PasswordInput(attrs={"class": "form-control", "placeholder": "repeat_pwd"} )) # 邮件 email=forms.EmailField(required=True, error_messages={"required": "邮箱不能为空" }, widget=widgets.EmailInput( attrs={"class": "form-control", "placeholder": "email"} )) #用户名的钩子 def clean_username(self): ret=models.UserInfo.objects.filter(username=self.cleaned_data.get("username")) print("****************************>>>>>",self.cleaned_data.get) print ("*****************************") if not ret: return self.cleaned_data.get("username") else: raise ValidationError("用户名已注册") #密码的钩子 def clean_password(self): data=self.cleaned_data.get("password") if not data.isdigit(): return self.cleaned_data.get("password") else: raise ValidationError("密码不能全是数字") #全局钩子 def clean(self): if self.cleaned_data.get("password") == self.cleaned_data.get("repeat_pwd"): return self.cleaned_data else: raise ValidationError("两次密码不一致")
代码流程
注册 1. form组件生成html标签 在RegFrom类中继承From这个类, 利用这个类的方法,生成input标签,在这个类中可设置,标签的属性 - username - password - email 2. 利用后端传来的form在前端生成html标签 第一次get请求时后返回RegFrom类实例化的对象, 前端生成- username,- password,- emai,- avatar对应的四个标签 显示图片: - 图片和上传文件折叠 - 作用:上传文件覆盖图片,透明度为零 用户点击图片其实点击上传文件 -把 - 把上传文件和图片的父亲标签设置为相对路径 设置长宽,自己 - 图片预览 - 用户点击上传文件 上传文件的标签发生变化,会触发change事件 - 取到用户上传的文件 var ele_file = this.files[0]; - 创建FileReader()对象4 - 把上传文件的对象的路径写入FileReader()对象,结果为FileReader().result - FileReader()加载onload事件,把FileReader().result结果写入到img标签 3. 用户提交数据 用户通过ajav提交数据: 如果要传二进制的数据必须要用FormData打包 在传二进制的时候必须要到的参数 -------contentType:false, ------processData:false, -----headers:{"X-CSRFToken":$.cookie('csrftoken')},防止跨站请求 --用FormData打包数据 var formdata=new FormData(); formdata.append("username",$("#id_username").val()); --通过data把数据传到后台函数 4.验证数据 post请求走Regfrom函数 -有错误利用RegForm 的钩子返回错误,----》 有错误将错误信息用键值对保存到errorst中 没有错误将前端拿到的数据放入cleaned_data中 注册函数中判断form_obj.is_valid对象是否正确 ---正确则获取数据写入数据库---返回一个状态 regResponse["user"]=user_obj.username-----给前端 ---错误则获取数据写入数据库---返回一个状太regResponse["errorsList"]=form_obj.errors-----给前端 后台函数利用form_obj.cleaned_data拿到数据 5、前端用ajax接收处理错误信息 $.each(data.errorsList,function (i,j) { {# controls.log(data.errorsList)#} {# console.log(i,j);#} // i j ------- username ["用户名不能为空"] ------- password ["密码不能为空"] ------- repeat_pwd ["验证密码不能为空"] ------- email ["邮箱不能为空"] $span=$("<span>"); $span.addClass("pull-right").css("color","red"); --------{# 添加一个span标签#} $span.html(j[0]); --------{#把log的值j写入span中#} $("#id_"+i).after($span).parent().addClass("has-error") -------#id_"+i找到当前的错误的input框 -------parent().addClass("has-error")给错误的input框加一个红色的颜色 if (i=="__all__"){ $("#id_repeat_pwd").after($span) -------全局钩子的错误信息 } })
4、博客首页的实现
后台代码示例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def index(request,*args,**kwargs): 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})
前端代码的实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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.min.css"> <script src="/static/jquery-3.2.1.js"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script> <style> .hides { display: none; } </style> <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="#">博文</a></li> <li><a href="#">新闻</a></li> <li><a href="#">收藏</a></li> <li><a href="#">小组</a></li> <li><a href="#">招聘</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {% if request.user.is_authenticated %} <li><a href=""><span class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li> <li><a href="/log_out/">注销</a></li> {% else %} <li><a href="/login/">登录</a></li> <li><a href="/reg/">注册</a></li> {% endif %} <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="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container"> {#左侧菜单部分#} <div class="col-md-2"> <div class="panel panel-default"> <div class="panel-heading">网站分类</div> {% for cate in cate_list %} <div class="panel panel-default "> <div class="panel-heading cate_title "> <div class="panel-body">{{ cate.name }} </div> </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 class="col-md-7"> {% for article in article_list %} <div class="article_itme"> <div class="title"><a href="/blog/{{ article.user.username }}">{{ article.title }}</a></div> <div class="row"> <div class="avatar col-md-2"> <a href="/blog/{{ article.user.username }}"><img src="/media/{{ article.user.avatar }}" alt="" width="70" height="70"></a> </div> <div class="desc col-md-10"><p>{{ article.desc }}</p></div> </div> <div class="row"> <a href="/blog/{{ article.user.username }}" class="name">{{ article.user.username }}</a> <span class="time ">发布于:{{ article.create_time|date:"Y-m-d" }}</span> <a href="" class="glyphicon glyphicon-comment"> 评论:{{ article.comment_count }}</a> <a href="" class="glyphicon glyphicon-thumbs-up"> 点赞:{{ article.comment_count }}</a> </div> <hr> </div> {% endfor %} </div> {#右侧部分#} <div class="col-md-3"> <div class="panel panel-default"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> <script> $(".cate_title").mouseover(function () { $(this).next().slideDown(300) }).parent().mouseleave(function () { $(this).children(".panel-body").slideUp(300) }) </script> </body> </html>
逻辑流程:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
问题一: 在导入图片的时候需要在url中配置,在查找图片的后面需要加上.url 模块的导入 from django.conf import settings from django.views.static import serve url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),#配置图片 问题二: 给博客站点的分类下的字分类进行隐藏 .hides { display: none; } 问题三:给分类下的给一个事件,鼠标移动就展开mouseover(鼠标移动事件) $(".cate_title").mouseover(function () { $(this).next().slideDown(300) <script> $(".cate_title").mouseover(function () { $(this).next().slideDown(300) }).parent().mouseleave(function () { $(this).children(".panel-body").slideUp(300) }) </script> url(r'^index', views.index) 2:创建视图函数,从数据库取出数据,返回到前端。 -------找到文章表,取出文章对象,返回。 -------找到网站分类表,取出分类对象,返回。 3:返回到主页html进行渲染: 1:-----拿到文章对象用for标签循环,{{.文章内容}}进行渲染。 2:-----拿到分类对象用for标签循环,{{.网站分类,父}}进行渲染。 3:-----拿到网站分类对象,{{.(反向查询,表名_set).网站分类,子}}进行渲染。 4:JQ做动态折叠效果,鼠标悬浮展开,鼠标移开折叠。 1:-----选择器找到网站分类,给他绑定一个(悬浮)事件,当触发这个事件(鼠标悬浮),执行function,$(this)找到选择器对象,执行展开事件。 2:-----选择器找到网站分类的父级,给他绑定一个(悬浮)执行function,$(this)找到选择器对象,执行折叠事件事件。 $(".cate_title").mouseover(function () { $(this).next().slideDown(300) }).parent().mouseleave(function () { $(this).children(".panel-footer").slideUp(300) }); 5:点击网站分类,子分类,查找到这个分类下的所有文章。 -------分类渲染在<a>标签中,给他一个url,鼠标点击,------走urls进行路由匹配--------找url的视图函数--------进行查找 a标签:---------/site/{{网站分类,name}} url:------------以site开头,有名分组(接收点击的分类),按关键字传参.*匹配所有,返回一个键值对,返回到视图函数。 url(r'^site/(?P<site_article_category>.*)/$', views.index), 视图函数--------**kwargs接收键值对,判断kwargs,判断正确,进行网站查询,否则查询所有文章。 查询------------找到文章表,过滤(反向查询按字段)出文章的网站分类,子分类 =kwargs传过来的分类,返回到前端进行渲染。 article_list = models.Article.objects.filter(site_article_category__name=kwargs.get("site_article_category"))