博客系统
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、登录界面的实现:
界面显示:
前段代码示例:
博客登录页面
请登录
后台登录代码示例:
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
使用到的知识点;
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、注册功能实现
代码示例
前端代码示例:
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.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>
后台代码示例:
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) # #import json return HttpResponse(json.dumps(regResponse)) else: form_obj=RegForm() return render(request,"reg.html",{"form_obj":form_obj}) #如果是get请求将返回一个页面,并且请from生成的标签返回
- 邮箱不能为空
后台生成form表单代码
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.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、博客首页的实现
后台代码示例:
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})
前端代码的实现:
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">
<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 navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a class="navbar-brand" href="#">博客园a>
div>
<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="#">Actiona>li>
<li><a href="#">Another actiona>li>
<li><a href="#">Something else herea>li>
<li role="separator" class="divider">li>
<li><a href="#">Separated linka>li>
<li role="separator" class="divider">li>
<li><a href="#">One more separated linka>li>
ul>
li>
ul>
div>
div>
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 titlediv>
<div class="panel-body">
Panel content
div>
div>
<div class="panel panel-default">
<div class="panel-heading">Panel heading without titlediv>
<div class="panel-body">
Panel content
div>
div>
<div class="panel panel-default">
<div class="panel-heading">Panel heading without titlediv>
<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>
逻辑流程:
问题一: 在导入图片的时候需要在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"))