フラスコメガチュヌトリアル、パヌトIXペヌゞネヌション2018幎版

ミゲル・グリンバヌグ




<<<前 次>>>


これは、Mega-Tutorial Flaskシリヌズの第9版で、デヌタベヌス内のリストを分割する方法を説明したす。


参考たでに、以䞋はこのシリヌズの蚘事のリストです。


目次

泚1このコヌスの叀いバヌゞョンをお探しの堎合は、こちらをご芧ください 。


泚2突然、このブログの私のミゲルの仕事を支持しお声を䞊げたい堎合、たたは単に蚘事を1週間埅぀忍耐がない堎合、私ミゲルグリヌンバヌグはこのガむドの完党版にパッケヌゞ化された電子曞籍たたはビデオを提䟛したす。 詳现に぀いおは、 learn.miguelgrinberg.comをご芧ください 。


第8章では、゜ヌシャルネットワヌクで非垞に人気のある「フォロワヌ」サブスクラむバヌパラダむムをサポヌトするために必芁なデヌタベヌスにいく぀かの倉曎を加えたした。 この機胜により、デモンストレヌション甚に䜜成した最埌の゚ントリを削陀する準備ができたした。 これらは停のメッセヌゞです。


この章では、アプリケヌションはナヌザヌからのブログ投皿の受信を開始し、それらをむンデックスペヌゞずプロファむルペヌゞに配信したす。


この章のGitHubリンク Browse 、 Zip 、 Diff 。


ブログ投皿


簡単なものから始めたしょう。 ホヌムペヌゞには、ナヌザヌが新しいメッセヌゞを入力できるフォヌムが必芁です。 最初にフォヌムクラスを䜜成したす。


class PostForm(FlaskForm): post = TextAreaField('Say something', validators=[ DataRequired(), Length(min=1, max=140)]) submit = SubmitField('Submit') 

これで、このフォヌムをアプリケヌションのメむンペヌゞのテンプレヌトに远加できたす。


 {% extends "base.html" %} {% block content %} <h1>Hi, {{ current_user.username }}!</h1> <form action="" method="post"> {{ form.hidden_tag() }} <p> {{ form.post.label }}<br> {{ form.post(cols=32, rows=4) }}<br> {% for error in form.post.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p>{{ form.submit() }}</p> </form> {% for post in posts %} <p> {{ post.author.username }} says: <b>{{ post.body }}</b> </p> {% endfor %} {% endblock %} 

このテンプレヌトぞの倉曎は、他のフォヌムで行われた倉曎に䌌おいたす。 結論ずしお、フォヌムの䜜成ず凊理を衚瀺機胜に远加する必芁がありたす。


 from app.forms import PostForm from app.models import 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, author=current_user) db.session.add(post) db.session.commit() flash('Your post is now live!') return redirect(url_for('index')) posts = [ { 'author': {'username': 'John'}, 'body': 'Beautiful day in Portland!' }, { 'author': {'username': 'Susan'}, 'body': 'The Avengers movie was so cool!' } ] return render_template("index.html", title='Home Page', form=form, posts=posts) 

このビュヌ関数ぞの倉曎を順番に芋おみたしょう。



続行する前に、Webフォヌムの凊理に関連するいく぀かの重芁なポむントに泚意を喚起したいず思いたす。 フォヌムデヌタを凊理した埌、 むンデックスのメむンペヌゞにリダむレクトを送信しおリク゚ストを終了するこずに泚意しおください。 これは既にむンデックスビュヌ関数であるため、リダむレクトを簡単にスキップしお、関数をテンプレヌトレンダリングパヌツで匕き続き機胜させるこずができたす。


なぜリダむレクトするのですか



暙準的な方法は、リダむレクトされたWebフォヌムを送信するずきにPOST芁求の芁求に応答するこずです。 これは、Webブラりザヌで曎新コマンドを䜿甚するずきに、刺激の発䜜を䜕らかの圢で回避するのに圹立ちたす。 結局、曎新ボタンをクリックするず、Webブラりザに最埌のリク゚ストが衚瀺されたす。 フォヌム送信を䌎うPOSTリク゚ストが通垞の応答を返す堎合、曎新はフォヌムを再送信したす。 これは垞に予期しないこずであるため、ブラりザヌはナヌザヌに再送信の確認を求めたすが、ほずんどのナヌザヌはブラりザヌに必芁なものを理解したせん。


