一個web應(yīng)用的誕生(11)--列表分頁

上章的結(jié)束饰剥,若在實際開發(fā)過程中忠荞,會發(fā)現(xiàn)一個問題蒋歌,那就首頁或關(guān)注分享,是一下子按時間順序全部顯示出來委煤,這在實際項目中不可能出現(xiàn)的,想想實際中的產(chǎn)品是如何做的碧绞?

一般來說府框,無非是兩種,一種是使用頁碼讥邻,來進(jìn)行分頁迫靖,還有一種是js到頁底自動加載,而使用頁底自動加載的話兴使,上一章實現(xiàn)的通過tab來區(qū)分全部和關(guān)注就不可取了系宜,因為無法保證兩個tab加載的內(nèi)容數(shù)量一致,導(dǎo)致頁面布局就無法實現(xiàn)发魄,所以盹牧,這里首頁參考tumblr的實現(xiàn)方式,刪除關(guān)注分享的部分励幼,只保留全部分享欢策,使用js頁底動態(tài)加載分頁方式,同時在導(dǎo)航欄新增兩個導(dǎo)航赏淌,分別為博文踩寇,和關(guān)注,使用傳統(tǒng)頁碼的方式顯示全部博文和已關(guān)注博文六水,這樣是為了有些人可能會查詢比較久的歷史信息俺孙,所以辣卒,一個頁面,一個功能如何設(shè)計睛榄,主要取決于業(yè)務(wù)需求荣茫,而不是技術(shù)需求。首先修改導(dǎo)航(base.html):

 <ul class="nav navbar-nav">
    <li><a href="/">首頁</a></li>
    <li><a href="#">分享</a></li>
    {% if current_user.is_authenticated %}
    <li><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
           aria-haspopup="true" aria-expanded="false">關(guān)注 <span class="caret"></span></a>
      <ul class="dropdown-menu">
        <li><a href="#">分享</a></li>
        <li><a href="#">用戶</a></li>
      </ul>
    </li>
    {% endif %}
  </ul>

用戶登錄后场靴,在首頁后面會新增兩個item啡莉,分別是分享和關(guān)注,其中關(guān)注是一個下拉菜單旨剥,分別是“我”關(guān)注的用戶發(fā)布的分享咧欣,和“我”關(guān)注的用戶

下面完成這幾個頁面,首先是分享頁轨帜,即所有用戶發(fā)布的分享魄咕,頁面與之前的首頁很像,首先完成視圖模型:

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

這個模型的route的意思是蚌父,既可以通過/post訪問哮兰,也可以通過/post/1等類型訪問,當(dāng)/post訪問的時候苟弛,默認(rèn)訪問第一頁喝滞。

endpoint的意思為訪問的端點,即方法的端點膏秫,針對于這個方法來說囤躁,endpoint的值為"main.post"

接下來的內(nèi)容,就是本章的一個重點了荔睹,pagination對象,這個是flask-SQLAlchemy框架中的一個很重要的對象言蛇,它包含了一系列用于分頁的屬性僻他,其中主要的屬性如下:

has_next
是否還有下一頁

has_prev
是否還有前一頁

items
當(dāng)前頁的數(shù)據(jù)

iter_pages(left_edge=2,left_current=2,right_current=5,right_edge=2)
一個關(guān)于頁面的迭代,可以有四個帶有默認(rèn)值的參數(shù):

  1. left_edge 頁碼最左邊顯示的頁數(shù)
  2. left_current 當(dāng)前頁左邊顯示的頁數(shù)
  3. right_current 當(dāng)前頁右邊顯示的頁數(shù)
  4. right_edge 頁面最右邊顯示的頁數(shù)

可能有些不好理解腊尚,舉個例子吨拗,假設(shè)共100頁,當(dāng)前為50頁婿斥,則顯示如下:

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

