• 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')

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

  • 相关阅读:
    struts2+Hibernate4+spring3+EasyUI环境搭建之四:引入hibernate4以及spring3与hibernate4整合
    struts2+Hibernate4+spring3+EasyUI环境搭建之三:引入sututs2以及spring与sututs2整合
    struts2+Hibernate4+spring3+EasyUI环境搭建之二:搭建spring
    Log4j
    【maven学习】构建maven web项目
    struts2+Hibernate4+spring3+EasyUI环境搭建之一:准备工作
    高版本myeclipse破解以及优化
    Eclipse&Myeclipse安装反编译插件
    Flask源码阅读-第四篇(flaskapp.py)
    Flask源码阅读-第三篇(flask\_compat.py)
  • 原文地址:https://www.cnblogs.com/fuckily/p/6367995.html
Copyright © 2020-2023  润新知