Flask Mega-Tutorial、パート10:全文検索

これは、Flaskマイクロフレームワークを使用してPython Webアプリケーションを作成した経験を説明するシリーズの10番目の記事です。



このガイドの目的は、かなり機能的なマイクロブログアプリケーションを開発することです。オリジナリティが完全に欠如していることから、 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でのデプロイ





短い繰り返し



前の記事では、ページに投稿を返すようにクエリを改善しました。



今日は、データベースの操作を続けますが、目的は異なります。 コンテンツを保存するすべてのアプリケーションは検索可能でなければなりません。



多くの種類のWebサイトでは、Google、Bingなどを有効にできます。 すべてにインデックスを付け、検索結果を提供します。 これは、フォーラムなどの静的ページに基づくサイトでうまく機能します。 小さなアプリケーションでは、コンテンツの基本単位はページ全体ではなく、短いユーザー投稿です。 より動的な検索結果が必要です。 たとえば、「dog」という単語を検索すると、この単語を含むすべてのユーザーメッセージが表示されます。 明らかに、誰も検索を実行しない限り、検索結果のページは存在しないため、検索エンジンはインデックスを作成できません。



全文検索システムの概要



残念ながら、リレーショナルデータベースでの全文検索のサポートは標準化されていません。 各データベースは独自の方法で全文検索を実装しており、SQLAlchemyにはこの場合に適した抽象化がありません。



現在、データベースにSQLiteを使用しているため、SQLAlchemyをバイパスして、SQLiteが提供する機能を使用してフルテキストインデックスを作成できます。 しかし、これは悪い考えです。ある日、別のデータベースに切り替えることにした場合、別のデータベースの全文検索を書き換える必要があるためです。



代わりに、通常のデータを操作するためのベースを残し、検索用の特別なデータベースを作成します。



オープンソースの全文検索エンジンがいくつかあります。 私が知る限り、Whooshと呼ばれるFlask拡張機能を持っているのは1つだけで、そのエンジンもPythonで書かれています。 純粋なPythonを使用する利点は、Pythonをインストールして、Pythonが利用可能な場所であればどこでも実行できることです。 欠点は検索効率であり、CまたはC ++で記述されたエンジンと比較することはできません。 私の意見では、Flask-SQLAlchemyがさまざまなデータベースのニュアンスから解放するように、異なるシステムに接続し、詳細から私たちを引き離すことができるFlaskの拡張機能を持つのが理想的なソリューションになると思いますが、全文検索の分野ではまだそのようなものはありません。 Django開発者には、django-haystackと呼ばれるさまざまな全文検索エンジンをサポートする非常に素晴らしい拡張機能があります。 たぶん、誰かがFlask用の同様の拡張機能を作成するでしょう。



しかし今、私たちはWhooshで検索を実現しています。 使用する拡張機能はFlask-WhooshAlchemyで、WhooshベースとFlask-SQLAlchemyモデルを組み合わせています。



仮想環境にFlask-WhooshAlchemyがまだない場合は、インストールします。 Windowsユーザーはこれを行う必要があります。



 flask\Scripts\pip install Flask-WhooshAlchemy
      
      







他のすべてがこれを行うことができます:



 flask/bin/pip install Flask-WhooshAlchemy
      
      







構成



Flask-WhooshAlchemyの設定は非常に簡単です。 拡張機能に全文検索ベースの名前( config.py



)を伝えるだけconfig.py







 WHOOSH_BASE = os.path.join(basedir, 'search.db')
      
      







モデルの変更



Flask-WhooshAlchemyはFlask-SQLAlchemyを統合しているため、どのモデルでどのデータにインデックスを付けるかを指定する必要があります(ファイルapp/models.py



):



 from app import app import flask.ext.whooshalchemy as whooshalchemy class Post(db.Model): __searchable__ = ['body'] id = db.Column(db.Integer, primary_key = True) body = db.Column(db.String(140)) timestamp = db.Column(db.DateTime) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __repr__(self): return '<Post %r>' % (self.body) whooshalchemy.whoosh_index(app, Post)
      
      







モデルに新しい__searchable__



フィールドが__searchable__



されました。これは、インデックス内にある必要があるすべての__searchable__



フィールドを含む配列です。 このケースでは、投稿のbodyフィールドのインデックスのみが必要です。



また、 whoosh_index



関数を呼び出して、このモデルのフルテキストインデックスを初期化します。



データベースの形式を変更しなかったため、新しい移行を行う必要はありません。



残念ながら、全文検索エンジンを追加する前にデータベースにあった投稿はすべてインデックスに登録されません。 データベースと検索エンジンが同期していることを確認するには、データベースからすべての投稿を削除して最初からやり直す必要があります。 最初に、Pythonインタープリターを実行します。 Windowsユーザーの場合:



 flask\Scripts\python
      
      





他の皆のために:



 flask/bin/python
      
      





このリクエストでは、すべての投稿を削除します。



 >>> from app.models import Post >>> from app import db >>> for post in Post.query.all(): ... db.session.delete(post) >>> db.session.commit()
      
      







検索する



これで検索の準備ができました。 最初にいくつかの投稿をデータベースに追加しましょう。 これを行うには2つの方法があります。 通常のユーザーとしてアプリケーションを実行し、Webブラウザー経由で投稿を追加するか、インタープリターを使用して投稿を追加できます。



