jsonp、瀑布流布局、组合搜索、多级评论(评论树)、Tornado初识
1.JSONP原理剖析以及实现
1.1 同源策略限制
用django分别建立两个项目,jsonp01和jsonp02,然后再在这两个项目里分别建立一个app,比如名字叫jsonp1、jsonp2;jsonp01的端口号是8005,jsonp02的端口号是8006。
jsonp1的代码如下,
setting做常规配置;
urls.py,
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^testjsonp/', views.testjsonp), url(r'^index/', views.index), ]
views.py,
def testjsonp(request): return HttpResponse('OK') def index(request): return render(request,'index.html')
index.html,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" onclick="ajaxjsonp1();" value="jsonp1" /> <script src="/static/js/jquery-1.12.4.js"></script> <script> function ajaxjsonp1() { $.ajax({ url : '/testjsonp/', type : 'POST', data: {'k1':'v1'}, success : function (data) { alert(data); } }); } </script> </body> </html>
jsonp2的代码如下,
settings.py做常规配置;
urls.py,
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^testjsonp2/', views.testjsonp2), ]
views.py,
def testjsonp2(request): return HttpResponse('jsonp2ok')
如上面的代码,因为jsonp1请求的是自己的项目域名(url : '/testjsonp/'),所以会如期收到返回的数据并alert(ok);
当把url : '/testjsonp/'改成url : 'http://10.103.9.83:8006/testjsonp2/',即用ajax实现跨域请求(好比在www.taobao.com上通过ajax访问www.jd.com的数据),则会报错如下图,
这是因为自身浏览器的同源策略限制,比如在www.taobao.com上通过ajax访问www.jd.com的数据,该请求能从自己的浏览器发送到jd.com服务端,服务端也能处理并返回数据,但是当自己的浏览器发现收到的数据是非本机域名发来的,就会阻拦该数据,过程如下图,
,通过ajax,如果在当前域名去访问其他域名时,浏览器会出现同源策略限制,从而阻止请求的返回,所以无法用ajax实现跨域请求。
1.2 解决同源策略
img、script、iframe、link这些标签是不受同源策略限制的;src属性一般不鸟同源策略。利用这个特点,就可以实现jsonp的跨域请求。
改进jsonp1的index.html的代码,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" onclick="ajaxjsonp1();" value="jsonp1" /> <input type="button" onclick="ajaxjsonp2();" value="jsonp2" /> <script src="/static/js/jquery-1.12.4.js"></script> <script> function ajaxjsonp1() { $.ajax({ url : 'http://10.103.9.83:8005 /testjsonp/', type : 'POST', data: {'k1':'v1'}, success : function (data) { alert(data); } }); } function ajaxjsonp2() { #创建一个script标签,src值设置为要请求的域名,将这个标签加到head标签下,请求完之后remove掉这个标签。 var tag = document.createElement("script"); tag.src = "http://10.103.9.83:8006/testjsonp2/"; document.head.appendChild(tag); document.head.removeChild(tag); } </script> </body> </html>
然后点击index.html的“jsonp2”按钮,就会收到如下报错:
,这说明本地浏览器已经收到服务端返回的数据(jsonp2ok)了,但是这个返回的数据是交给javascripts处理的,因为script里没有“jsonp2ok”这个方法,所以会报错“jsonp2ok没有定义”,所以需要在服务端和客户端再做一些修改,代码见下,
jsonp2的views.py,
import json def testjsonp2(request): ll = ['jack','luce','goi'] #直接返回一个jsonpfunc(ll),然后客户端的script标签里需要定义一个jsonpfunc方法,然后就能处理数据了。 temp = 'jsonpfunc(%s)' % (json.dumps(ll)) return HttpResponse(temp)
jsonp1的index.html,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" onclick="ajaxjsonp1();" value="jsonp1" /> <input type="button" onclick="ajaxjsonp2();" value="jsonp2" /> <script src="/static/js/jquery-1.12.4.js"></script> <script> function ajaxjsonp1() { $.ajax({ url : 'http://10.103.9.83:8005 /testjsonp/', type : 'POST', data: {'k1':'v1'}, success : function (data) { alert(data); } }); } function ajaxjsonp2() { var tag = document.createElement("script"); tag.src = "http://10.103.9.83:8006/testjsonp2/"; document.head.appendChild(tag); document.head.removeChild(tag); } #收到服务端返回的数据,然后执行下面的jsonpfunc方法。 function jsonpfunc(data) { console.log(data); } </script> </body> </html>
现在再点击jsonp1的index.html的"jsonp2"按钮,返回数据见下:
,客户端收到这些数据后,就可以处理了。
1.3 利用jquery实现伪ajax的跨域请求
上面讲的是jsonp的原理,原理就是利用那些不受同源策略限制的标签来发送请求,下面要说的是利用jquery实现伪ajax的跨域请求。
function weiajax(){ $.ajax({ url : 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403', type : 'GET', dataType : 'jsonp', jsonp : 'callback', jsonpCallback : 'list' }); } function list(arg){ console.log(arg); } #这样就能获取到江西卫视的节目单了。 #注意,上面的function list(arg){}和 jsonpCallback : 'list',之所以叫list,是因为江西卫视的服务器返回的数据中包含的javascript方法名叫list,所以我们也必须起名叫list,但是江西卫视是不规范的,规范的服务端应该是不限制客户端起什么方法名的;比如我本地有一个list方法,向江西卫视请求数据也需要定义一个list方法,那我岂不是要为了请求数据而将已有的list方法改名?显然是不合理的。但是江西卫视这么办了,我们就必须起这个名字。后面会讲到规范的方法。
如果是请求本地域名,就直接用ajax即可;如果是请求跨域数据,则依然用ajax,但是需要dataType : 'jsonp'、jsonp : 'callback'、jsonpCallback : 'funcdemo',然后再定义一个function funcdemo(arg){}方法。
1.4 讲解jsonp : 'callback'、jsonpCallback : 'funcdemo'
如果服务端规范的话,则客户端处理返回数据的方法名是任意起的,比如客户端可以这样请求数据,
function weiajax(){ $.ajax({ url : 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403', type : 'GET', dataType : 'jsonp', jsonp : 'callbackaaaaa', jsonpCallback : 'listqqqqq' }); } function listqqqqq(arg){ console.log(arg); } #如果服务端规范的话,则客户端任意起方法名都不会影响接收数据,原因下面会讲到。
jsonp2的views.py的代码修改如下,
import json def testjsonp2(request): func = request.GET.get('callbackaaaaa') ll = ['jack','luce','goi'] #之前方法名是固定的,现在把方法名设置为变量了,方法名就是取的客户端发来的值,所以客户端发什么值,服务端就将这个值作为方法名返回给客户端。 temp = '%s(%s)' % (func,json.dumps(ll)) return HttpResponse(temp)
虽然“jsonp : 'callbackaaaaa',”可以随便定义,但是为了客户端和服务端的统一,我们约定设置为“jsonp : 'callback'”,不然比如客户端是callbackaaa,但是服务端是“request.GET.get('callbackbbb')”,那肯定不能返回数据给客户端;
只要服务端如jsonp2的views所示将返回的方法名设置为变量,则客户端进行ajax跨域请求时,就可以随便定义方法名,listqqqqq也行、ll44也行、funcdemo也行等等。
1.5 jsonp不支持POST请求
jsonp的原理是利用那几个不受同源策略限制的标签的src属性来发送请求,比如<script src="http://www.baidu.com">,单单是写一个域名,所以肯定是GET请求,即使在$.ajax里指定了type:'POST'也不会起作用,本质上还是GET请求。
2. 瀑布流
2.1 瀑布流布局介绍
网站页面上展示很多图片,图片的高度不同但是宽度相同,这时候就需要用瀑布流排版格式来展示图片;原理就是将主div分割为几个竖着的大div,然后在几个竖着的大div里放图片,这样图片不管高低就是依次展示了。
img.html,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <style> #定义主div的宽度为980px。 .container{ width: 980px; margin: 0 auto; } #每个列div的宽度为总宽度的四分之一 .container .column{ float: left; width: 245px; } .container .item img{ width: 245px; } </style> </head> <body> <div class="container"> <div class="column"> <div class="item"> <img src="/static/1.jpg"> </div> <div class="item"> <img src="/static/2.jpg"> </div> <div class="item"> <img src="/static/3.jpg"> </div> </div> <div class="column"> <div class="item"> <img src="/static/1.jpg"> </div> <div class="item"> <img src="/static/2.jpg"> </div> <div class="item"> <img src="/static/3.jpg"> </div> </div> <div class="column"> <div class="item"> <img src="/static/1.jpg"> </div> <div class="item"> <img src="/static/2.jpg"> </div> <div class="item"> <img src="/static/3.jpg"> </div> </div> <div class="column"> <div class="item"> <img src="/static/1.jpg"> </div> <div class="item"> <img src="/static/2.jpg"> </div> <div class="item"> <img src="/static/3.jpg"> </div> </div> </div> </body> </html>
不用瀑布流,直接各个小div依次堆叠的效果如下,遇到长图后就会出现空白区域,
分隔成四个大的div,
用瀑布流布局后,效果展示,
图片竖着往下排列,没有间隙了,看起来就舒服多了。
2.2 循环读取图片信息并展示
后端将图片信息返回给前端,
前端收到数据后,循环读取图片信息并展示图片,我们把主div分为了4个竖div,所以此时选择用“余数”法来展示图片,即第一个竖div只放“列表下标+1除以4余数是1”的图片,第二个竖div只放“列表下标+1除以4余数是2”的图片,第三个竖div只放“列表下标+1除以4余数是3”的图片,第四个竖div只放“列表下标+1除以4余数是0”的图片,思路是如下代码,
{% for i in img_list %} #思路是对的,但是模板语言不支持用“%”做余数运算,所以要自定义一个判断余数的函数 {% if forloop.counter%4==1 %} <div> <img src="/static/{{ i.src }}" /> </div> {% endif %} {% endfor %}
filter和simple_tag的区别:
filter:
限制参数个数;
支持作为模板语言if判断的条件,也就是可以用{% if k1|filterfunc %}这种形式,如果funcone返回true,就为真,返回false就为假。
simple_tag:
不限制参数个数;
不支持作为模板语言if判断的条件,也就是不能用{% if simple_tag_func arg1 %}这种形式,不论simple_tag_func返回true或false都没作用。
因为我们要用if判断,所以要用filter建立模板函数。
新建一个模板函数,
app下的templatetags目录下的judge.py,
from django import template from django.utils.safestring import mark_safe from django.template.base import resolve_variable, Node, TemplateSyntaxError register = template.Library() @register.filter def detail1(value,arg): """ 查看余数是否等于remainder arg="1,2" :param counter: :param allcount: :param remainder: :return: """ allcount, remainder = arg.split(',') allcount = int(allcount) remainder = int(remainder) if value%allcount == remainder: return True return False
修改html代码,
{% load judge %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <style> .container{ width: 980px; margin: 0 auto; } .container .column{ float: left; width: 245px; } .container .item img{ width: 245px; } </style> </head> <body> <div class="container"> <div class="column"> {% for i in img_list %} #只展示余数是1的,下面依次展示余数是2、3、0的。 {% if forloop.counter|detail1:"4,1" %} <div class="item"> {{ forloop.counter }} <img src="/static/{{ i.src }}"> </div> {% endif %} {% endfor %} </div> <div class="column"> {% for i in img_list %} {% if forloop.counter|detail1:"4,2" %} <div class="item"> {{ forloop.counter }} <img src="/static/{{ i.src }}"> </div> {% endif %} {% endfor %} </div> <div class="column"> {% for i in img_list %} {% if forloop.counter|detail1:"4,3" %} <div class="item"> {{ forloop.counter }} <img src="/static/{{ i.src }}"> </div> {% endif %} {% endfor %} </div> <div class="column"> {% for i in img_list %} {% if forloop.counter|detail1:"4,0" %} <div class="item"> {{ forloop.counter }} <img src="/static/{{ i.src }}"> </div> {% endif %} {% endfor %} </div> </div> </body> </html>
最后在settings里注册当前app名称。
2.3 循环读取图片信息的改进
上面的方法虽然可行,但是每个竖div都会循环所有的图片找到满足自己要求的图片,4个竖div就要完整的循环4次所有图片,耗时。
所以最好是当页面加载完成(除图片外)时触发一个ajax请求,获取到img_list的图片信息,然后在success:function(data){}里做一个循环,从1开始依次循环,然后取除4的余数,如果是1就放到第一个竖div($('.container').eq(1).append(<img src="/static/{{ i.src }}">)),如果是2就放到第二个竖div($('.container').eq(2).append(<img src="/static/{{ i.src }}">)),以此类推,这样仅循环一次就可以完整的展示所有图片。
3.组合搜索
3.1 建立数据库,并写入数据
新建一个project和app,此处app起名叫combinesearchapp,
models.py,
from django.db import models # 技术方向, class Direction(models.Model): name = models.CharField(verbose_name='名称', max_length=32) classification = models.ManyToManyField('Classification') class Meta: db_table = 'Direction' verbose_name_plural = u'方向(视频方向)' def __str__(self): return self.name # 技术分类、语言 class Classification(models.Model): name = models.CharField(verbose_name='名称', max_length=32) class Meta: db_table = 'Classification' verbose_name_plural = u'分类(视频分类)' def __str__(self): return self.name # 技术视频, class Video(models.Model): level_choice = ( (1, u'初级'), (2, u'中级'), (3, u'高级'), ) level = models.IntegerField(verbose_name='级别', choices=level_choice, default=1) classification = models.ForeignKey('Classification', null=True, blank=True) title = models.CharField(verbose_name='标题', max_length=32) summary = models.CharField(verbose_name='简介', max_length=32) img = models.ImageField(verbose_name='图片', upload_to='./static/images/Video/') href = models.CharField(verbose_name='视频地址', max_length=256) create_date = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'Video' verbose_name_plural = u'视频' def __str__(self): return self.title
settings.py,
#注册app INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'combinesearchapp', ] #配置静态文件路径 STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), )
urls.py,
#利用django提供的admin功能来给数据库添加内容,所以要用admin这个路由 url(r'^admin/', admin.site.urls),
admin.py,
from django.contrib import admin # Register your models here. from combinesearchapp import models #将三个表注册到admin admin.site.register(models.Direction) admin.site.register(models.Classification) admin.site.register(models.Video)
在命令行同步数据表、建立管理用户,
python3 manage.py makemigrations
python3 manage.py migrate
python3 manage.py createsuperuser
登录admin后台,http://10.103.9.83:8007/admin,
先添加分类,比如是,
在添加方向,比如是,
,
最后添加视频(这里起名叫视频没什么特别含义,就是顺手起的而已),比如是,
,
在admin后台添加完数据后,此时数据库就有数据了,方向分别是:全栈 测试 开发 运维,分类分别是:python linux javascript openstack,视频(也可以起名叫等级)分别是:初级 中级 高级。
其实也可以通过代码来添加数据,只是用admin来添加更简单、直观。
3.2 组合搜索的所有代码
settings.py,
#允许自定义服务器IP ALLOWED_HOSTS = ['*'] #注册APP INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'combinesearchapp', ] #配置静态文件路径 STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), )
urls.py,
from combinesearchapp import views urlpatterns = [ url(r'^admin/', admin.site.urls), #后端代码会根据传的方向id、分类id、等级id来判断,将数据库里对应的数据反馈给前端。 url(r'^video-(?P<direction_id>\d+)-(?P<classfication_id>\d+)-(?P<level_id>\d+)/', views.video), ]
models.py,
from django.db import models # 技术方向, class Direction(models.Model): name = models.CharField(verbose_name='名称', max_length=32) classification = models.ManyToManyField('Classification') class Meta: db_table = 'Direction' verbose_name_plural = u'方向(视频方向)' def __str__(self): return self.name # 技术分类、语言 class Classification(models.Model): name = models.CharField(verbose_name='名称', max_length=32) class Meta: db_table = 'Classification' verbose_name_plural = u'分类(视频分类)' def __str__(self): return self.name # 技术视频, class Video(models.Model): level_choice = ( (1, u'初级'), (2, u'中级'), (3, u'高级'), ) level = models.IntegerField(verbose_name='级别', choices=level_choice, default=1) classification = models.ForeignKey('Classification', null=True, blank=True) title = models.CharField(verbose_name='标题', max_length=32) summary = models.CharField(verbose_name='简介', max_length=32) img = models.ImageField(verbose_name='图片', upload_to='./static/images/Video/') href = models.CharField(verbose_name='视频地址', max_length=256) create_date = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'Video' verbose_name_plural = u'视频' def __str__(self): return self.title
views.py,
from django.shortcuts import render # Create your views here. from combinesearchapp import models #**kwargs,获取到url的值。 def video(request,**kwargs): #方向id,默认是0 direction_id = kwargs.get('direction_id', '0') #分类id,默认是0 classfication_id = kwargs.get('classfication_id', '0') #当前url值 current_url = request.path_info #如果方向id是0,也就是用户没有选择方向,则默认就将所有的分类(python、linux、openstatck、javascripts)显示 if direction_id == '0': CList = models.Classification.objects.values('id', 'name') else: #方向id不是0,则获取到用户选择的方向(比如是测试)的id对应的表对象 obj = models.Direction.objects.get(id=direction_id) #获取这个方向下的所有分类的id和name,因为测试这个方向下只有python一个分类,所以此处的temp是(1,‘python’) temp = obj.classification.all().values('id', 'name') #获取该方向下的所有分类的id值 id_list = list(map(lambda x: x['id'], temp)) #因为用户选择了方向,所以将这个方向下对应的分类显示,假如用户选择的“测试”,则此时页面的分类那一行只显示“python”,而不显示linux、openstack、javascript CList = temp if classfication_id != '0': #如果用输入的分类id,在该方向下没有对应的分类id时,就将url中的分类id的值设置为0; #比如现在方向选的开发,分类选的javascript,然后再选方向测试(测试只有一个python分类),因为测试里没有javascirpt这个分类,所以就将url中的classfication_id的值置为0。 if int(classfication_id) not in id_list: url_list = current_url.split('-') url_list[2] = "0" current_url = '-'.join(url_list) DList = models.Direction.objects.values('id','name') VList = models.Video.level_choice return render(request,'video.html',{'DList':DList,'CList':CList,'VList':VList,'current_url':current_url})
因为models.py里有一行“img = models.ImageField(verbose_name='图片', upload_to='./static/images/Video/')”,所以要建立static/images/Video目录,以供上传图片的存储用。
video.html,
#导入模板方法 {% load fenlei %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .combinediv a{ padding: 3px; } .combinediv .active{ background-color: aqua; } </style> </head> <body> <div class="combinediv"> <div> {% all current_url 1 %} {% for i in DList %} {% actionone current_url i.id i.name %} {% endfor %} </div> <div> {% all current_url 2 %} {% for i in CList %} {% actiontwo current_url i.id i.name %} {% endfor %} </div> <div> {% all current_url 3 %} {% for i in VList %} {% actionthree current_url i.0 i.1 %} {% endfor %} </div> </div> </body> </html>
combinesearchapp/templatetag/fenlei.py,
from django import template from django.utils.safestring import mark_safe register = template.Library() @register.simple_tag() #获取当前url,当前方向的id,当前方向的name def actionone(current_url,nid,name): sp_url = current_url.split('-') old = sp_url[1] #如果当前方向id等于url里的方向id,则表示是选中的,就添加“active”样式。 if old == str(nid): temp = '<a class="active" href="%s">%s</a>' else: temp = '<a href="%s">%s</a>' #将方向id的值设置为新的id值 sp_url[1] = str(nid) tp = '-'.join(sp_url) tag = temp % (tp,name) return mark_safe(tag) @register.simple_tag() #获取当前url,当前分类的id,当前分类的name def actiontwo(current_url,nid,name): sp_url = current_url.split('-') old = sp_url[2] #如果当前分类id等于url里的分类id,则表示是选中的,就添加“active”样式。 if old == str(nid): temp = '<a class="active" href="%s">%s</a>' else: temp = '<a href="%s">%s</a>' #将分类id的值设置为新的id值 sp_url[2] = str(nid) tp = '-'.join(sp_url) tag = temp % (tp,name) print('old,nid:',old,nid) return mark_safe(tag) @register.simple_tag() #获取当前url,当前等级的id,当前等级的name def actionthree(current_url,nid,name): sp_url = current_url.split('-') old = sp_url[3].strip('/') #如果当前等级id等于url里的等级id,则表示是选中的,就添加“active”样式。 if old == str(nid): temp = '<a class="active" href="%s">%s</a>' else: temp = '<a href="%s">%s</a>' #将等级id的值设置为新的id值 sp_url[3] = str(nid) tp = '-'.join(sp_url) tag = temp % (tp,name) return mark_safe(tag) @register.simple_tag() #id为1表示是方向那一行的“全部”;id为2表示是分类那一行的“全部”,id为3表示是等级那一行的“全部” def all(current_url,id): sp_url = current_url.split('-') if int(id) == 3: sp_url[3] = sp_url[3].strip('/') if sp_url[id] == '0': temp = '<a class="active" href="%s">全部</a>' else: temp = '<a href="%s">全部</a>' sp_url[id] = '0' tp = '-'.join(sp_url) tag = temp % (tp) return mark_safe(tag)
组合搜索完整代码的github地址:https://github.com/z843248880/combinesearch
4.多级评论
4.1 多级评论实现原理
评论表里设置一个值用来存储评论之间的关系,比如id为3的评论是评论id为1的评论的,那存储的时候就是“3 评论内容 1”,如果是评论新闻的而不是评论别人的评论的,最后一位就设置为None。
将评论表里的所有数据做成一个字典,每一条评论都是key,如果该评论有子评论就设置为该key的value,如果没有子评论,则value设置为空字典{}。然后循环读字典里的内容,然后再根据“根评论”还是“子评论”来缩进显示评论内容。
注:此处的key必须是元组格式的,字典不能作为key。
4.2 多级评论实现代码
setting.py常规配置(注册app);
urls.py,
url(r'^comment/', views.comment),
views.py,
from django.shortcuts import render import collections # Create your views here. def tree_search(d_dic, comment_obj): # 在comment_dic中一个一个的寻找其回复的评论 # 检查当前评论的 reply_id 和 comment_dic中已有评论的nid是否相同, # 如果相同,表示就是回复的此信息 # 如果不同,则需要去 comment_dic 的所有子元素中寻找,一直找,如果一系列中未找,则继续向下找 # d_dic{ # (1, '111', None): { # (5, '555', 1): {} # } # (2, '222', None): { # "(4, '444', 2)": { # "(6, '666', 4),": {} # } # } # (3, '333', None): {} # } # comment_obj # (6, '666', 4), for k, v_dic in d_dic.items(): # 如果key值的元组里的第一个元素与传入元组的第三个元素相等,就表示他俩是父子评论,比如(3,111,10)和(5,555,3),(5,555,3)就是(3,111,10)的子评论 if k[0] == comment_obj[2]: d_dic[k][comment_obj] = collections.OrderedDict() return else: if v_dic: # 在当前第一个跟元素中递归的去寻找父亲 tree_search(d_dic[k], comment_obj) def build_tree(comment_list): # collections.OrderedDict()的作用是创建一个有序空字典{};之所以要有序,是因为可以做到让评论有序的显示,不然的话,因为字典是无需的,所以取到的评论内容也是无需的,显示起来会有变化。 comment_dic = collections.OrderedDict() for comment_obj in comment_list: if comment_obj[2] is None: # 如果是根评论(元组的最后一位是None),添加到comment_dic[(1, '111', None)] = {} # { # (1, '111', None): {} # (2, '222', None): {} # (3, '333', None): {} # } comment_dic[comment_obj] = collections.OrderedDict() else: # (4, '444', 2), # 如果是子评论,则需要在 comment_dic 中找到其回复的评论 tree_search(comment_dic, comment_obj) return comment_dic def comment(request): # comment_list里存储的就当是数据库评论表里的条目,格式必须是元组的,因为元组格式可以作为字典的key。 comment_list = [ (1, '111', None), (2, '222', None), (3, '333', None), (4, '444', 2), (5, '555', 1), (6, '666', 4), (7, '777', 2), (8, '888', 4), ] comment_dict = build_tree(comment_list) #经过build_tree处理后,comment_list就变成下面的字典格式了,有子评论的话,子评论就是父评论的key对应的value;如果没有子评论,则该key对应的value就是一个空有序字典。 # dic = { # "(1 qqq None)":{ # "(2 360 1)": { # "(4 baidu 2)": {} # }, # "(3 ali 1)": {} # }, # "(5 baidu None)": { # "(8 baidu 5)": {} # }, # "(6 baidu None)": { # "(7 baidu 6)": {} # } # } # 将处理好的字典传入前端 return render(request, 'comment.html', {'comment_dict': comment_dict})
comment.html,
# 使用模板函数 {% load xx %} {% tree comment_dict %}
app01/templatetag/xx.py,
from django import template from django.utils.safestring import mark_safe register = template.Library() TEMP1 = """ <div class='content' style='margin-left:%s;'> <span>%s</span> """ def generate_comment_html(sub_comment_dic, margin_left_val): html = '<div class="comment">' for k, v_dic in sub_comment_dic.items(): # 因为是子评论了,所以需要加上margin_left_val(30)像素的偏移量,子子评论再加margin_left_val(30)的偏移量,以此类推。 html += TEMP1 % (margin_left_val, k[1]) # 只要有字典,就递归的往下执行generate_comment_html()函数 if v_dic: html += generate_comment_html(v_dic, margin_left_val) html += "</div>" html += "</div>" return html @register.simple_tag def tree(comment_dic): # 将comment_dic字典里的数据拼接成html传给前端 html = '<div class="comment">' for k, v in comment_dic.items(): # 因为是根评论,所以margin-left应该是0,所以这里传入(0,k[1]),k[1]是评论内容 html += TEMP1 % (0, k[1]) # 如果v不为空字典,则执行generate_comment_html() if v: html += generate_comment_html(v, 30) html += "</div>" html += '</div>' return mark_safe(html)
多级评论完整代码github地址:https://github.com/z843248880/morecomment
5. Tornado使用示例
Tornado自带的功能没有Django丰富,所以它也比较轻量;创建Tornado程序时就创建一个py文件即可,然后import tornado的模块即可使用。
新建一个tornadodemo.py,
import tornado.ioloop import tornado.web user_info = [] class MainHandler(tornado.web.RequestHandler): # get请求就执行这个 def get(self): # self.write("Hello, world") # 等同于HttpResponse self.render('index.html', user_info_list = user_info) # post请求就执行这个 def post(self, *args, **kwargs): # self.write('post') # self.render('index.html') # 获取用户提交的数据 user = self.get_argument('user') pwd = self.get_argument('pwd') user_info.append({'u': user, 'p': pwd}) self.redirect('/index') # 配置静态文件和html的目录;默认是在根目录下(也就是主.py文件的同级目录) settings = { 'template_path': 'template', 'static_path': 'static', } application = tornado.web.Application([ (r"/index", MainHandler), # 等价于url.py的路由功能;用户访问index,就交给MainHandler处理。 ], **settings) if __name__ == "__main__": application.listen(8888) # epoll + socket tornado.ioloop.IOLoop.instance().start()
template/index.html,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1>asdfsadf</h1> <form action="/index" method="POST"> <input type="text" name="user" /> <input type="text" name="pwd" /> <input type="submit" value="提交" /> </form> <!--<img src="/static/1.jpg">--> <hr/> <ul> # tornado模板语言,无论for或者if,结尾都是end,不像django的endfor、endif。 {% for item in user_info_list%} # tornado模板语言,取数据时跟python一模一样,如下面的取字典里的数据,可以直接dict['key'],也可以dict.get('key','default');不像django里的item.1。 <li>{{item.get('u', "123")}}-{{item['p']}}</li> {% end %} </ul> </body> </html>
这样一个使用tornado web框架的例子就做完了。
有点小问题,为了以后功能多了也能比较清晰的查看代码,所以最好把主.py里的代码按功能分离一下,比如上面的tornadodemo.py内容,可以分离成下面两个,
tornadodemo.py,
import tornado.ioloop import tornado.web #建立一个controller目录,下面建一个home.py文件,这个py文件里定义MainHandler类 from controller.home import MainHandler settings = { 'template_path': 'views', 'static_path': 'static', } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) # epoll + socket tornado.ioloop.IOLoop.instance().start()
controller/home.py,
import tornado.web user_info = [] class MainHandler(tornado.web.RequestHandler): def get(self): # self.write("Hello, world") # HttpResponse self.render('index.html', user_info_list = user_info) def post(self, *args, **kwargs): # self.write('post') # self.render('index.html') # 获取用户提交的数据 user = self.get_argument('user') pwd = self.get_argument('pwd') user_info.append({'u': user, 'p': pwd}) self.redirect('/index')
这样分离后,以后功能多了,也能比较清晰的查看各功能对应的代码。