しかし、リダむレクトがPOSTリク゚ストに応答するず、ブラりザはGETリク゚ストを送信しおリダむレクトで指定されたペヌゞをキャプチャするように指瀺されるため、最埌のリク゚ストはPOSTリク゚ストではなくなり、曎新コマンドはより予枬可胜な方法で機胜したす。


この単玔なトリックは、 Post / Redirect / Getパタヌンにすぎたせん。 。 ナヌザヌがWebフォヌムを送信した埌に誀っおペヌゞを曎新したずきに、重耇したメッセヌゞが挿入されるのを防ぎたす。


ブログ投皿を芋る


芚えおいるなら、私は長い間ホヌムペヌゞに衚瀺しおいるいく぀かのブログ投皿を䜜成したした。 これらの停オブゞェクトは、単玔なPythonリストずしおむンデックスビュヌ関数で明瀺的に䜜成されたす。


 posts = [ { 'author': {'username': 'John'}, 'body': 'Beautiful day in Portland!' }, { 'author': {'username': 'Susan'}, 'body': 'The Avengers movie was so cool!' } ] 

しかし今、私はUserモデルにfollowed_posts()メ゜ッドがあり、このナヌザヌが芋たいメッセヌゞを返したす。 そのため、䞀時メッセヌゞを実際のメッセヌゞに眮き換えるこずができたす。


 @app.route('/', methods=['GET', 'POST']) @app.route('/index', methods=['GET', 'POST']) @login_required def index(): # ... posts = current_user.followed_posts().all() return render_template("index.html", title='Home Page', form=form, posts=posts) 

Userクラスのfollowed_postsメ゜ッドは、 Userがデヌタベヌスから賌読したメッセヌゞをキャプチャするように構成されたSQLAlchemyク゚リオブゞェクトを返したす。 このリク゚ストでall()を呌び出すず実行が開始され、戻り倀はすべおの結果を含むリストになりたす。


したがっお、これたでに䜿甚した䞀時メッセヌゞを生成したものず非垞によく䌌た構造を取埗したす。 これは非垞に䌌おいるため、テンプレヌトを倉曎する必芁さえありたせん。


ナヌザヌ怜玢を促進する


珟時点でアプリケヌションがどのように機胜するかは、ナヌザヌが他のナヌザヌを芋぀けられるようにするのにあたり䟿利ではないこずに気づいたこずを願っおいたす。 実際、他のナヌザヌが䜕をしおいるのかを確認する方法はたったくありたせん。 いく぀かの簡単な倉曎でこれを修正したす。


新しいペヌゞを䜜成する必芁がありたす。これを「探玢」ペヌゞず呌びたす。 このペヌゞはホヌムペヌゞずしお機胜したすが、次のナヌザヌからのメッセヌゞのみを衚瀺する代わりに、すべおのナヌザヌからのグロヌバルなメッセヌゞフロヌを衚瀺したす。 新しいビュヌ関数は次のずおりです。


 @app.route('/explore') @login_required def explore(): posts = Post.query.order_by(Post.timestamp.desc()).all() return render_template('index.html', title='Explore', posts=posts) 

この機胜に奇劙なこずに気づいたこずがありたすか render_template()の呌び出しは、アプリケヌションのメむンペヌゞで䜿甚するindex.htmlテンプレヌトを参照したす。 このペヌゞはメむンペヌゞず非垞に䌌おいるため、テンプレヌトを再利甚するこずにしたした。


ただし、メむンペヌゞずの1぀の違いは、[探玢]ペヌゞにはブログ投皿を曞くためのフォヌムが必芁ないため、このビュヌ関数ではテンプレヌト呌び出しにform匕数を含めなかったこずです。


index.htmlテンプレヌトがクラッシュしないように、存圚しないWebフォヌムを衚瀺しようずするず、定矩されおいる堎合にのみフォヌムを衚瀺する条件を远加したす。


 {% extends "base.html" %} {% block content %} <h1>Hi, {{ current_user.username }}!</h1> {% if form %} <form action="" method="post"> ... </form> {% endif %} ... {% endblock %} 

たた、ナビゲヌションバヌにこの新しいペヌゞぞのリンクを远加したす。


 <a href="{{ url_for('explore') }}">Explore</a> 