next(error_out=False)
下一頁的分頁對象劝篷,當(dāng)error_out為true時,超過頁面返回404
prev(error_out=False)
上一頁的分頁對象
page
當(dāng)前頁碼
prev_num
上頁頁碼
next_num
下頁頁碼
per_page
每頁顯示的記錄數(shù)量
total
記錄總數(shù)
還有更多屬性民宿,請查驗文檔

分享頁的模板與首頁幾乎一樣娇妓,同樣是一個分享發(fā)布框,一個已分享列表(posts.html):

{% import "_index_post_macros.html" as macros %}
...
<div class="container">
<div class="row">
 <div class="col-xs-12 col-md-8 col-md-8 col-lg-8">
  <div>
      {% if current_user.is_authenticated %}
      {{ wtf.quick_form(form) }}
      {% endif %}
  </div>
  <br>

<div  class="tab-content">
  <!--全部-->
  <div id="all" role="tabpanel" class="tab-pane fade in active">
    {{macros.rander_posts(posts,moment,pagination,endpoint)}}
  </div>
</div>
 </div>
 <div class="col-md-4 col-md-4 col-lg-4">
     <!--這里 當(dāng)沒有用戶登錄的時候 顯示熱門分享列表 稍后實現(xiàn)-->
     {% if current_user.is_authenticated %}
    ![...](http://on4ag3uf5.bkt.clouddn.com/{{current_user.headimg}})
     <br><br>
     <p class="text-muted">我已經(jīng)分享<span class="text-danger">{{ current_user.posts.count() }}</span>條心情</p>
     <p class="text-muted">我已經(jīng)關(guān)注了<span class="text-danger">{{ current_user.followed.count() }}</span>名好友</p>
     <p class="text-muted">我已經(jīng)被<span class="text-danger">{{ current_user.followers.count() }}</span>名好友關(guān)注</p>
     {%endif%}
 </div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
{{ pagedown.include_pagedown() }}
<script type="text/javascript">
    $('.nav-tabs a').click(function (e) {
      e.preventDefault()
      $(this).tab('show')
    })
</script>
{% endblock%}

并沒有做過多的封裝活鹰,其實完全可以把右側(cè)在封裝成為一個macro

接下來是_index_post_macros.html

{% macro rander_posts(posts,moment,pagination=None,endpoint=None) %}
{% import "_posts_page_macros.html" as macros %}
{% for post in posts %}
  <div class="bs-callout
          {% if loop.index % 2 ==0 %}
           bs-callout-d
          {% endif %}
          {% if loop.last %}
            bs-callout-last
          {% endif %}" >
      <div class="row">
          <div class="col-sm-2 col-md-2">
                <!--使用測試域名-->
               <a class="text-left" href="{{url_for('main.user',username=post.author.username)}}">
                ![...](http://on4ag3uf5.bkt.clouddn.com/{{post.author.headimg}})
               </a>
          </div>
          <div class="col-sm-10 col-md-10">
           <div>
            <p>
               {% if post.body_html%}
                  {{post.body_html|safe}}
                {% else %}
               {{post.body}}
               {% endif %}
            </p>
            </div>
           <div>
            <a class="text-left" href="{{url_for('main.user',username=post.author.username)}}">{{post.author.nickname}}</a>
            <span class="text-right">發(fā)表于&nbsp;{{ moment( post.createtime).fromNow(refresh=True)}}</span>
           </div>
          </div>
      </div>
  </div>
{% endfor %}
{% if pagination and endpoint %}
  {{macros.rander_page(pagination,endpoint)}}
{% endif %}
{%endmacro%}

這里需要注意的一點也就是最下邊新增的代碼哈恰,意味著macro也可以嵌套只估,如果pagination和endpoint不為None,則顯示頁碼着绷,而_posts_page_macros.html的代碼如下:

{% macro rander_page(pagination,endpoint) %}
<nav aria-label="Page navigation">
  <ul class="pagination  pagination-sm">
      {% if pagination.has_prev %}
      <li>
          <a href="{{url_for(endpoint,page=pagination.page-1)}}" aria-label="Previous">
               <span aria-hidden="true">&laquo;</span>
          </a>
       </li>
      {% else %}
      <li class="disabled">
          <a href="#" aria-label="Previous">
               <span aria-hidden="true">&laquo;</span>
          </a>
      </li>
      {% endif %}
    {% for p in pagination.iter_pages() %}
      {% if p%}
        {% if p ==pagination.page%}
            <li class="active"><a href="#">{{p}}</a></li>
        {% else %}
            <li><a href="{{url_for(endpoint,page=p)}}">{{p}}</a></li>
        {% endif %}
      {% else %}
        <li class="disabled"><a href="#">...</a></li>
      {% endif %}
    {% endfor %}

    {% if pagination.has_next %}
     <li>
      <a href="{{url_for(endpoint,page=pagination.page+1)}}"  aria-label="Next">
          <span aria-hidden="true">&raquo;</span>
      </a>
     </li>
    {% else %}
      <li class="disabled">
      <a href="#" aria-label="Next">
          <span aria-hidden="true">&raquo;</span>
      </a>
     </li>
    {% endif %}
  </ul>
</nav>
{% endmacro %}

這是一個比較典型的pagination的使用方式蛔钙,完全使用了bootstrap的樣式,最終的顯示效果如下:

貌似內(nèi)容有點少荠医,分頁我發(fā)測試吁脱,并且之后關(guān)注分頁,首頁動態(tài)分頁都要用彬向,所以首先要擴(kuò)充一些分享內(nèi)容兼贡,擴(kuò)充的方式多種多樣,比如實際數(shù)據(jù)幢泼,手動修改數(shù)據(jù)庫紧显,但對于python來說,它提供了一個不錯的生成虛擬數(shù)據(jù)的輪子缕棵,即ForgeryPy孵班,首先當(dāng)然還是安裝:

pip3.6 install ForgeryPy

然后修改Post類,添加一個靜態(tài)方法(Post.py)

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

幾個參數(shù)說明一下:

  1. lorem_ipsum比較有趣招驴,原意為一些排版時的占位用的無意義字符篙程,具體解釋可參考阮博的博客,sentences方法為生成普通句子,參數(shù)代表句子的數(shù)量别厘。
  2. date的參數(shù)表示生成時間的區(qū)間虱饿,True表示生成的區(qū)間都為過去的時間

這個靜態(tài)方法的使用方式為:

python manage.py shell
Post.generate_fake()

這樣就會生成100條分享。

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

不考慮美工的話触趴,效果還是可以的氮发,不過內(nèi)容都是英文的,不知道能不能有一個中文的虛擬數(shù)據(jù)生成器:)

下面是關(guān)注分享頁冗懦,和這個頁類似,視圖模型為:

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

注意爽冕,由于關(guān)注分享,分享和首頁都有PostForm披蕉,所以把這個功能獨立出來:

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

其實我想flask應(yīng)該有整個頁面的某一個功能獨立為一個視圖模型的方式颈畸,但我沒有找到,如果讀者找到了別忘了留言回復(fù)

最后没讲,關(guān)注的用戶就太簡單了眯娱,可以直接使用某用戶關(guān)注的頁面,將userid參數(shù)賦予當(dāng)前用戶的參數(shù)即可爬凑,最終base.html的導(dǎo)航部分代碼為:

<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
  <ul class="nav navbar-nav">
    <li><a href="/">首頁</a></li>
    <li><a href="{{url_for('main.post')}}">分享</a></li>
    {% if current_user.is_authenticated %}
    <li><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
           aria-haspopup="true" aria-expanded="false">關(guān)注 <span class="caret"></span></a>
      <ul class="dropdown-menu">
        <li><a href="{{url_for('main.follow_post')}}">分享</a></li>
        <li><a href="{{url_for('main.follow_list',type='followed',userid=current_user.id)}}">用戶</a></li>
      </ul>
    </li>
    {% endif %}
  </ul>
  <ul class="nav navbar-nav navbar-right">
    {% if current_user.is_authenticated %}
        <li><p class="navbar-text"><a href="#" class="navbar-link">{{current_user.username}}</a>  您好</p></li>
        <li><a href="{{url_for('auth.logout')}}">登出</a></li>
    {% else %}
        <li><a href="{{url_for('auth.login')}}">登錄</a></li>
        <li><a href="{{url_for('auth.register')}}">注冊</a></li>
    {% endif %}
  </ul>
  <form class="navbar-form navbar-right">
    <div class="form-group">
      <input type="text" class="form-control" placeholder="Search">
    </div>
    <button type="submit" class="btn btn-default">搜索</button>
  </form>
</div><!-- /.navbar-collapse -->

然后刪除首頁中tab的部分徙缴,最終代碼為:

<div class="container">
<div class="row">
 <div class="col-xs-12 col-md-8 col-md-8 col-lg-8">
  <div>
      {% if current_user.is_authenticated %}
      {{ wtf.quick_form(form) }}
      {% endif %}
  </div>
  <br>
  <ul class="nav nav-tabs">
      <li role="presentation" class="active"><a href="#all">最新分享</a></li>
  </ul>
<div  class="tab-content">
  <!--全部-->
  <div id="all" role="tabpanel" class="tab-pane fade in active">
    {{macros.rander_posts(posts,moment)}}
  </div>
</div>
 </div>
 <div class="col-md-4 col-md-4 col-lg-4">
     <!--這里 當(dāng)沒有用戶登錄的時候 顯示熱門分享列表 稍后實現(xiàn)-->
     {% if current_user.is_authenticated %}
    ![...](http://on4ag3uf5.bkt.clouddn.com/{{current_user.headimg}})
     <br><br>
     <p class="text-muted">我已經(jīng)分享<span class="text-danger">{{ current_user.posts.count() }}</span>條心情</p>
     <p class="text-muted">我已經(jīng)關(guān)注了<span class="text-danger">{{ current_user.followed.count() }}</span>名好友</p>
     <p class="text-muted">我已經(jīng)被<span class="text-danger">{{ current_user.followers.count() }}</span>名好友關(guān)注</p>
     {%endif%}
 </div>
</div>
</div>

這時候,你可能已經(jīng)發(fā)現(xiàn)了嘁信,首頁的分享還沒有進(jìn)行分頁迁霎,在本章的開始部分就已經(jīng)解釋道,首頁使用動態(tài)加載的分頁方式百宇,而動態(tài)加載顯然需要js的配合考廉,使用json的方式向html中注入昌粤。這時候服務(wù)端就會面臨一個問題,如何與客戶端的js進(jìn)行交互呢袱讹,這是下一章將要說明的問題捷雕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浦译,一起剝皮案震驚了整個濱河市溯职,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖鹰贵,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亭珍,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蓖租,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門油猫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來情妖,“玉大人共螺,你說我怎么就攤上這事藐不〕” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵姻灶,是天一觀的道長。 經(jīng)常有香客問我这嚣,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮友酱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘郭计。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般揭蜒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上职车,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天登下,我揣著相機(jī)與錄音,去河邊找鬼篓冲。 笑死诽俯,一個胖子當(dāng)著我的面吹牛刃唤,可吹牛的內(nèi)容都是我干的密幔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼偎箫,長吁一口氣:“原來是場噩夢啊……” “哼木柬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淹办,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤眉枕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怜森,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體速挑,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年副硅,在試婚紗的時候發(fā)現(xiàn)自己被綠了姥宝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡恐疲,死狀恐怖腊满,靈堂內(nèi)的尸體忽然破棺而出套么,到底是詐尸還是另有隱情,我是刑警寧澤碳蛋,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布违诗,位于F島的核電站,受9級特大地震影響疮蹦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茸炒,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一愕乎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧壁公,春花似錦感论、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至囊陡,卻和暖如春芳绩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撞反。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工妥色, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遏片。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓嘹害,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吮便。 傳聞我的和親對象是個殘疾皇子笔呀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容