このガイドの目的は、非常に機能的なマイクロブログアプリケーションを開発することです
microblog.
オリジナリティが完全に欠如していることから
microblog.
と呼ぶことにしました
microblog.
内容
内容
パート1:Hello World!
パート2:テンプレート
パート3:フォーム
パート4:データベース
パート5:ユーザーログイン
パート6:プロフィールページとアバター
パート7:単体テスト
パート8:サブスクライバー、連絡先、および友人。
パート9:ページネーション(この記事)
パート10:全文検索
パート11:メールサポート
パート12:再構成
パート13:日付と時刻
パート14:I18nおよびL10n
パート15:Ajax
パート16:デバッグ、テスト、およびプロファイリング
パート17:Linux(およびRaspberry Piでも)での展開
パート18:Heroku Cloudでのデプロイ
パート1:Hello World!
パート2:テンプレート
パート3:フォーム
パート4:データベース
パート5:ユーザーログイン
パート6:プロフィールページとアバター
パート7:単体テスト
パート8:サブスクライバー、連絡先、および友人。
パート9:ページネーション(この記事)
パート10:全文検索
パート11:メールサポート
パート12:再構成
パート13:日付と時刻
パート14:I18nおよびL10n
パート15:Ajax
パート16:デバッグ、テスト、およびプロファイリング
パート17:Linux(およびRaspberry Piでも)での展開
パート18:Heroku Cloudでのデプロイ
短い繰り返し
前の記事では、ユーザーが他のユーザーの投稿を追跡できる「サブスクライバー」パラダイムをサポートするために、データベースに必要なすべての変更を加えました。
今日は、前回行ったことに基づいて、アプリケーションを念頭に置いて、ユーザーに実際のコンテンツを受け取って配信するようにします。 今日、私たちは最後の偽物に別れを告げます!
ブログ投稿の送信
簡単なものから始めましょう。 ホームページには、新しい投稿用のカスタムフォームが必要です。
最初に、単一フィールドのフォームオブジェクト
( app/forms.py)
を定義します。
class PostForm(Form): post = TextField('post', validators = [Required()])
次に、フォームをテンプレート
( app/templates/index.html)
追加し
( app/templates/index.html)
:
<!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>Hi, {{g.user.nickname}}!</h1> <form action="" method="post" name="post"> {{form.hidden_tag()}} <table> <tr> <td>Say something:</td> <td>{{ form.post(size = 30, maxlength = 140) }}</td> <td> {% for error in form.errors.post %} <span style="color: red;">[{{error}}]</span><br> {% endfor %} </td> </tr> <tr> <td></td> <td><input type="submit" value="Post!"></td> <td></td> </tr> </table> </form> {% for post in posts %} <p> {{post.author.nickname}} says: <b>{{post.body}}</b> </p> {% endfor %} {% endblock %}
お気づきかもしれませんが、超自然的なものはありません。 以前と同じように、別の図形を追加します。
そして最後に、すべてを結び付けるビュー関数
( app/views.py)
:
from forms import LoginForm, EditForm, PostForm from models import User, ROLE_USER, ROLE_ADMIN, Post @app.route('/', methods = ['GET', 'POST']) @app.route('/index', methods = ['GET', 'POST']) @login_required def index(): form = PostForm() if form.validate_on_submit(): post = Post(body = form.post.data, timestamp = datetime.utcnow(), author = g.user) db.session.add(post) db.session.commit() flash('Your post is now live!') return redirect(url_for('index')) posts = [ { 'author': { 'nickname': 'John' }, 'body': 'Beautiful day in Portland!' }, { 'author': { 'nickname': 'Susan' }, 'body': 'The Avengers movie was so cool!' } ] return render_template('index.html', title = 'Home', form = form, posts = posts)
この関数で行った変更をステップごとに見てみましょう。
- まず、
Post
クラスとPostForm
クラスをインポートしますPostForm
-
index()
ビュー関数に関連付けられた両方のルートからのPOSTリクエストを受け入れます。ここでは新しい投稿を受け入れます。
- 投稿がデータベースに保存される前に、この関数に分類されます。 GETを介してこの関数に入ると、すべてが以前のように行われます。
- テンプレートは、テキストフィールドにレンダリングするフォームという追加の引数を受け取ります。
そして最後に、続行する前に。 新しい投稿をデータベースに書き込む前に、これを行うことに注意してください。
return redirect(url_for('index'))
このリダイレクトを簡単にスキップして、レンダリングに進むことができます。 おそらくそれはさらに効果的です。 実際、このリダイレクトが行うことはすべて、実際にこの機能に戻るためです。
では、なぜリダイレクトなのでしょうか? ユーザーがブログに投稿を書き込み、公開し、更新ボタンを押したときに何が起こるかを考えてください。 更新コマンドは何をしますか? ブラウザは以前のリクエストを再送信します。
リダイレクトがない場合、最後の1つはフォームを送信したPOSTリクエストであったため、「更新」アクションはこのリクエストを再送信し、最初のポストと同じ2番目のポストを作成します。 これは悪いです。
ただし、リダイレクトを使用すると、フォームの送信後にブラウザーに別の要求を強制的に発行させます。 これは単純なGETリクエストであり、Refreshボタンはフォームを再度送信する代わりに、ページを再度ロードするだけです。
この単純なトリックにより、ユーザーが投稿後に誤ってページを更新した場合に、重複した投稿を挿入しないようにすることができます。
ブログ投稿を表示する
最も興味深いものに渡します。 データベースから投稿を引き出して表示します。
いくつかの記事を思い出していただければ、偽の投稿をいくつか作成し、長い間メインページに表示してきました。 これらの偽オブジェクトは、Pythonリストビュー関数で明示的に作成されました。
posts = [ { 'author': { 'nickname': 'John' }, 'body': 'Beautiful day in Portland!' }, { 'author': { 'nickname': 'Susan' }, 'body': 'The Avengers movie was so cool!' } ]
しかし、前回の記事では、ユーザーがサブスクライブしているユーザーからのすべてのメッセージを受信できるようにするリクエストを作成したため、上記の行
( app/views.py)
簡単に置き換えることができ
( app/views.py)
。
posts = g.user.followed_posts().all()
これで、アプリケーションを起動すると、データベースからの投稿が表示されます!
User
クラスの
followed_post
メソッドは、関心のあるメッセージを取得するように構成されたsqlalchemy
query
オブジェクトを返します。 このオブジェクトで
all()
メソッドを呼び出すと、
all()
の投稿がシートの形式で取得されるため、最終的には使い慣れた構造で作業できます。 これらは非常に類似しているため、テンプレートは何も認識しません。
これで、アプリケーションで遊ぶことができます。 恥ずかしがらないでください。 複数のユーザーを作成し、それらを他のユーザーにサブスクライブし、最後に複数のメッセージを公開して、各ユーザーが自分のフィードをどのように表示するかを確認できます。
ページネーション
私たちのアプリケーションはこれまで以上に良く見えますが、問題があります。 ホームページにすべてのメッセージを表示します。 ユーザーが数千人にサインアップするとどうなりますか? それとも100万? ご想像のとおり、このような大きなリストを取得して処理することは非常に非効率的です。
代わりに、潜在的に多数のページ分割された投稿を表示します。
Flask-SQLAlchemyには、非常に優れたページネーションサポートが備わっています。 たとえば、追跡対象ユーザーから最初の3つの投稿を取得したい場合は、次のようにします。
posts = g.user.followed_posts().paginate(1, 3, False).items
paginate
メソッド
paginate
、
query
オブジェクトで呼び出されます。 次の3つの引数が必要です。
- 1から始まるページ番号
- ページあたりの要素数、
- およびエラーフラグ。 フラグがTrueに設定されている場合、リストを超えると、404エラーがクライアントに返されます。
- それ以外の場合は、エラーの代わりに空のリストが返されます。
paginate
メソッドは、ページネーションオブジェクトを返します。 このオブジェクトのメンバーには、要求されたページの要素のリストが含まれています。 Paginationオブジェクトには他にも便利なものがありますが、それらについては後ほど説明します。
index()ビュー関数でページネーションを実装する方法について考えてみましょう。 まず、アプリケーションに構成要素を追加して、ページに表示する要素の数を決定します。
# pagination POSTS_PER_PAGE = 3
動作に影響を与える可能性があるグローバルアプリケーション設定を1か所に保存することをお勧めします。
もちろん、最終アプリケーションでは3より大きい数を使用しますが、テストには少量で作業する方が便利です。
次に、ページリクエストを含むURLがどのようになるかを決めましょう。 Flaskを使用すると、ルートで引数を受け入れることができるため、目的のページを指すサフィックスを追加できます。
http://localhost:5000/ <-- page #1 (default) http://localhost:5000/index <-- page #1 (default) http://localhost:5000/index/1 <-- page #1 http://localhost:5000/index/2 <-- page #2
このURL形式は、ビュー
( app/views.py)
追加ルートを使用して簡単に実装できます。
from config import POSTS_PER_PAGE @app.route('/', methods = ['GET', 'POST']) @app.route('/index', methods = ['GET', 'POST']) @app.route('/index/<int:page>', methods = ['GET', 'POST']) @login_required def index(page = 1): form = PostForm() if form.validate_on_submit(): post = Post(body = form.post.data, timestamp = datetime.utcnow(), author = g.user) db.session.add(post) db.session.commit() flash('Your post is now live!') return redirect(url_for('index')) posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False).items return render_template('index.html', title = 'Home', form = form, posts = posts)
新しいルートは、引数をページ番号でヤンクし、整数として宣言します。 また、この引数を
index()
関数に追加してデフォルト値を設定する必要があります。これは、3つのルートのうち2つがこの引数を使用せず、デフォルト値で使用されるためです。
ページ番号ができたので、前に定義した
POSTS_PER_PAGE
変数とともに、
followed_posts
リクエストに簡単に接続できます。
これらの変更がいかに簡単で、コードの変更が少ないことに注目してください。 他の部分がどのように機能するかを推測するのではなく、アプリケーションのすべての部分を記述しようとします。これにより、テストしやすいモジュール式で信頼性の高いアプリケーションを構築できます。
ブラウザのアドレスバーに異なる行番号のURLを入力することで、ページネーションを体験できるようになりました。 ページに表示される投稿が3つ以上あることを確認してください。
ページナビゲーション
次に、ユーザーが次/前のページに移動できるリンクを追加する必要があります。幸いなことに、Flask-SQLAlchemyがほとんどの作業を行います。
プレゼンテーション機能に小さな変更を加えることから始めます。 現在のバージョンでは、次のようにページネーションを使用しています。
posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False).items
これにより、paginateメソッドによって返されたPaginationオブジェクトの要素のみを保存します。 ただし、このオブジェクトは非常に便利な機能を提供するため、オブジェクト全体
( app/views.py)
。
posts = g.user.followed_posts().paginate(page, POSTS_PER_PAGE, False)
この変更をコピーするには、テンプレート
( app/templates/index.html)
を変更する必要があり
( app/templates/index.html)
:
<!-- posts is a Paginate object --> {% for post in posts.items %} <p> {{post.author.nickname}} says: <b>{{post.body}}</b> </p> {% endfor %}
テンプレートのPaginateオブジェクトを提供するもの。 使用するオブジェクトのメソッドは次のとおりです。
- has_next:現在のページの後に少なくとも1ページあります
- has_prev:現在の前に少なくとも1ページがある場合はtrue
- next_num:次のページ番号
- prev_num:前のページ番号
彼らの助けを借りて、次のことができます
( app/templates/index.html)
:
<!-- posts is a Paginate object --> {% for post in posts.items %} <p> {{post.author.nickname}} says: <b>{{post.body}}</b> </p> {% endfor %} {% if posts.has_prev %}<a href="{{ url_for('index', page = posts.prev_num) }}"><< Newer posts</a>{% else %}<< Newer posts{% endif %} | {% if posts.has_next %}<a href="{{ url_for('index', page = posts.next_num) }}">Older posts >></a>{% else %}Older posts >>{% endif %}
これで2つのリンクができました。 最初に、「新しい投稿」リンクを表示します。これにより、前のページから新しい投稿に移動できます。 一方、古い投稿は次のページ、古い投稿に送られます。
しかし、最初のページにいるときは、前のページへのリンクは必要ありません。 このような場合は、falseを返すposts.has_prevメソッドを使用して簡単に追跡できます。 この場合、リンクテキストは表示されますが、リンク自体は表示されません。 次のページではないリンクも同様に処理されます。
サブパターンの実装後
前に、アバターを追加した記事で、1つの投稿をレンダリングするためのHTMLコードでサブパターンを定義します。 異なるページに投稿を表示したい場合にコードの重複をなくすために、このテンプレートを作成しました。
メインページにこのサブパターンを実装する時が来ましたが、今日のほとんどのこと
( app/templates/index.html)
と同じくらい簡単
( app/templates/index.html)
:
<!-- posts is a Paginate object --> {% for post in posts.items %} {% include 'post.html' %} {% endfor %}
すごいね 古いレンダリングコードをテンプレートのインクルードに置き換えました。 このようにして、ユーザーアバターを使用して、より良いバージョンのレンダリングを取得します。
以下は、現在の状態のメインページのスクリーンショットです。
ユーザープロファイルページ
これで、メインページが完成しました。 また、ユーザープロファイルにはメッセージを含めましたが、すべてではなく、プロファイルの所有者のみを含めました。 一貫性を保つには、メインページに合わせてプロファイルページを変更する必要があります。
変更は、メインページで行った変更と同様です。 ここに、私たちがする必要があることの短いリストがあります:
- ページ番号を取る余分なルートを追加します
- 引数をページ番号とともにビュー関数に追加して割り当てます1
- 偽の投稿をデータベースの実際の投稿に置き換え、ページに分割します
- ページネーションオブジェクトを使用するようにテンプレートを更新します
これらは、ビュー関数
( app/views.py)
の更新
( app/views.py)
:
@app.route('/user/<nickname>') @app.route('/user/<nickname>/<int:page>') @login_required def user(nickname, page = 1): user = User.query.filter_by(nickname = nickname).first() if user == None: flash('User ' + nickname + ' not found.') return redirect(url_for('index')) posts = user.posts.paginate(page, POSTS_PER_PAGE, False) return render_template('user.html', user = user, posts = posts)
, ( ), .
テンプレートの変更も非常に簡単です
( app/templates/user.html)
:
<!-- posts is a Paginate object --> {% for post in posts.items %} {% include 'post.html' %} {% endfor %} {% if posts.has_prev %}<a href="{{ url_for('user', nickname = user.nickname, page = posts.prev_num) }}"><< Newer posts</a>{% else %}<< Newer posts{% endif %} | {% if posts.has_next %}<a href="{{ url_for('user', nickname = user.nickname, page = posts.next_num) }}">Older posts >></a>{% else %}Older posts >>{% endif %}
最後の言葉
以下に、この記事で行ったすべての変更を含む
microblog
アプリケーションの更新バージョンを投稿します。
microblog-0.9.zipをダウンロードします。
いつものように、データベースはありません。自分で作成する必要があります。 この一連の記事に従うと、その方法がわかります。 そうでない場合は、データベースの記事に戻って調べてください。
いつものように、私をフォローしてくれてありがとう。 次の記事でお会いしましょう。
ミゲル