ナヌザヌプロファむルペヌゞにブログ投皿を衚瀺するには、 第6章の_post.htmlサブパタヌンを芚えおいたすか これは、ナヌザヌプロファむルペヌゞテンプレヌトから抜出され、他のテンプレヌトから䜿甚できるように分離された小さなテンプレヌトです。 ブログ投皿のナヌザヌ名をリンクずしお衚瀺できるように、少し改善したす。


 <table> <tr valign="top"> <td><img src="{{ post.author.avatar(36) }}"></td> <td> <a href="{{ url_for('user', username=post.author.username) }}"> {{ post.author.username }} </a> says:<br>{{ post.body }} </td> </tr> </table> 

これで、このサブテンプレヌトを䜿甚しお、ホヌムペヌゞでブログを芖芚化および孊習できたす。


 ... {% for post in posts %} {% include '_post.html' %} {% endfor %} ... 

ネストされたテンプレヌトは、 postずいう名前の倉数が存圚するこずを想定しおおり、それがむンデックステンプレヌト内のルヌプ倉数が呌び出されるため、これは正垞に機胜したす。


これらの小さな倉曎のおかげで、アプリケヌションの䜿いやすさが倧幅に向䞊したした。 これで、ナヌザヌはペヌゞにアクセスしお未知のナヌザヌからのブログ投皿を読むこずができ、これらの投皿に基づいお新しいナヌザヌを芋぀けおサブスクリプションを远加できたす。 すごいね


この時点で、アプリケヌションを再詊行しお、これらの最新のUIの改善を䜓隓するこずをお勧めしたす。



ブログ投皿の共有


アプリケヌションは以前よりも芋栄えがよくなりたすが、ホヌムペヌゞにすべおの゚ントリを衚瀺するこずは、想像よりもはるかに早く問題になりたす。 ナヌザヌが1,000件のレコヌドを持っおいる堎合はどうなりたすか それずも100䞇 このような倧量のメッセヌゞのリストの管理は、非垞に遅く、非効率的です。


この問題を解決するために、メッセヌゞのリストを分割したす。 ぀たり、最初は䞀床に限られた数のメッセヌゞのみを衚瀺し、メッセヌゞリストの残りの郚分をナビゲヌトするリンクを含めたす。 Flask-SQLAlchemyは、 paginate()ク゚リメ゜ッドを䜿甚しおネむティブにペヌゞネヌションをサポヌトしたす。 たずえば、最初の20個のナヌザヌレコヌドを取埗する必芁がある堎合、リク゚ストの最埌にall()ぞの呌び出しを眮き換えるこずができたす。


 >>> user.followed_posts().paginate(1, 20, False).items 

paginateメ゜ッドは、Flask-SQLAlchemyの任意のク゚リオブゞェクトに察しお呌び出すこずができたす。 これには3぀の匕数が必芁です。



paginateからの戻り倀は、 Paginationオブゞェクトです。 このオブゞェクトのitems属性には、リク゚ストされたペヌゞのアむテムのリストが含たれおいたす。 Paginationオブゞェクトにはただ有甚なものがありたすが、これに぀いおは埌で説明したす。


次に、 index()ビュヌ関数でペヌゞネヌションを実装する方法に぀いお考えおみたしょう。 たず、ペヌゞに衚瀺されるアむテムの数を決定する構成アむテムをアプリケヌションに远加したす。


 class Config(object): # ... POSTS_PER_PAGE = 3 

アプリケヌション党䜓のこれらの「ノブ」が構成ファむルの動䜜に圱響を䞎える可胜性があるこずをお勧めしたす。これにより、1か所ですべおの修正を行うこずができたす。 その結果、私はもちろん、ペヌゞ䞊で3぀以䞊の芁玠を䜿甚したすが、テストには小さな数で䜜業するのが䟿利です。


次に、ペヌゞ番号をアプリケヌションURLに含める方法を決定する必芁がありたす。 かなり䞀般的な方法は、 ク゚リ文字列匕数を䜿甚しおオプションのペヌゞ番号を指定するこずです。指定されおいない堎合は、デフォルトでペヌゞ1に衚瀺されたす。 これを実装する方法を瀺すURLの䟋を次に瀺したす。



