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{
             980px;
            margin: 0 auto;
        }
#每个列div的宽度为总宽度的四分之一
        .container .column{
            float: left;
             245px;
        }
        .container .item img{
             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{
             980px;
            margin: 0 auto;
        }
        .container .column{
            float: left;
             245px;
        }
        .container .item img{
             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')

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

原文地址:https://www.cnblogs.com/fuckily/p/6367995.html