ããã¯ãFlaskãã€ã¯ããã¬ãŒã ã¯ãŒã¯ã䜿çšããŠPython Webã¢ããªã±ãŒã·ã§ã³ãäœæããçµéšã説æããã·ãªãŒãºã®10çªç®ã®èšäºã§ãã
ãã®ã¬ã€ãã®ç®çã¯ãããªãæ©èœçãªãã€ã¯ãããã°ã¢ããªã±ãŒã·ã§ã³ãéçºããããšã§ãããªãªãžããªãã£ãå®å
šã«æ¬ åŠããŠããããšããã
microblog
ãšåŒã¶ããšã«ããŸããã
çãç¹°ãè¿ã
åã®èšäºã§ã¯ãããŒãžã«æçš¿ãè¿ãããã«ã¯ãšãªãæ¹åããŸããã
ä»æ¥ã¯ãããŒã¿ããŒã¹ã®æäœãç¶ããŸãããç®çã¯ç°ãªããŸãã ã³ã³ãã³ããä¿åãããã¹ãŠã®ã¢ããªã±ãŒã·ã§ã³ã¯æ€çŽ¢å¯èœã§ãªããã°ãªããŸããã
å€ãã®çš®é¡ã®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
ïŒ
{% 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ãããŠã³ããŒãããŸãã
ãã€ãã®ããã«ãããŒã¿ããŒã¹ã¯ãããŸãããèªåã§äœæããå¿
èŠããããŸãã ãã®äžé£ã®èšäºã«åŸããšããã®æ¹æ³ãããããŸãã ããã§ãªãå Žåã¯ãããŒã¿ããŒã¹ã®èšäºã«æ»ã£ãŠèª¿ã¹ãŠãã ããã
ãã®ãã¥ãŒããªã¢ã«ãã楜ãã¿ãã ããã
ãã²ã«