ク゚リ文字列で指定された匕数にアクセスするには、Flaskオブゞェクトのrequest.argsオブゞェクトを䜿甚できたす。 これに぀いおは、 第5章ですでに説明したした。ここでは、Flask-Loginからのナヌザヌログむン甚のURLを実装したした。


次の䟋は、ホヌムペヌゞの内蚳をいく぀かに远加し、衚瀺機胜を調査した方法を瀺しおいたす。


 @app.route('/', methods=['GET', 'POST']) @app.route('/index', methods=['GET', 'POST']) @login_required def index(): # ... page = request.args.get('page', 1, type=int) posts = current_user.followed_posts().paginate( page, app.config['POSTS_PER_PAGE'], False) return render_template('index.html', title='Home', form=form, posts=posts.items) @app.route('/explore') @login_required def explore(): page = request.args.get('page', 1, type=int) posts = Post.query.order_by(Post.timestamp.desc()).paginate( page, app.config['POSTS_PER_PAGE'], False) return render_template("index.html", title='Explore', posts=posts.items) 

これらの倉曎では、2぀のルヌトの䞡方が、 pageリク゚ストのpage匕数から、たたはデフォルトで1のいずれかで、衚瀺するペヌゞ番号を決定したす。次に、 paginate()メ゜ッドを䜿甚しお、結果の目的のペヌゞのみを取埗したす。 ペヌゞサむズを決定するPOSTS_PER_PAGE構成POSTS_PER_PAGE 、 POSTS_PER_PAGEオブゞェクトからアクセスできたす。


これらの倉曎がどれほど簡単で、どのコヌドにもほずんど圱響しないこずに泚意しおください。 私は各郚分を曞き、他の郚分の䜜業から抜象化しようずしおいたす。これにより、拡匵ずテストが容易なモゞュヌル匏で信頌性の高いアプリケヌションを䜜成できたす。 同時に、臎呜的なミスや小さなミスが発生する可胜性は本質的に小さいです。


続けお そしお、ペヌゞネヌション機胜を詊しおみるべきです。 事前に3぀以䞊のブログ投皿があるこずを確認しおください。 これは、すべおのナヌザヌからのメッセヌゞが衚瀺される怜玢ペヌゞで芋やすくなりたす。 これで、最埌の3぀のメッセヌゞのみを衚瀺できたす。 次の3぀を衚瀺する堎合は、 http://localhost:5000/explore?page=2をブラりザヌのアドレスバヌに入力したす。



次の倉曎点は、ブログ投皿のリストの䞋郚にリンクを远加しお、ナヌザヌが次のペヌゞや前のペヌゞに移動できるようにするこずです。 paginate()呌び出しからの戻り倀は、Flask-SQLAlchemyからのPaginationクラスのオブゞェクトであるこずを述べたこずを思い出しおください。 これたで、このオブゞェクトのitems属性を䜿甚したしたが、
遞択したペヌゞに぀いお取埗されたアむテムのリストが含たれたす。 しかし、このオブゞェクトには、ペヌゞぞのリンクを䜜成するずきに圹立぀いく぀かの他の属性がありたす。



これら4぀の芁玠を䜿甚しお、ペヌゞ次および前ぞのリンクを䜜成し、それらを衚瀺甚のテンプレヌトに枡すこずができたす。


 @app.route('/', methods=['GET', 'POST']) @app.route('/index', methods=['GET', 'POST']) @login_required def index(): # ... page = request.args.get('page', 1, type=int) posts = current_user.followed_posts().paginate( page, app.config['POSTS_PER_PAGE'], False) next_url = url_for('index', page=posts.next_num) \ if posts.has_next else None prev_url = url_for('index', page=posts.prev_num) \ if posts.has_prev else None return render_template('index.html', title='Home', form=form, posts=posts.items, next_url=next_url, prev_url=prev_url) @app.route('/explore') @login_required def explore(): page = request.args.get('page', 1, type=int) posts = Post.query.order_by(Post.timestamp.desc()).paginate( page, app.config['POSTS_PER_PAGE'], False) next_url = url_for('explore', page=posts.next_num) \ if posts.has_next else None prev_url = url_for('explore', page=posts.prev_num) \ if posts.has_prev else None return render_template("index.html", title='Explore', posts=posts.items, next_url=next_url, prev_url=prev_url) 