インタープリターを通じて、次のようにこれを行うことができます。



 >>> from app.models import User, Post >>> from app import db >>> import datetime >>> u = User.query.get(1) >>> p = Post(body='my first post', timestamp=datetime.datetime.utcnow(), author=u) >>> db.session.add(p) >>> p = Post(body='my second post', timestamp=datetime.datetime.utcnow(), author=u) >>> db.session.add(p) >>> p = Post(body='my third and last post', timestamp=datetime.datetime.utcnow(), author=u) >>> db.session.add(p) >>> db.session.commit()
      
      





Flask-WhooshAlchemy拡張機能は、Flask-SQLAlchemyに自動的に接続するため、非常に便利です。 全文検索インデックスを維持する必要はありません。すべてが透過的に行われます。



これで、全文検索用にインデックス付けされたいくつかの投稿があり、検索を試すことができます。



 >>> Post.query.whoosh_search('post').all() [<Post u'my second post'>, <Post u'my first post'>, <Post u'my third and last post'>] >>> Post.query.whoosh_search('second').all() [<Post u'my second post'>] >>> Post.query.whoosh_search('second OR last').all() [<Post u'my second post'>, <Post u'my third and last post'>]
      
      





例でわかるように、クエリは単一の単語に限定する必要はありません。 実際、Whooshは優れた検索クエリ言語をサポートしています



全文検索のアプリケーションへの統合



アプリケーションのユーザーが検索にアクセスできるようにするには、いくつかの小さな変更を加える必要があります。



構成



設定では、返す検索結果の数( config.py



)を指定する必要があります。



 MAX_SEARCH_RESULTS = 50
      
      







検索フォーム



ページ上部のナビゲーションバーに検索フォームを追加します。 検索はすべてのページから利用できるため、上部の場所は非常に良好です。



最初に、検索フォームクラス(ファイルapp/forms.py



)を追加する必要があります。



 class SearchForm(Form): search = TextField('search', validators = [Required()])
      
      







次に、検索フォームオブジェクトを作成し、すべてのテンプレートで使用できるようにする必要があります。 すべてのページに共通のナビゲーションバーに配置します。 これを実現する簡単な方法は、 before_request



ハンドラーでフォームを作成し、それをグローバル変数g



(file app/views.py



)に貼り付けることapp/views.py







 from forms import SearchForm @app.before_request def before_request(): g.user = current_user if g.user.is_authenticated(): g.user.last_seen = datetime.utcnow() db.session.add(g.user) db.session.commit() g.search_form = SearchForm()
      
      







次に、テンプレートにフォームを追加します( app/templates/base.html



):



 <div>Microblog: <a href="{{ url_for('index') }}">Home</a> {% if g.user.is_authenticated() %} | <a href="{{ url_for('user', nickname = g.user.nickname) }}">Your Profile</a> | <form style="display: inline;" action="{{url_for('search')}}" method="post" name="search">{{g.search_form.hidden_tag()}}{{g.search_form.search(size=20)}}<input type="submit" value="Search"></form> | <a href="{{ url_for('logout') }}">Logout</a> {% endif %} </div>
      
      







ユーザーがログインしているときのみ検索フォームを表示することに注意してください。 同様に、 before_request



ハンドラーは、ユーザーがログインしたときにのみフォームを作成します。これは、アプリケーションが許可されていないゲストにコンテンツを表示しないためです。



表示する 検索機能



フォームのaction



フィールドは、ビューのsearch



機能にすべてのリクエストを送信するために上記で設定されました。 これは、フルテキストクエリ( app/views.py



)を実行するapp/views.py







 @app.route('/search', methods = ['POST']) @login_required def search(): if not g.search_form.validate_on_submit(): return redirect(url_for('index')) return redirect(url_for('search_results', query = g.search_form.search.data))
      
      







この関数は実際にはそれほど大きくありません。フォームからリクエストを収集し、リクエストを引数として受け取る別のページにリダイレクトするだけです。 ユーザーがページを更新しようとした場合、ユーザーのブラウザがフォームの再送信に関する警告を表示しないように、この関数では直接検索しません。 この状況は、POSTリクエストにリダイレクトすることで回避できます。その後、ページが更新されると、ブラウザはリクエスト自体ではなく、リダイレクトが行われたページを更新します。



結果ページ



フォームからリクエスト文字列が送信されると、POSTハンドラーはリダイレクトを介してsearch_results



ハンドラー(ファイルapp/views.py



)にapp/views.py



ます。



 from config import MAX_SEARCH_RESULTS @app.route('/search_results/<query>') @login_required def search_results(query): results = Post.query.whoosh_search(query, MAX_SEARCH_RESULTS).all() return render_template('search_results.html', query = query, results = results)
      
      







search_result



関数はクエリをWhooshに送信し、クエリとともに結果数の制限を渡し、潜在的に多数の検索結果から保護します。



検索はsearch_resultテンプレート( app/templates/search_results.html



)でapp/templates/search_results.html







 <!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>Search results for "{{query}}":</h1> {% for post in results %} {% include 'post.html' %} {% endfor %} {% endblock %}
      
      







そして、ここでpost.html



を再利用できます。



最後の言葉



見過ごされがちですが、まともなWebアプリケーションに必要な別の非常に重要な機能を完成させました。



以下に、この記事で行ったすべての変更を含むマイクロブログアプリケーションの更新バージョンを投稿します。



microblog-0.10.zipをダウンロードします。



いつものように、データベースはありません。自分で作成する必要があります。 この一連の記事に従うと、その方法がわかります。 そうでない場合は、データベースの記事に戻って調べてください。



このチュートリアルをお楽しみください。



ミゲル



All Articles