30-项目实战BBS(仿抽屉论坛)
创建如上的关系:
方法1
m2m 会自动创建联合唯一索引(但是数据表只能3列数据:id,user,news)
通过对象操作第三张表
方法2
自己创建第三张表,但是需要手工创建联合唯一索引【unique_together】(可以指定多列数据)
通过表操作第三张表
方法3.
m2m通过to关联m2m的另一张表,还可以through关联另一张表(通过through_fields指定字段),这种方式不需要创建第三张表
通过对象操作第三张表(只能查询,不能增加,修改,是因为这种方式定义的第三张表还可能有其他的列) + 通过表操作第三张表
备注:through_fields 接收一个二元组('field1', 'field2'),其中field1 为指向定义ManyToManyField 字段的模型的外键名称(本例中为group),field2 为指向目标模型的外键的名称(本例中为person). 更多参考 Django多表查询
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) class NewsType(models.Model): caption = models.CharField(max_length=16) class News(models.Model): title = models.CharField(verbose_name='标题',max_length=32) url = models.CharField(verbose_name='URL',max_length=255) avatar = models.CharField(verbose_name='头像',max_length=255) summary = models.CharField(verbose_name='简介',max_length=255) new_type = models.ForeignKey(verbose_name='新闻类型',to="NewsType") user = models.ForeignKey(verbose_name='发布者',to='UserInfo',related_name='c') ctime = models.DateTimeField(verbose_name='创建时间',auto_now_add=True) # 点赞和评论时,记着更新like_count,comment_count。自增1: F 实现 like_count = models.IntegerField(default=0) comment_count = models.IntegerField(default=0) # 第一种 # like = models.ManyToManyField(to='UserInfo') # 查询功能才能使用 # likes = models.ManyToManyField(to='UserInfo',through="Like",through_fields=('uuser','nnew')) # new_list = News.objects.all() # for obj in new_list: # obj.title, class Comment(models.Model): content = models.CharField(verbose_name='评论内容',max_length=255) new = models.ForeignKey(verbose_name='评论的新闻ID',to='News') user = models.ForeignKey(verbose_name='评论者',to='UserInfo') ctime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True) # 第二种 # class Like(models.Model): # user = models.ForeignKey(to='UserInfo') # news = models.ForeignKey(to='News') # ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True) # # class Meta: # unique_together = [ # ('user','news'), # ] class Like(models.Model): uuser = models.ForeignKey(to='UserInfo',related_name='a') nnew = models.ForeignKey(to='News',related_name='b') ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True) class Meta: unique_together = [ ('uuser','nnew'), ]
1. Bug解决 related_name,用于定义反向关联时候,使用的字段名称 related_query_name class A: title = models.CharField() obj = models.A.objects.get(id=1) obj.b_set.all() obj.xxxxxx_set.all() # related_query_name='xxxxxx' obj.uuuu.all() # related_name='uuuu' obj.x obj.u class B: xx ..xx fk1 = models.ForignKey(related_name='x') fk2 = models.ManyToMany(related_name='u') models.B.objects.filter(fk__title='xx') through_fields = (在关系表中与当前表建立FK关系的字段名称,在关系表中与目标表建立的FK关系字段名称)
chunks 类似有readlines
related_name的作用:反查。不用表名加set了
- BBS项目练习: 地址:http://dig.chouti.com/ 要求: 表结构设计 功能开发: 页面样式和布局 文章列表(分页) 点赞:思路,发送ajax请求: - 判断: 已经存在: like表中删除一条记录,new中like_count,自减1 不存在: like表中天剑一条记录,new中like_count,自加1 - 登录之后才能点赞 发布文章(上传图片) 1. 根据URL,自动获取标题和摘要 pip3 install beautifulsoup4 pip3 install requests 点击: 发送Ajax请求,将 http://music.163.com/#/song?id=188057 发送到后台 def get_title_summary(request): url = request.POST.get('url') import requests from bs4 import BeautifulSoup response = requests.get('http://music.163.com/#/song?id=188057') soup = BeautifulSoup(response.text,'html.parser') title = soup.find('title').text desc = soup.find('meta',attrs={'name': 'description'}).get('content') print(title) print(desc) data = {'title':title,'desc':decc} return HttpResponse(json.dumps(data)) 2. 基于Ajax实现图片上传 隐藏的Input框,放置头像路径 评论
#####################################
点击不同的标签分类(跳转不同的URL展示不同的内容)
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^all/(?P<new_type_id>\d+)/', views.index), # 将文章类型的id传递给url url(r'^', views.index), # 不要写在上面,会劫胡 ]
views.py
from django.shortcuts import render, HttpResponse from app01 import models import os def index(request, *args, **kwargs): """ 首页: - 显示所有的新闻类型 - 显示所有新闻列表 :param request: :param args: :param kwargs: :return: """ # current_new_type_id的值可能是:None, "1" "2" ...... current_new_type_id = kwargs.get('new_type_id') if current_new_type_id: current_new_type_id = int(current_new_type_id) # 获取当前选中的新闻类型 new_type_list = models.NewsType.objects.all() # 获取所有新闻类型分类 new_list = models.News.objects.filter(**kwargs) # 获取当前选中的新闻类型下的所有文章 return render(request, 'index.html', {'new_type_list': new_type_list, 'new_list': new_list, 'current_new_type_id': current_new_type_id})
models.py
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) class NewsType(models.Model): caption = models.CharField(max_length=16) class News(models.Model): title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name='URL', max_length=255) avatar = models.CharField(verbose_name='头像', max_length=255) summary = models.CharField(verbose_name='简介', max_length=255) new_type = models.ForeignKey(verbose_name='新闻类型', to="NewsType") user = models.ForeignKey( verbose_name='发布者', to='UserInfo', related_name='c') # related_name 反向查找的时候不在用【小写表名_set】(如果B表与A表有两个FK、m2m的时候很有用) # 如果B中关联了两个A的字段,可以在一个FK或m2m中定义一个related_name # 那么A的对象使用的时候,一个使用related_name,另外一个则用默认的(a.b_set) ctime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True), # 点赞和评论时,记着更新like_count,comment_count。自增1: F 实现 # 字段定义结尾不能加逗号,否则前端页面显示(<django.db.models.fields.IntegerField>,) like_count = models.IntegerField(default=0) comment_count = models.IntegerField(default=0) likes = models.ManyToManyField( to='UserInfo', through="Like", through_fields=( 'nnew', 'uuser')) class Comment(models.Model): content = models.CharField(verbose_name='评论内容', max_length=255) new = models.ForeignKey(verbose_name='评论的新闻ID', to='News') user = models.ForeignKey(verbose_name='评论者', to='UserInfo') ctime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True) class Like(models.Model): nnew = models.ForeignKey(to='News', related_name='b') uuser = models.ForeignKey(to='UserInfo', related_name='a') ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True) class Meta: unique_together = [ ('uuser', 'nnew'), ]
templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>分类列表</h1> <ul> {% if current_new_type_id %} <li><a href="/">全部</a></li> {% else %} <li><a style="color:green" href="/">全部</a></li> {% endif %} {% for row in new_type_list %} {% if row.id == current_new_type_id %} <li><a style="color:green" href="/all/{{ row.id }}/">{{ row.caption }}</a></li> {% else %} <li><a href="/all/{{ row.id }}/">{{ row.caption }}</a></li> {% endif %} {% endfor %} </ul> <h2>新闻列表</h2> <div> {% for row in new_list %} <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} <div style="display: inline-block;position: relative;"> 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a> </div> </div> </div> <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div> </div> <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div> </div> {% endfor %} </div> </body> </html>
实现的效果:
上传图片两种方法(FormData和伪Ajax的方法)
1. FormData实现图片上传
urls.py
from django.conf.urls import url from django.contrib import admin from app01.views import index,upload,upload_img from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^all/(?P<new_type_id>\d+)/', index), url(r'^upload/', views.upload), url(r'^upload_img/', views.upload_img), url(r'^', index), # 不要写在上面,会劫胡 ]
views.py
from django.shortcuts import render, HttpResponse from app01 import models import os
def upload(request):
return render(request, 'upload.html')
def upload_img(request):
obj = request.FILES.get('a1') # 从请求中获取图片
img_path = os.path.join('static', 'img', obj.name) # 拼接图片路径
with open(img_path, mode='wb') as f:
for chunk in obj.chunks():
f.write(chunk)
data = {
'status': True,
'path': img_path # 图片保存后,并将图片路径返回给前端
}
import json
return HttpResponse(json.dumps(data))
templates/upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>上传图片</h1> <input id="fafafa" type="file" name="fafafa" /> <input type="button" value="点我点我" onclick="uploadImg()" /> <input type="text" id="avatar" name="avatar" /> <!--这个可以对用户隐藏--> <div id="container"></div> <script src="/static/jquery-3.2.1.js"></script> <script> function uploadImg() { // 获取文件 // 上传文件 // 预览 var formData = new FormData(); //创建一个FormData空对象 formData.append('a1',$('#fafafa')[0].files[0]); //然后使用append方法添加key/value console.log(formData); $.ajax({ url: '/upload_img/', type: 'POST', data: formData, processData: false, // tell jQuery not to process the data contentType: false, // tell jQuery not to set contentType success:function (arg) { arg = JSON.parse(arg); if (arg.status){ var tag = document.createElement('img'); tag.src = "/" + arg.path; $('#container').empty(); $('#container').append(tag); //预览的效果即在页面生成一个img标签 $('#avatar').val(arg.path); } } }) } </script> </body> </html>
实现效果:
2. 伪Ajax实现图片上传(form + iframe)
iframe学习
urls.py
url(r'^upload2/', views.upload2),
views.py
def upload2(request): return render(request, 'upload2.html')
templates/upload2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form> <input type="text" /> <input type="submit" value="提交" /> </form> <input type="text"/> <input type="text" placeholder="请输入关键字" id="key" /> <input type="button" value="搜索" onclick="searchSB();" /> <div class="container"> <iframe id="im1" src="" style="width: 500px;height: 1000px;"></iframe> <iframe id="im2" src="" style="width: 500px;height: 1000px;"></iframe> </div> <script src="/static/jquery-3.2.1.js"></script> <script> function searchSB() { var val = $('#key').val(); $('#im1').attr("src","https://www.baidu.com/s?wd="+val); $('#im2').attr("src","https://www.sogou.com/web?query="+val); } </script> </body> </html>
效果
上传文件总结
4. 上传文件 - 基于FormData - 缺点,兼容性不好 - 优点,Ajax直接发送 - 伪Ajax,兼容性更好 - iframe,天生局部刷新 - form,天生整个页面刷新 普通POST请求: - Ajax(*) - 伪Ajax 上传内文件: - Ajax, FormData - 伪Ajax (*)
点赞方法实现
urls.py
from django.conf.urls import url from django.contrib import admin from app01.views import index,upload,upload_img from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^all/(?P<new_type_id>\d+)/', views.index), url(r'^do_like.html$', views.do_like), url(r'^', views.index), # 不要写在上面,会劫胡 ]
views.py
from django.shortcuts import render, HttpResponse
from app01 import models
import os
import json
from django.db.models import F
from django.db import transaction
from utils.response import LikeResponse #用了自定义的LikeResponse
def index(request, *args, **kwargs):
"""
首页:
- 显示所有的新闻类型
- 显示所有新闻列表
:param request:
:param args:
:param kwargs:
:return:
"""
# current_new_type_id的值可能是:None, "1" "2" ......
current_new_type_id = kwargs.get('new_type_id')
if current_new_type_id:
current_new_type_id = int(current_new_type_id) # 获取当前选中的新闻类型
new_type_list = models.NewsType.objects.all() # 获取所有新闻类型分类
new_list = models.News.objects.filter(**kwargs) # 获取当前选中的新闻类型下的所有文章
return render(request,
'index.html',
{'new_type_list': new_type_list,
'new_list': new_list,
'current_new_type_id': current_new_type_id})
def do_like(request):
"""
点赞
:param request:
:return:
"""
response = LikeResponse()
try:
new_id = request.POST.get('newId')
# 当前登录用户ID
# uid = request.session.get('uid')
uid = 1
# 判断当前用户是否已经点赞
exist_like = models.Like.objects.filter(
nnew_id=new_id, uuser_id=uid).count()
with transaction.atomic(): # 开启django的事务,但是只能处理事务,不能处理异常,所以外面加上异常判断
if exist_like:
# 已点赞,则取消
models.Like.objects.filter(
nnew_id=new_id, uuser_id=uid).delete()
models.News.objects.filter(
id=new_id).update(
like_count=F('like_count') - 1)
response.code = 666 # 返回封装的状态码
else:
# 未点赞,则加1
models.Like.objects.create(nnew_id=new_id, uuser_id=uid)
models.News.objects.filter(
id=new_id).update(
like_count=F('like_count') + 1)
response.code = 999 # 返回封装的状态码
except Exception as e:
response.msg = str(e) # 返回封装的错误信息
else:
response.status = True # 返回封装的状态值(True | False)
# json不能dumps对象,可以通过__dict__ 把对象换成dict
return HttpResponse(json.dumps(response.get_dict()))
models.py(同上)
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) class NewsType(models.Model): caption = models.CharField(max_length=16) class News(models.Model): title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name='URL', max_length=255) avatar = models.CharField(verbose_name='头像', max_length=255) summary = models.CharField(verbose_name='简介', max_length=255) new_type = models.ForeignKey(verbose_name='新闻类型', to="NewsType") user = models.ForeignKey( verbose_name='发布者', to='UserInfo', related_name='c') # related_name 反向查找的时候不在用【小写表名_set】(如果B表与A表有两个FK、m2m的时候很有用) # 如果B中关联了两个A的字段,可以在一个FK或m2m中定义一个related_name # 那么A的对象使用的时候,一个使用related_name,另外一个则用默认的(a.b_set) ctime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True), # 点赞和评论时,记着更新like_count,comment_count。自增1: F 实现 # 字段定义结尾不能加逗号,否则前端页面显示(<django.db.models.fields.IntegerField>,) like_count = models.IntegerField(default=0) comment_count = models.IntegerField(default=0) likes = models.ManyToManyField( to='UserInfo', through="Like", through_fields=( 'nnew', 'uuser')) class Comment(models.Model): content = models.CharField(verbose_name='评论内容', max_length=255) new = models.ForeignKey(verbose_name='评论的新闻ID', to='News') user = models.ForeignKey(verbose_name='评论者', to='UserInfo') ctime = models.DateTimeField(verbose_name='评论时间', auto_now_add=True) class Like(models.Model): nnew = models.ForeignKey(to='News', related_name='b') uuser = models.ForeignKey(to='UserInfo', related_name='a') ctime = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True) class Meta: unique_together = [ ('uuser', 'nnew'), ]
utils/response.py
class BaseResponse(object): # 定义BaseResponse def __init__(self): self.status = False self.data = None self.msg = None def get_dict(self): return self.__dict__ # json不能dumps对象,可以通过__dict__ 把对象换成dict,这里将其定义为方法 class LikeResponse(BaseResponse): def __init__(self): self.code = 0 super(LikeResponse,self).__init__() # obj = LikeResponse() # print(obj.__dict__)
templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>分类列表</h1> <ul> {% if current_new_type_id %} <li><a href="/">全部</a></li> {% else %} <li><a style="color:green" href="/">全部</a></li> {% endif %} {% for row in new_type_list %} {% if row.id == current_new_type_id %} <li><a style="color:green" href="/all/{{ row.id }}/">{{ row.caption }}</a></li> {% else %} <li><a href="/all/{{ row.id }}/">{{ row.caption }}</a></li> {% endif %} {% endfor %} </ul> <h2>新闻列表</h2> <div> {% for row in new_list %} <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} <div style="display: inline-block;position: relative;"> 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a> </div> </div> </div> <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div> </div> <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div> </div> {% endfor %} </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function () { bindLikeEvent(); }); function bindLikeEvent() { $('.new-like').click(function () { // 获取当前新闻ID var newId = $(this).attr('new-id'); var $this = $(this); //ajax里面不能用$(this),###可以提前赋值###,当前函数没有,没有会找上面的 $.ajax({ url: '/do_like.html', type: "POST", data: {'newId': newId}, dataType: 'JSON', success:function (arg) { if(arg.status){ var origin = $this.text(); var count = parseInt(origin); if(arg.code == 666){ $this.text(count - 1 ); // 这个$this就是提前赋值的 showLikeCount($this,'-1'); }else if(arg.code == 999){ $this.text(count + 1 ); showLikeCount($this,'+1'); } }else{ alert(arg.msg); } } }) }) } function showLikeCount($this,text) { var fontSize = 5; var top = 0; var right = 0; var opacity = 1; // 定义字体的透明度 var tag = document.createElement('span'); // var tag = document.getElementById() tag.innerText = text; tag.style.position = "absolute"; //父级div用relative,这里用absolute // 默认大小 tag.style.fontSize = fontSize + "px"; tag.style.top = top + 'px'; tag.style.right = right + 'px'; tag.style.opacity = opacity; // 已上都是设置tag标签的属性 $this.after(tag); // 设置点赞数字的动画效果 // 定时器,没0.5s执行一次 var obj = setInterval(function () { fontSize += 5; top -= 5; right -= 5; opacity -= 0.1; tag.style.fontSize = fontSize + "px"; // tag是引用上面的,不用重新再取了 tag.style.top = top + 'px'; tag.style.right = right + 'px'; tag.style.opacity = opacity; if(opacity <= 0){ clearInterval(obj); //一个函数里面没有这个obj变量,在往上一层找,闭包>>>>>> tag.remove(); } },100); } </script> </body> </html>
实现效果
2. 点赞 - 事务 from django.db import transaction with transaction.atomic(): models.Like.objects.create(nnew_id=new_id,uuser_id=uid) models.News.objects.filter(id=new_id).update(like_count=F('like_count') + 1) response.code = 999 - 返回值:封装对象 class BaseResponse(object): def __init__(self): self.status = False self.data = None self.msg = None def get_dict(self): return self.__dict__ class LikeResponse(BaseResponse): def __init__(self): self.code = 0 super(LikeResponse,self).__init__() json.dumps(对象.__dict__) json.dumps(对象.get_dict()) - 在Ajax操作时候,回调函数中的 $(this)已经不是原来的$(this) - css: position:fixed,absolute,relative setInterval:定时器
评论方法实现(扩展:动态菜单的实现)
评论
1. 评论展示
urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^comment_list.html$', views.comment_list), # ajax 这块必须加$,不加的话不会执行do_like函数 ]
views.py
def build_comment_data(li): dic = {} for item in li: item['children'] = [] dic[item['id']] = item result = [] for item in li: pid = item['parent_id'] if pid: dic[pid]['children'].append(item) else: result.append(item) return result def build_comment_tree(com_list): tpl = """ <div class="item"> <div class="title">{0}:{1}</div> <div class="body">{2}</div> </div> """ html = '' for item in com_list: if not item['children']: html += tpl.format(item['user'], item['content'], '') else: html += tpl.format(item['user'], item['content'], build_comment_tree(item['children'])) return html def comment_list(request): li = [ {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None}, {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None}, {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1}, {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2}, {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3}, {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5}, {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4}, {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4}, ] com_list = build_comment_data(li) html = build_comment_tree(com_list) return render(request, 'comment_list.html', {'comment_html': html})
templates/comment_list.html
<!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</title> <style> .hide{ display: none; } .item .body{ margin-left: 30px; } </style> </head> <body> <h1>评论</h1> {{ comment_html|safe }} </body> </html>
效果
2. 评论和click事件结合
urls.py
from django.conf.urls import url from django.contrib import admin from app01.views import index from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^all/(?P<new_type_id>\d+)/', index), url(r'^fetch_comment.html$', views.fetch_comment), url(r'^', index), ]
views.py
from django.shortcuts import render,HttpResponse
from app01 import models
import os
import json
from django.db.models import F
from django.db import transaction
from utils.response import LikeResponse
def index(request,*args,**kwargs):
"""
首页:
- 显示所有的新闻类型
- 显示所有新闻列表
:param request:
:param args:
:param kwargs:
:return:
"""
# None, "1" "2"
current_new_type_id = kwargs.get('new_type_id')
if current_new_type_id:
current_new_type_id = int(current_new_type_id)
new_type_list = models.NewsType.objects.all()
new_list = models.News.objects.filter(**kwargs)
return render(request,'index.html',{'new_type_list':new_type_list,'new_list':new_list,'current_new_type_id':current_new_type_id})
def build_comment_data(li): dic = {} for item in li: item['children'] = [] dic[item['id']] = item result = [] for item in li: pid = item['parent_id'] if pid: dic[pid]['children'].append(item) else: result.append(item) return result def build_comment_tree(com_list): """ {'user': '银秋良1', 'children': [],content:'xxx'], {'user': '银秋良2', 'children': [{'user': '型谱', 'children': [{'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}], 'parent_id': 1, 'content': '你个文盲', 'id': 3}], 'parent_id': None, 'content': '灌我鸟事', 'id': 1} {'user': '银秋良3', 'children': [{'user': '详解', 'children': [], 'parent_id': 2, 'content': '好羡慕你们这些没脸的人呀', 'id': 4}], 'parent_id': None, 'content': '管我鸟事', 'id': 2} [ {'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5} ] """ tpl = """ <div class="item"> <div class="title">{0}:{1}</div> <div class="body">{2}</div> </div> """ html = "" for item in com_list: if not item['children']: html +=tpl.format(item['user'],item['content'],"") else: html +=tpl.format(item['user'], item['content'], build_comment_tree(item['children'])) return html def fetch_comment(request): li = [ {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None}, {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None}, {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1}, {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2}, {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3}, {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5}, {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4}, {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4}, ] com_list = build_comment_data(li) # 第一种选择 html = build_comment_tree(com_list) return HttpResponse(html) # 第二种选择,前端递归去生成 # return HttpResponse(json.dumps(com_list))
templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .body a{ display: block; } .body{ margin-left: 30px; } </style> </head> <body> <h1>分类列表</h1> <ul> {% if current_new_type_id %} <li><a href="/">全部</a></li> {% else %} <li><a style="color:green" href="/">全部</a></li> {% endif %} {% for row in new_type_list %} {% if row.id == current_new_type_id %} <li><a style="color:green" href="/all/{{ row.id }}/">{{ row.caption }}</a></li> {% else %} <li><a href="/all/{{ row.id }}/">{{ row.caption }}</a></li> {% endif %} {% endfor %} </ul> <h2>新闻列表</h2> <div> {% for row in new_list %} <div> <div>{{ row.title }}</div> <div> {{ row.user.username }} <div class="new-comment" style="display: inline-block;" new-id="{{ row.id }}"> 评论:{{ row.comment_count }} </div> <div style="display: inline-block;position: relative;"> 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a> </div> </div> <div class="comment-area"></div> </div> <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div> </div> <div> <div>{{ row.title }}</div> <div>{{ row.user.username }} 评论:{{ row.comment_count }} 赞:<a class="new-like" new-id="{{ row.id }}">{{ row.like_count }}</a></div> </div> {% endfor %} </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function () { bindLikeEvent(); bindCommentEvent(); // 点击评论数字,则显示评论 }); function bindCommentEvent() { $('.new-comment').click(function () { var newId = $(this).attr('new-id'); var $this = $(this); $.ajax({ url: '/fetch_comment.html', type: "GET", data: {'newId': newId}, success:function (arg) { console.log(arg); $this.parent().next().html(arg); } }) }) } function bindLikeEvent() { $('.new-like').click(function () { // 获取当前新闻ID var newId = $(this).attr('new-id'); var $this = $(this); $.ajax({ url: '/do_like.html', type: "POST", data: {'newId': newId}, dataType: 'JSON', success:function (arg) { if(arg.status){ var origin = $this.text(); var count = parseInt(origin); if(arg.code == 666){ $this.text(count - 1 ); showLikeCount($this,'-1'); }else if(arg.code == 999){ $this.text(count + 1 ); showLikeCount($this,'+1'); } }else{ alert(arg.msg); } } }) }) } function showLikeCount($this,text) { var fontSize = 5; var top = 0; var right = 0; var opacity = 1; var tag = document.createElement('span'); // var tag = document.getElementById() tag.innerText = text; tag.style.position = "absolute"; // 默认大小 tag.style.fontSize = fontSize + "px"; tag.style.top = top + 'px'; tag.style.right = right + 'px'; tag.style.opacity = opacity; $this.after(tag); // 定时器,没0.5s执行一次 var obj = setInterval(function () { fontSize += 5; top -= 5; right -= 5; opacity -= 0.1; tag.style.fontSize = fontSize + "px"; tag.style.top = top + 'px'; tag.style.right = right + 'px'; tag.style.opacity = opacity; if(opacity <= 0){ clearInterval(obj); tag.remove(); } },100); } </script> </body> </html>
效果(点击数字,会展开评论)
3. 评论 - 字典,列表,通过引用赋值,一个修改全部都改 - 递归 作业: 评论:0 绑定点击事件 - 发送Ajax请求: 新闻ID - 查询当前新闻ID对应的所有评论 models.xxxx.values() li = [ {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None}, {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None}, {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1}, {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2}, {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3}, {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5}, {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4}, {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4}, ] 选择: - 数据进行操作并且渲染,返回给用户HTML - 返回给前端JavaScrip ***** 应用:菜单
菜单
1. 普通格式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .comment{ margin-left: 20px; }</style> </head> <body><h1>菜单</h1> <hr/> <div> <h3>普通格式</h3> <div class="comment"> <div class="content">第一个评论</div> <div class="content">第二个评论</div> <div class="comment"> <div class="content">对第二条评论恢复</div> <div class="comment"> <div class="content">对第二条评论恢复</div> </div> <div class="content">对第二条评论恢复</div> </div> <div class="content">第三个评论</div> </div> </div> </body> </html>
效果
2.带标题格式(菜单)
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^comment_list.html$', views.comment_list), ]
views.py
from django.shortcuts import render,HttpResponse from app01 import models import os import json from django.db.models import F from django.db import transaction from utils.response import LikeResponse def build_comment_data(li): dic = {} for item in li: item['children'] = [] dic[item['id']] = item result = [] for item in li: pid = item['parent_id'] if pid: dic[pid]['children'].append(item) else: result.append(item) return result def build_comment_tree(com_list): """ {'user': '银秋良1', 'children': [],content:'xxx'], {'user': '银秋良2', 'children': [{'user': '型谱', 'children': [{'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}], 'parent_id': 1, 'content': '你个文盲', 'id': 3}], 'parent_id': None, 'content': '灌我鸟事', 'id': 1} {'user': '银秋良3', 'children': [{'user': '详解', 'children': [], 'parent_id': 2, 'content': '好羡慕你们这些没脸的人呀', 'id': 4}], 'parent_id': None, 'content': '管我鸟事', 'id': 2} [ {'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5} ] """ tpl = """ <div class="item"> <div class="title">{0}:{1}</div> <div class="body">{2}</div> </div> """ html = "" for item in com_list: if not item['children']: html +=tpl.format(item['user'],item['content'],"") else: html +=tpl.format(item['user'], item['content'], build_comment_tree(item['children'])) return html def comment_list(request): """ 获取多级评论列表 :param request: :return: """ li = [ {'id': 1, 'user': '银秋良', 'content': '灌我鸟事', 'parent_id': None}, {'id': 2, 'user': '银秋良', 'content': '管我鸟事', 'parent_id': None}, {'id': 3, 'user': '型谱', 'content': '你个文盲', 'parent_id': 1}, {'id': 4, 'user': '详解', 'content': '好羡慕你们这些没脸的人呀', 'parent_id': 2}, {'id': 5, 'user': '银秋良', 'content': '你是流氓', 'parent_id': 3}, {'id': 6, 'user': '银秋良', 'content': '你冷库无情', 'parent_id': 5}, {'id': 7, 'user': '银秋良', 'content': '你才冷酷无情', 'parent_id': 4}, {'id': 8, 'user': '银秋良', 'content': '你无理取闹', 'parent_id': 4}, ] com_list = build_comment_data(li) """ {'user': '银秋良', 'children': [{'user': '型谱', 'children': [{'user': '银秋良', 'children': [], 'parent_id': 3, 'content': '你是流氓', 'id': 5}], 'parent_id': 1, 'content': '你个文盲', 'id': 3}], 'parent_id': None, 'content': '灌我鸟事', 'id': 1} {'user': '银秋良', 'children': [{'user': '详解', 'children': [], 'parent_id': 2, 'content': '好羡慕你们这些没脸的人呀', 'id': 4}], 'parent_id': None, 'content': '管我鸟事', 'id': 2} """ html = build_comment_tree(com_list) return render(request,'comment_list.html',{'comment_html':html})
tempaltes/comment_list.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .comment{ margin-left: 20px; } .body a{ display: block; } .body{ margin-left: 30px; } .hide{ display: none; } </style> </head> <body> <h1>菜单</h1> <hr/> <div> <h3>普通格式</h3> <div class="comment"> <div class="content">第一个评论</div> <div class="content">第二个评论</div> <div class="comment"> <div class="content">对第二条评论恢复</div> <div class="comment"> <div class="content">对第二条评论恢复</div> </div> <div class="content">对第二条评论恢复</div> </div> <div class="content">第三个评论</div> </div> <h3>带标题格式(菜单)</h3> <div class="item"> <div class="title">用户管理</div> <div class="body"> <div class="item"> <div class="title">权限管理</div> <div class="body"> <a href="#">用户授权</a> <a href="#">权限变更</a> </div> </div> <div class="item"> <div class="title">权限管理</div> <div class="body"> <a href="#">用户授权</a> <a href="#">权限变更</a> </div> </div> </div> </div> <div class="item"> <div class="title">订单管理</div> <div class="body"> <a href="#">创建叮当</a> <a href="#">取消订单</a> </div> </div> </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function () { $('.title').click(function () { if ($(this).next().hasClass('hide')){ $(this).next().removeClass('hide') }else{ $(this).next().addClass('hide') } }) }) </script> </body> </html>
效果
3. NBMENU(通过templatetags实现)
目录结构如下:
settings.py【将目录信息放在了配置文件中,缺点增删目录结构的时候需要重启应用,最好的方式是放在数据库中】
.....
.....
STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), ) MENU_LIST = [ {'id': 1, 'caption': '用户管理', 'url': None, 'parent_id': None}, {'id': 2, 'caption': '订单管理', 'url': None, 'parent_id': None}, {'id': 3, 'caption': '用户列表', 'url': '/users.html', 'parent_id': 1}, {'id': 4, 'caption': '添加用户', 'url': '/add_user.html', 'parent_id': 1}, {'id': 5, 'caption': '订单列表', 'url': '/orders.html', 'parent_id': 2}, {'id': 6, 'caption': '取消订单', 'url': '/xxxxx.html', 'parent_id': 2}, {'id': 7, 'caption': '退换管理', 'url': None, 'parent_id': 2}, {'id': 8, 'caption': '退货', 'url': '/aaa.html', 'parent_id': 7}, {'id': 9, 'caption': '换货', 'url': '/bbb.html', 'parent_id': 7}, ]
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index.html', views.index), ]
views.py【优点:将菜单展示逻辑和view的逻辑分开】
from django.shortcuts import render def index(request): return render(request,'index.html')
templates/index.html
{% load menu %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .body a{ display: block; } .body{ margin-left: 30px; <!--margin-left 是相对于父标签的 --> } .hide{ display: none; } </style> </head> <body> <div> <h2>原来方式已经注释</h2> <!-- {{ menu_html|safe }} --> <h2>sample_tag的方式</h2> {% menu_html %} </div> <script src="/static/jquery-3.2.1.js"></script> <script> $(function () { $('.title').click(function () { if ($(this).next().hasClass('hide')){ $(this).next().removeClass('hide') }else{ $(this).next().addClass('hide') } }) }) </script> </body> </html>
app01/templatetags/menu.py
from django.template import Library from django.utils.safestring import mark_safe from django.conf import settings # 备注: # 1,放在配置文件中( from django.conf import serrints 如果settingsh中有定义配置项,通过这种方式导入更全),修改得重启 ,放在数据库中是动态的, # 2,settings中的内容必须大写,不大写读取不到!!!! register = Library() def build_menu_data(li): dic = {} """ 创建一个字典,保存原来的列表内容,没有开辟新的内存空间,而是引用(指向的都是内存中的同一块数据) """ for item in li: item['children'] = [] dic[item['id']] = item result = [] for item in li: pid = item['parent_id'] if pid: dic[pid]['children'].append(item) else: result.append(item) return result def build_menu_tree(mn_list): tpl1 = """ <div class="item"> <div class="title">{0}</div> <div class="body">{1}</div> </div> """ tpl2 = "<a href={0}>{1}</a>" html = "" for item in mn_list: # 判断:如果有url就生成a标签,没有url就不生成div(item title body) if item['url']: html += tpl2.format(item['url'],item['caption']) else: if not item['children']: html +=tpl1.format(item['caption'],"") else: html +=tpl1.format(item['caption'], build_menu_tree(item['children'])) return html @register.simple_tag def menu_html(): result = build_menu_data(settings.MENU_LIST) html = build_menu_tree(result) return mark_safe(html) # 前端不用再使用 safe
备注:
如果要将目录信息放到数据库中,models.py如下
from django.db import models class Menu(models.Model): caption = models.CharField(max_length=32) # is_menu = models.BooleanField() url = models.CharField(max_length=128,null=True,blank=True) parent = models.ForeignKey('Menu',related_name='xxxx',null=True,blank=True) # 自关联的时候要加realted_name
作业参考
https://github.com/zengchunyun/news
https://github.com/uge3/BBS