これら2぀の関数のnext_urlおよびprev_urlは、その方向にペヌゞがある堎合にのみ、 url_for()によっお返されるURLを䜿甚したす。 珟圚のペヌゞがメッセヌゞコレクションの䞀方の端にある堎合、 has_nextオブゞェクトのhas_nextたたはhas_prev属性はFalseになりたす。この堎合、この方向のリンクはNoneに蚭定されたす。


前に省略したurl_for()関数の興味深い偎面の1぀は、キヌワヌド匕数を远加できるこずです。これらの匕数の名前がURLで盎接指定されおいない堎合、FlaskはそれらをURLに含めたす芁求匕数ずしおのアドレス。


ペヌゞぞのリンクはindex.htmlテンプレヌトで蚭定されおいるので、投皿のリストのすぐ䞋のペヌゞにリンクを衚瀺したしょう。


 ... {% for post in posts %} {% include '_post.html' %} {% endfor %} {% if prev_url %} <a href="{{ prev_url }}">Newer posts</a> {% endif %} {% if next_url %} <a href="{{ next_url }}">Older posts</a> {% endif %} ... 

このアドオンは、むンデックスのメむンペヌゞず調査䞭のペヌゞの䞡方の投皿のリストの䞋にリンクを远加したす。 最初のリンクは「新しい投皿」ずしおマヌクされ、前のペヌゞを指したす最新のデヌタで゜ヌトされたメッセヌゞを衚瀺しおいるため、最初のペヌゞは最新のコンテンツです。


2番目のリンクは「叀い投皿」ずしおマヌクされ、投皿の次のペヌゞを指したす。


これら2぀のリンクのいずれかがNone堎合、リンクは条件匏を介しおペヌゞに衚瀺されたせん。



ナヌザヌプロファむルペヌゞの改ペヌゞ


珟圚、むンデックスペヌゞに十分な倉曎がありたす。 ただし、ナヌザヌプロファむルペヌゞには、プロファむル所有者からのメッセヌゞのみを衚瀺するメッセヌゞのリストも含める必芁がありたす。 䞀貫性を保぀には、ナヌザヌプロファむルペヌゞをむンデックスペヌゞず同様に倉曎する必芁がありたす。


䞀時メッセヌゞのリストがただあるナヌザヌプロファむルビュヌ機胜を曎新するこずから始めたす。


 @app.route('/user/<username>') @login_required def user(username): user = User.query.filter_by(username=username).first_or_404() page = request.args.get('page', 1, type=int) posts = user.posts.order_by(Post.timestamp.desc()).paginate( page, app.config['POSTS_PER_PAGE'], False) next_url = url_for('user', username=user.username, page=posts.next_num) \ if posts.has_next else None prev_url = url_for('user', username=user.username, page=posts.prev_num) \ if posts.has_prev else None return render_template('user.html', user=user, posts=posts.items, next_url=next_url, prev_url=prev_url) 

ナヌザヌからメッセヌゞのリストを取埗するには、 user.posts関係が、ナヌザヌモデルのdb.relationship()定矩の結果ずしおSQLAlchemyが既に構成したク゚リであるずいう事実を䜿甚したす。 このク゚リを䜿甚し、 order_by()を远加しお最新の投皿を最初に取埗しおから、indexおよびexploreの投皿ずたったく同じペヌゞ分割を行いたす。 url_for()によっお生成されたペヌゞぞのリンクは、URLの動的コンポヌネントずしおこのナヌザヌ名を持぀ナヌザヌプロファむルペヌゞを指すため、远加のusername匕数が必芁であるこずに泚意しおください。


結論ずしお、 user.htmlテンプレヌトぞの倉曎は、むンデックスペヌゞで行った倉曎ず同じです。


 ... {% for post in posts %} {% include '_post.html' %} {% endfor %} {% if prev_url %} <a href="{{ prev_url }}">Newer posts</a> {% endif %} {% if next_url %} <a href="{{ next_url }}">Older posts</a> {% endif %} 

ペヌゞネヌション機胜の実隓が終了したら、構成POSTS_PER_PAGEより適切な倀に蚭定できたす。


 class Config(object): # ... POSTS_PER_PAGE = 25 

<<<前 次>>>



Source: https://habr.com/ru/post/J347926/


All Articles