列表分页

 

上章的结束,若在实际开发过程中,会发现一个问题,那就首页或关注分享,是一下子按时间顺序全部显示出来,这在实际项目中不可能出现的,想想实际中的产品是如何做的?

一般来说,无非是两种,一种是使用页码,来进行分页,还有一种是js到页底自动加载,而使用页底自动加载的话,上一章实现的通过tab来区分全部和关注就不可取了,因为无法保证两个tab加载的内容数量一致,导致页面布局就无法实现,所以,这里首页参考tumblr的实现方式,删除关注分享的部分,只保留全部分享,使用js页底动态加载分页方式,同时在导航栏新增两个导航,分别为博文,和关注,使用传统页码的方式显示全部博文和已关注博文,这样是为了有些人可能会查询比较久的历史信息,所以,一个页面,一个功能如何设计,主要取决于业务需求,而不是技术需求。首先修改导航(base.html):

  1. <ul class="nav navbar-nav">
  2. <li><a href="/">首页</a></li>
  3. <li><a href="#">分享</a></li>
  4. {% if current_user.is_authenticated %}
  5. <li><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
  6. aria-haspopup="true" aria-expanded="false">关注 <span class="caret"></span></a>
  7. <ul class="dropdown-menu">
  8. <li><a href="#">分享</a></li>
  9. <li><a href="#">用户</a></li>
  10. </ul>
  11. </li>
  12. {% endif %}
  13. </ul>

用户登录后,在首页后面会新增两个item,分别是分享和关注,其中关注是一个下拉菜单,分别是“我”关注的用户发布的分享,和“我”关注的用户

下面完成这几个页面,首先是分享页,即所有用户发布的分享,页面与之前的首页很像,首先完成视图模型:

  1. @main.route("/post")
  2. @main.route("/post/<int:page>")
  3. def post(page=1):
  4. pagination=Post.query.order_by(Post.createtime.desc()).paginate(
  5. page,per_page=current_app.config["POSTS_PER_PAGE"],error_out=False
  6. )
  7. return render_template("posts.html",posts=pagination.items,pagination=pagination,endpoint=request.endpoint)

这个模型的route的意思是,既可以通过/post访问,也可以通过/post/1等类型访问,当/post访问的时候,默认访问第一页。

endpoint的意思为访问的端点,即方法的端点,针对于这个方法来说,endpoint的值为"main.post"

接下来的内容,就是本章的一个重点了,pagination对象,这个是flask-SQLAlchemy框架中的一个很重要的对象,它包含了一系列用于分页的属性,其中主要的属性如下:

has_next
是否还有下一页

has_prev
是否还有前一页

items
当前页的数据

iter_pages(left_edge=2,left_current=2,right_current=5,right_edge=2)
一个关于页面的迭代,可以有四个带有默认值的参数:

  1. left_edge 页码最左边显示的页数
  2. left_current 当前页左边显示的页数
  3. right_current 当前页右边显示的页数
  4. right_edge 页面最右边显示的页数

可能有些不好理解,举个例子,假设共100页,当前为50页,则显示如下:

  1. 1,2...48,49,50,51,52,53,54,55...99,100

next(error_out=False)
下一页的分页对象,当error_out为true时,超过页面返回404
prev(error_out=False)
上一页的分页对象
page
当前页码
prev_num
上页页码
next_num
下页页码
per_page
每页显示的记录数量
total
记录总数
还有更多属性,请查验文档

