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', '') #分类id,默认是0
classfication_id = kwargs.get('classfication_id', '') #当前url值
current_url = request.path_info #如果方向id是0,也就是用户没有选择方向,则默认就将所有的分类(python、linux、openstatck、javascripts)显示
if direction_id == '':
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 != '': #如果用输入的分类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] = ""
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] == '':
temp = '<a class="active" href="%s">全部</a>'
else:
temp = '<a href="%s">全部</a>'
sp_url[id] = ''
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, '', None),
(2, '', None),
(3, '', None),
(4, '', 2),
(5, '', 1),
(6, '', 4),
(7, '', 2),
(8, '', 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', "")}}-{{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')

这样分离后,以后功能多了,也能比较清晰的查看各功能对应的代码。

jsonp、瀑布流布局、组合搜索、多级评论(评论树)、Tornado初识的更多相关文章

  1. django实现瀑布流、组合搜索、阶梯评论、验证码

    django实现图片瀑布流布局 我们在一些图片网站上经常会看到,满屏都是图片,而且图片都大小不一,却可以按空间排列.默认一个div是占用一行,当想把div里的图片并排显示的时候,只能使用float属性 ...

  2. 轮播组件/瀑布流/组合搜索/KindEditor插件

    一.企业官网 ### 瀑布流 ​ Models.Student.objects.all() #获取所有学员信息 ​ 通过div进行循环图片和字幕 ​ 1.以template模板方法实现瀑布流以列为单位 ...

  3. python运维开发(二十二)---JSONP、瀑布流、组合搜索、多级评论、tornado框架简介

    内容目录: JSONP应用 瀑布流布局 组合搜索 多级评论 tornado框架简介 JSONP应用 由于浏览器存在同源策略机制,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. ...

  4. Django 之组合搜索

    现在很多网站都会有这样的组合搜索功能,其实质是几个模型之间组合对数据库进行查询,并将结果显示到页面上. 每一行都是一个模型,模型之间有着连表关系(一对多.多对多等) 模型设计 总共四个模型:分别为方向 ...

  5. Django 项目补充知识(JSONP,前端瀑布流布局,组合搜索,多级评论)

    一.JSONP 1浏览器同源策略 通过Ajax,如果在当前域名去访问其他域名时,浏览器会出现同源策略,从而阻止请求的返回 由于浏览器存在同源策略机制,同源策略阻止从一个源加载的文档或脚本获取或设置另一 ...

  6. Django 六——自定义标签、图片验证码、发送邮件、评论树、组合搜索

    1.自定义标签 2.图片验证码 3.生成邮箱验证码.发送邮件 4.评论树实现 5.组合搜索(Q) 1.自定义标签 配置: a.在app中新建文件夹  templatetags,里面新建  xx.py文 ...

  7. python(Django之组合搜索、JSONP、XSS过滤 )

    一.组合搜索 二.jsonp 三.xss过滤 一.组合搜索 首先,我们在做一个门户网站的时候,前端肯定是要进行搜索的,但是如果搜索的类型比较多的话,怎么做才能一目了然的,这样就引出了组合搜索的这个案例 ...

  8. Django实现瀑布流,组合搜索

    Django中组合搜索功能 需求分析 很多电商网站中有组合搜索的功能,所谓组合搜索就是网页中组合多个条件,对数据库中进行查询,并且将结果显示在页面中,看个例子吧: 注意红框中的标识,我们可以根据URL ...

  9. Python开发【Django】:组合搜索、JSONP、XSS过滤

    组合搜索 做博客后台时,需要根据文章的类型做不同的检索 1.简单实现 关联文件: from django.conf.urls import url from . import views urlpat ...

随机推荐

  1. hdu-2852 KiKi's K-Number---二分+树状数组

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2852 题目大意: 题意:    给出三种操作,    0 在容器中插入一个数.    1 在容器中删 ...

  2. Gym 100090M Jumping along the Hummocks

    题意: 从 前往后跳,要么跳一步,跳到相邻的位置,要么跳到下一个数字相同的位置,求跳到最后的最少步数. dp,但是会tle,我用map优化了一下. #include <bits/stdc++.h ...

  3. 【[SDOI2017]新生舞会】

    题目 好题啊 我们要求的是 \[C=\frac{\sum_{i=1}^na_i}{\sum_{i=1}^nb_i}\] 使得\(C\)最大 显然 \[C\times \sum_{i=1}^nb_i=\ ...

  4. Matlab将矩阵保存为图像

    imwrite(image,'image.jpg'); image为矩阵的内容 image.jpg为要保存的图像的名字

  5. matlab 大块注释和取消注释的快捷键

    matlab 大块注释和取消注释的快捷键 注释:Ctrl+R 取消注释:Ctrl +T

  6. NestedScrollView和RecyclerView使用,并设置间距

    NestedScrollView和RecyclerView使用,并设置间距: 效果图如下: 1.NestedScrollView 和RecyclerView嵌套问题(类似ScrollView 和lis ...

  7. 总结JavaScript常用数组操作方法,包含ES6方法

    一.concat() concat() 方法用于连接两个或多个数组.该方法不会改变现有的数组,仅会返回被连接数组的一个副本. var arr1 = [1,2,3]; var arr2 = [4,5]; ...

  8. js将人民币数字转大写

    function numberToUpper(money) { var cnNums = new Array("零", "壹", "贰", ...

  9. 用js控制单选框或者多选框问题

    出现如图问题时,这时不能用attr方法添加checked属性了,改用$( "input" ).prop( "checked", true ),完美解决.

  10. 使用union all 命令之后如何对hive表格进行去重

    业务场景大概是这样的,这里由两个hive表格,tableA 和 tableB, 格式内容都是这样的: uid cate1 cate2 在hive QL中,我们知道union有着自动去重的功能,但是那是 ...