分享页的模板与首页几乎一样,同样是一个分享发布框,一个已分享列表(posts.html):

  1. {% import "_index_post_macros.html" as macros %}
  2. ...
  3. <div class="container">
  4. <div class="row">
  5. <div class="col-xs-12 col-md-8 col-md-8 col-lg-8">
  6. <div>
  7. {% if current_user.is_authenticated %}
  8. {{ wtf.quick_form(form) }}
  9. {% endif %}
  10. </div>
  11. <br>
  12. <div class="tab-content">
  13. <!--全部-->
  14. <div id="all" role="tabpanel" class="tab-pane fade in active">
  15. {{macros.rander_posts(posts,moment,pagination,endpoint)}}
  16. </div>
  17. </div>
  18. </div>
  19. <div class="col-md-4 col-md-4 col-lg-4">
  20. <!--这里 当没有用户登录的时候 显示热门分享列表 稍后实现-->
  21. {% if current_user.is_authenticated %}
  22. <img src="http://on4ag3uf5.bkt.clouddn.com/{{current_user.headimg}}" alt="..." class="headimg img-thumbnail">
  23. <br><br>
  24. <p class="text-muted">我已经分享<span class="text-danger">{{ current_user.posts.count() }}</span>条心情</p>
  25. <p class="text-muted">我已经关注了<span class="text-danger">{{ current_user.followed.count() }}</span>名好友</p>
  26. <p class="text-muted">我已经被<span class="text-danger">{{ current_user.followers.count() }}</span>名好友关注</p>
  27. {%endif%}
  28. </div>
  29. </div>
  30. </div>
  31. {% endblock %}
  32. {% block scripts %}
  33. {{ super() }}
  34. {{ pagedown.include_pagedown() }}
  35. <script type="text/javascript">
  36. $('.nav-tabs a').click(function (e) {
  37. e.preventDefault()
  38. $(this).tab('show')
  39. })
  40. </script>
  41. {% endblock%}

并没有做过多的封装,其实完全可以把右侧在封装成为一个macro

接下来是_index_post_macros.html

  1. {% macro rander_posts(posts,moment,pagination=None,endpoint=None) %}
  2. {% import "_posts_page_macros.html" as macros %}
  3. {% for post in posts %}
  4. <div class="bs-callout
  5. {% if loop.index % 2 ==0 %}
  6. bs-callout-d
  7. {% endif %}
  8. {% if loop.last %}
  9. bs-callout-last
  10. {% endif %}" >
  11. <div class="row">
  12. <div class="col-sm-2 col-md-2">
  13. <!--使用测试域名-->
  14. <a class="text-left" href="{{url_for('main.user',username=post.author.username)}}">
  15. <img src="http://on4ag3uf5.bkt.clouddn.com/{{post.author.headimg}}" alt="...">
  16. </a>
  17. </div>
  18. <div class="col-sm-10 col-md-10">
  19. <div>
  20. <p>
  21. {% if post.body_html%}
  22. {{post.body_html|safe}}
  23. {% else %}
  24. {{post.body}}
  25. {% endif %}
  26. </p>
  27. </div>
  28. <div>
  29. <a class="text-left" href="{{url_for('main.user',username=post.author.username)}}">{{post.author.nickname}}</a>
  30. <span class="text-right">发表于&nbsp;{{ moment( post.createtime).fromNow(refresh=True)}}</span>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. {% endfor %}
  36. {% if pagination and endpoint %}
  37. {{macros.rander_page(pagination,endpoint)}}
  38. {% endif %}
  39. {%endmacro%}

这里需要注意的一点也就是最下边新增的代码,意味着macro也可以嵌套,如果pagination和endpoint不为None,则显示页码,而_posts_page_macros.html的代码如下:

  1. {% macro rander_page(pagination,endpoint) %}
  2. <nav aria-label="Page navigation">
  3. <ul class="pagination pagination-sm">
  4. {% if pagination.has_prev %}
  5. <li>
  6. <a href="{{url_for(endpoint,page=pagination.page-1)}}" aria-label="Previous">
  7. <span aria-hidden="true">&laquo;</span>
  8. </a>
  9. </li>
  10. {% else %}
  11. <li class="disabled">
  12. <a href="#" aria-label="Previous">
  13. <span aria-hidden="true">&laquo;</span>
  14. </a>
  15. </li>
  16. {% endif %}
  17. {% for p in pagination.iter_pages() %}
  18. {% if p%}
  19. {% if p ==pagination.page%}
  20. <li class="active"><a href="#">{{p}}</a></li>
  21. {% else %}
  22. <li><a href="{{url_for(endpoint,page=p)}}">{{p}}</a></li>
  23. {% endif %}
  24. {% else %}
  25. <li class="disabled"><a href="#">...</a></li>
  26. {% endif %}
  27. {% endfor %}
  28. {% if pagination.has_next %}
  29. <li>
  30. <a href="{{url_for(endpoint,page=pagination.page+1)}}" aria-label="Next">
  31. <span aria-hidden="true">&raquo;</span>
  32. </a>
  33. </li>
  34. {% else %}
  35. <li class="disabled">
  36. <a href="#" aria-label="Next">
  37. <span aria-hidden="true">&raquo;</span>
  38. </a>
  39. </li>
  40. {% endif %}
  41. </ul>
  42. </nav>
  43. {% endmacro %}

这是一个比较典型的pagination的使用方式,完全使用了bootstrap的样式,最终的显示效果如下:

貌似内容有点少,分页我发测试,并且之后关注分页,首页动态分页都要用,所以首先要扩充一些分享内容,扩充的方式多种多样,比如实际数据,手动修改数据库,但对于python来说,它提供了一个不错的生成虚拟数据的轮子,即ForgeryPy,首先当然还是安装:

  1. pip3.6 install ForgeryPy

然后修改Post类,添加一个静态方法(Post.py)

  1. @staticmethod
  2. def generate_fake():
  3. from random import seed, randint;
  4. from .User import User
  5. import forgery_py;
  6. seed()
  7. user_count = User.query.count()
  8. for i in range(100):
  9. u = User.query.offset(randint(0, user_count - 1)).first()
  10. p = Post(body=forgery_py.lorem_ipsum.sentences(randint(1, 3)),
  11. createtime=forgery_py.date.date(True), author=u)
  12. db.session.add(p)
  13. db.session.commit()

几个参数说明一下:

  1. lorem_ipsum比较有趣,原意为一些排版时的占位用的无意义字符,具体解释可参考阮博的博客,sentences方法为生成普通句子,参数代表句子的数量。
  2. date的参数表示生成时间的区间,True表示生成的区间都为过去的时间

这个静态方法的使用方式为:

  1. python manage.py shell
  2. Post.generate_fake()

这样就会生成100条分享。

下面在看一下分享页的效果(尾页):

不考虑美工的话,效果还是可以的,不过内容都是英文的,不知道能不能有一个中文的虚拟数据生成器:)

下面是关注分享页,和这个页类似,视图模型为:

  1. @main.route("/follow_post",methods=["GET","POST"])
  2. @main.route("/follow_post/<int:page>",methods=["GET","POST"])
  3. @login_required
  4. def follow_post(page=1):
  5. form = PostForm()
  6. if form_util(form):
  7. return redirect(url_for(request.endpoint)) # 跳回首页
  8. print(form.body.data)
  9. pagination=Post.query.select_from(Follow).filter_by(follower_id=current_user.id)\
  10. .join(Post,Follow.followed_id == Post.author_id).paginate(
  11. page,per_page=current_app.config["POSTS_PER_PAGE"],error_out=False
  12. )
  13. return render_template("posts.html",posts=pagination.items,form=form,
  14. pagination=pagination,endpoint=request.endpoint)

注意,由于关注分享,分享和首页都有PostForm,所以把这个功能独立出来:

  1. def form_util(form):
  2. if form.validate_on_submit():
  3. post = Post(body=form.body.data, author_id=current_user.id)
  4. db.session.add(post);
  5. return True
  6. return False

其实我想flask应该有整个页面的某一个功能独立为一个视图模型的方式,但我没有找到,如果读者找到了别忘了留言回复

最后,关注的用户就太简单了,可以直接使用某用户关注的页面,将userid参数赋予当前用户的参数即可,最终base.html的导航部分代码为:

  1. <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
  2. <ul class="nav navbar-nav">
  3. <li><a href="/">首页</a></li>
  4. <li><a href="{{url_for('main.post')}}">分享</a></li>
  5. {% if current_user.is_authenticated %}
  6. <li><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
  7. aria-haspopup="true" aria-expanded="false">关注 <span class="caret"></span></a>
  8. <ul class="dropdown-menu">
  9. <li><a href="{{url_for('main.follow_post')}}">分享</a></li>
  10. <li><a href="{{url_for('main.follow_list',type='followed',userid=current_user.id)}}">用户</a></li>
  11. </ul>
  12. </li>
  13. {% endif %}
  14. </ul>
  15. <ul class="nav navbar-nav navbar-right">
  16. {% if current_user.is_authenticated %}
  17. <li><p class="navbar-text"><a href="#" class="navbar-link">{{current_user.username}}</a> 您好</p></li>
  18. <li><a href="{{url_for('auth.logout')}}">登出</a></li>
  19. {% else %}
  20. <li><a href="{{url_for('auth.login')}}">登录</a></li>
  21. <li><a href="{{url_for('auth.register')}}">注册</a></li>
  22. {% endif %}
  23. </ul>
  24. <form class="navbar-form navbar-right">
  25. <div class="form-group">
  26. <input type="text" class="form-control" placeholder="Search">
  27. </div>
  28. <button type="submit" class="btn btn-default">搜索</button>
  29. </form>
  30. </div><!-- /.navbar-collapse -->

然后删除首页中tab的部分,最终代码为:

  1. <div class="container">
  2. <div class="row">
  3. <div class="col-xs-12 col-md-8 col-md-8 col-lg-8">
  4. <div>
  5. {% if current_user.is_authenticated %}
  6. {{ wtf.quick_form(form) }}
  7. {% endif %}
  8. </div>
  9. <br>
  10. <ul class="nav nav-tabs">
  11. <li role="presentation" class="active"><a href="#all">最新分享</a></li>
  12. </ul>
  13. <div class="tab-content">
  14. <!--全部-->
  15. <div id="all" role="tabpanel" class="tab-pane fade in active">
  16. {{macros.rander_posts(posts,moment)}}
  17. </div>
  18. </div>
  19. </div>
  20. <div class="col-md-4 col-md-4 col-lg-4">
  21. <!--这里 当没有用户登录的时候 显示热门分享列表 稍后实现-->
  22. {% if current_user.is_authenticated %}
  23. <img src="http://on4ag3uf5.bkt.clouddn.com/{{current_user.headimg}}" alt="..." class="headimg img-thumbnail">
  24. <br><br>
  25. <p class="text-muted">我已经分享<span class="text-danger">{{ current_user.posts.count() }}</span>条心情</p>
  26. <p class="text-muted">我已经关注了<span class="text-danger">{{ current_user.followed.count() }}</span>名好友</p>
  27. <p class="text-muted">我已经被<span class="text-danger">{{ current_user.followers.count() }}</span>名好友关注</p>
  28. {%endif%}
  29. </div>
  30. </div>
  31. </div>

这时候,你可能已经发现了,首页的分享还没有进行分页,在本章的开始部分就已经解释道,首页使用动态加载的分页方式,而动态加载显然需要js的配合,使用json的方式向html中注入。这时候服务端就会面临一个问题,如何与客户端的js进行交互呢,这是下一章将要说明的问题。

python列表分页的更多相关文章

  1. python/Djangof分页与自定义分页

    python/Djangof分页与自定义分页 Django分页 ##============================================分页==================== ...

  2. python 列表排序

    转自http://www.iplaypython.com/jinjie/jj114.html reverse()方法 将列表中元素反转排序,比如下面这样>>> x = [1,5,2, ...

  3. python列表、元祖、字典

    python列表   ['a','1','vs2']       里面的值可以改 python元祖   ('a','1','css','sdf12')   里面的值不能改 python字典   {'s ...

  4. 基于Metronic的Bootstrap开发框架经验总结(2)--列表分页处理和插件JSTree的使用

    在上篇<基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理>介绍了Bootstrap开发框架的一些基础性概括,包括总体界面效果,以及布局.菜单等内容, ...

  5. Python列表、元组、字典和字符串的常用函数

    Python列表.元组.字典和字符串的常用函数 一.列表方法 1.ls.extend(object) 向列表ls中插入object中的每个元素,object可以是字符串,元组和列表(字符串“abc”中 ...

  6. Python 列表

    python 列表 列表的特点 1.列表是一种可变的数据类型,这点是跟元组有区别的 2.列表中的值是有序的,并且可存放重复的值,这点跟set有区别的 3.python中的列表类似于其它语言中的数组 4 ...

  7. python 列表生成器

    python 列表生成器 列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式. 一个循环 在C语言等其他语言中,for循环一般是这样的 ...

  8. PHP+jQuery 列表分页类 ( 支持 url 分页 / ajax 分页 )

    /* ******* 环境:Apache2.2.8 ( 2.2.17 ) + PHP5.2.6 ( 5.3.3 ) + MySQL5.0.51b ( 5.5.8 ) + jQuery-1.8.3.mi ...

  9. [转载] Python 列表(list)、字典(dict)、字符串(string)常用基本操作小结

    创建列表 sample_list = ['a',1,('a','b')] Python 列表操作 sample_list = ['a','b',0,1,3] 得到列表中的某一个值 value_star ...

随机推荐

  1. 微信小程序常见的UI框架/组件库总结

    想要开发出一套高质量的小程序,运用框架,组件库是省时省力省心必不可少一部分,随着小程序日渐火爆,各种不同类型的小程序也渐渐更新,其中不乏一些优秀好用的框架/组件库. 1:WeUI 小程序–使用教程 h ...

  2. swift开发多线程篇 - NSThread 线程相关简单说明(一些使用和注意点)

    一 说明 本文涉及代码可以从https://github.com/HanGangAndHanMeimei/Code地址获得. 二 NSThread的基本使用和创建 1)基本用法(主线程|当前线程) 1 ...

  3. Oracle游标进行循环效率比较

    对300万一张表数据,用游标进行循环,不同写法的效率比较 对300万一张表数据,用游标进行循环,不同写法的效率比较   1.显示游标   declare     cursor cur_2 is sel ...

  4. windows下, nginx 提示错误 "No input file specified"

    https://blog.csdn.net/m_nanle_xiaobudiu/article/details/80386035

  5. layui是什么

    layui是什么 一.总结 一句话总结:初步看起来比amazeui好看一点点.移动端显示看起来效果真心不错.还有即时聊天那个组件下载,感觉真心不错,可以多去看看. 二.Layui 1.简介 经典模块化 ...

  6. 《Unix编程艺术》读书笔记(1)

    <Unix编程艺术>读书笔记(1) 这两天開始阅读该书,以下是自己的体会,以及原文的摘录,尽管有些东西还无法全然吃透. 写优雅的代码来提高软件系统的透明性:(P134) Elegance ...

  7. 一起学libcef--给你的浏览器删除cookie

    long long ago, 我们讨论了如给你cef设置cookie. 如今来补充一点,假设给你的浏览器删除某一cookie. review一下设置cookie: std::wstring usern ...

  8. [Angular] Zones and NgZone

    NgZone, Angular uses it to profiling all the async actions such as setTimeout, http request and anim ...

  9. Android官方数据绑定框架DataBinding(一)

    还记得在博客<高逼格UI-ASD(Android Support Design)>的開始曾经说过,Android最新推出了一个官方的数据绑定框架-Data Binding Library. ...

  10. 使用Opencv中matchTemplate模板匹配方法跟踪移动目标

    模板匹配是一种在图像中定位目标的方法,通过把输入图像在实际图像上逐像素点滑动,计算特征相似性,以此来判断当前滑块图像所在位置是目标图像的概率. 在Opencv中,模板匹配定义了6种相似性对比方式: C ...