Flaskã¯åªããPythonããŒã¹ã®ãã€ã¯ãWebãã¬ãŒã ã¯ãŒã¯ã§ãã Flaskrã¯ã å
¬åŒã® Flask ã¬ã€ãã§èª¬æãããŠããããããã°ã§ãã ç§ã¯ãããèªããããšãã§ãã以äžããã®ã¬ã€ããäœåºŠãçµéšããŸããã ãã ãããã®ã¬ã€ãã次ã®ã¹ãããã«äœ¿çšãã ãã¹ãã«ããéçº ïŒ ãã¹ãé§åéçº ïŒãšå°ãã®jQueryãè¿œå ããŸãã
FlaskãWebéçºå
šè¬ã«äžæ
£ããªå Žåã¯ã次ã®åºæ¬æŠå¿µãç解ããããšãéèŠã§ãã
- Getãªã¯ãšã¹ããšPostãªã¯ãšã¹ãã®éããããã³ããããåŠçããã¢ããªã±ãŒã·ã§ã³ã®æ©èœã
- ãªã¯ãšã¹ããšã¯äœã§ããïŒ
- ãã©ãŠã¶ã§HTMLããŒãžã衚瀺ããŠèšªåè
ã«è¿ãæ¹æ³ã
**泚**ïŒãã®ã¬ã€ãã¯https://realpython.comãããžã§ã¯ãã«ãã£ãŠæäŸãããŸãã http://www.realpython.com/coursesã³ãŒã¹ã§Pythonãã¬ãŒãã³ã°ãšDjangoãšFlaskã䜿çšããWebéçºã賌å
¥ããŠããã®ãªãŒãã³ãœãŒã¹ãããžã§ã¯ãããµããŒãããŠãã ããã
å
容
- ãã¹ãã«ããéçºïŒ
- PythonãããŠã³ããŒã
- ãããžã§ã¯ãã®ã€ã³ã¹ããŒã«
- æåã®ãã¹ã
- Flaskrãã€ã³ã¹ããŒã«ãã
- äºæ¬¡è©Šéš
- ããŒã¿ããŒã¹ã®ã€ã³ã¹ããŒã«
- ãã³ãã¬ãŒããšãã¥ãŒ
- è²ãè¿œå ãã
- ãã¹ã
- jQuery
- å±é
- å¥ã®ãã¹ãïŒ
- ããŒãã¹ãã©ãã
- SQLAlchemy
- ãããã«
å¿
èŠæ¡ä»¶
ãã®ããã¥ã¢ã«ã§ã¯ã次ã®ãœãããŠã§ã¢ã䜿çšããŠããŸãã
- Python v3.5.1
- ãã©ã¹ã³v0.10.1
- Flask-SQLAlchemy v2.1
- gunicorn v19.4.5
ãã¹ãã«ããéçºïŒ
ãã¹ãã«ããéçºïŒtddïŒã¯ãé¢æ°èªäœãäœæããåã«èªåãã¹ããäœæããéçºã®äžçš®ã§ãã ã€ãŸãããã¹ããšã³ãŒãèšè¿°ã®çµã¿åããã§ãã ãã®ããã»ã¹ã¯ãã³ãŒãã®æ£ç¢ºæ§ã確ä¿ããã®ã«åœ¹ç«ã€ã ãã§ãªãããããžã§ã¯ãã®èšèšãšã¢ãŒããã¯ãã£ã絶ããå¶åŸ¡ããªããéçºããããšãã§ããŸãã
TDDã¯éåžžãäžã®å³ã«ç€ºãããã«ãRed-Green-Refactoringã¹ããŒã ã«åŸããŸãã
- æžã蟌ã¿ãã¹ã
- ãã¹ããå®è¡ããŸãïŒãããŠå€±æããŸãïŒ
- ãã¹ãã«åæ Œããããã®ã³ãŒããæžã
- ã³ãŒãã®ãªãã¡ã¯ã¿ãªã³ã°ãšåãã¹ãïŒäœåºŠãïŒ
PythonãããŠã³ããŒã
éå§ããåã«ã Python 3.5ã®ææ°ããŒãžã§ã³ãã€ã³ã¹ããŒã«ãããŠããããšã確èªããŠãã ãããããã¯http://www.python.org/download/ããããŠã³ããŒãã§ããŸãã
泚 ïŒãã®ã¬ã€ãã§ã¯Python V 3.5.1ã䜿çšããŠããŸãã
Pythonãšäžç·ã«ã以äžãé
眮ããå¿
èŠããããŸãã
- pipã¯ãRubyãŸãã¯Nodeã®gemãŸãã¯npmã«ãããã䌌ãPythonã®ããã±ãŒãžç®¡çã·ã¹ãã ã§ãã
- pyvenv-éçºç°å¢ã§éé¢ãããç°å¢ãäœæããããã«äœ¿çšãããŸãã ããã¯æšæºçãªæ¹æ³ã§ãã åžžã«ããããŠäœåºŠããåžžã«ä»®æ³ç°å¢ã䜿çšããŸãã ããããªããšãããŸããŸãªäŸåé¢ä¿éã®äºææ§ã®åé¡ãçºçããŸãã
ãããžã§ã¯ãã®ã€ã³ã¹ããŒã«
ãããžã§ã¯ããä¿åããæ°ãããã©ã«ããŒãäœæããŸãã
$ mkdir flaskr-tdd $ cd flaskr-tdd
ä»®æ³ç°å¢ãäœæããŠã¢ã¯ãã£ãåããŸãã
$ pyvenv-3.5 env $ source env/bin/activate
泚 ïŒ
ä»®æ³ç°å¢å
ã«ããå Žåã端æ«ã¯$ã¢ã€ã³ã³ã®åã«ç¢æïŒenvïŒã衚瀺ããŸãã ä»®æ³ç°å¢ãçµäºããã«ã¯ã deactivate
ã³ãã³ãã䜿çšããå¿
èŠã«å¿ããŠã¢ã¯ãã£ãåããç®çã®ãã£ã¬ã¯ããªã«ç§»åããŠã³ãã³ãsource env/bin/activate
ãŸãã
pipã䜿çšããFlaskã®ã€ã³ã¹ããŒã«ïŒ
$ pip3 install Flask
æåã®ãã¹ã
ç°¡åãªHello Worldããã°ã©ã ããå§ããŸãããã
ãã¹ããã¡ã€ã«ãäœæããŸãã
$ touch app-test.py
ãæ°ã«å
¥ãã®ããã¹ããšãã£ã¿ã§ãã®ãã¡ã€ã«ãéããŸãã ãããŠã次ã®è¡ãapp-test.pyãã¡ã€ã«ã«è¿œå ããŸãã
from app import app import unittest class BasicTestCase(unittest.TestCase): def test_index(self): tester = app.test_client(self) response = tester.get('/', content_type='html/text') self.assertEqual(response.status_code, 200) self.assertEqual(response.data, b'Hello, World!') if __name__ == '__main__': unittest.main()
å®éãã200ããšããã³ãŒãã®åçãå±ããã©ããããhelloãworldãã衚瀺ããããã©ããã確èªããŸãã
ãã¹ããå®è¡ããŸãã
$ python app-test.py
ãã¹ãŠãé 調ã§ããã°ããã¹ãã¯å€±æããŸãã
ããã§ã次ã®è¡ãapp.pyãã¡ã€ã«ã«è¿œå ããŠããã¹ãã«åæ ŒããŸãã
$ touch app.py
ã³ãŒãïŒ
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello, World!" if __name__ == "__main__": app.run()
ã¢ããªãå®è¡ããŸãã
$ python app.py
httpïŒ// localhostïŒ5000 /ã§å¯ŸåŠããŸã ã ãHelloãWorldïŒããšããè¡ã衚瀺ãããŸãã ç»é¢ã«ã
ã¿ãŒããã«ã«æ»ããCtrl + Cã䜿çšããŠéçºãµãŒããŒãåæ¢ããŸãããã
ãã¹ããå床å®è¡ããŸãã
$ python app-test.py . ---------------------------------------------------------------------- Ran 1 test in 0.016s OK
çŽ æŽããããããªããå¿
èŠãªãã®ã
Flaskrãã€ã³ã¹ããŒã«ãã
æ§é ãè¿œå
ãããžã§ã¯ãã®ã«ãŒãã«ãéçããšããã³ãã¬ãŒããã®ããã€ãã®ãã©ã«ããŒãè¿œå ããŸãã 次ã®æ§é ãååŸããå¿
èŠããããŸãã
âââ app-test.py âââ app.py âââ static âââ templates
SQLã¹ããŒã
ãschema.sqlããšããååã®æ°ãããã¡ã€ã«ãäœæãã次ã®ã³ãŒããè¿œå ããŸãã
drop table if exists entries; create table entries ( id integer primary key autoincrement, title text not null, text text not null );
ããã«ããããidãããtitleãããtextãã®3ã€ã®ãã£ãŒã«ããæã€ããŒãã«ãäœæãããŸãã SQLiteã¯æšæºã®Pythonã©ã€ãã©ãªã«çµã¿èŸŒãŸããæ§æãå¿
èŠãšããªããããSQLiteã¯DBMSã«äœ¿çšãããŸãã
äºæ¬¡è©Šéš
ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããããŒã¹ãã¡ã€ã«ãäœæããŸãããã ãã ããæåã«ãã¹ããäœæããå¿
èŠããããŸãã
æåã®ãã¹ãããapp-test.pyãå€æŽããã ãã§ãïŒ
from app import app import unittest class BasicTestCase(unittest.TestCase): def test_index(self): tester = app.test_client(self) response = tester.get('/', content_type='html/text') self.assertEqual(response.status_code, 404) if __name__ == '__main__': unittest.main()
ãããã£ãŠã404ïŒãšã©ãŒïŒã³ãŒããååŸããäºå®ã§ãã ãã¹ããå®è¡ããŸãã ãã¹ãã¯å€±æããŸããã ãã¹ãã倱æããã®ã¯ãªãã§ããïŒ ãã¹ãŠãã·ã³ãã«ã§ãã 404ãæåŸ
ããŠããŸããããå®éã«ã¯ãã®ã«ãŒããã200ã³ãŒããååŸããŸãã
app.pyãå€æŽããŸã ã
ããã§ãå¿
èŠãªã¢ãžã¥ãŒã«ãã€ã³ããŒãããã°ããŒãã«å€æ°ã®æ§æã»ã¯ã·ã§ã³ãäœæããã¢ããªã±ãŒã·ã§ã³ãåæåããŠããèµ·åããŸãã
ã ããããããå®è¡ããŸãïŒ
$ python app.py
ãµãŒããŒãèµ·åããŸãã ã«ãŒãããªãããã®è¡šçŸãååšããªããããã/ãã«ãŒãã«ã¢ã¯ã»ã¹ãããšãã«404ãšã©ãŒã¡ãã»ãŒãžã衚瀺ãããã¯ãã§ãã ã¿ãŒããã«ã«æ»ããŸãããã éçºãµãŒããŒãåæ¢ããŸãã 次ã«ãåäœãã¹ããå®è¡ããŸãã ãšã©ãŒãªãã§åæ Œããã¯ãã§ãã
ããŒã¿ããŒã¹ã®ã€ã³ã¹ããŒã«
ç§ãã¡ã®ç®æšã¯ãããŒã¿ããŒã¹ãžã®æ¥ç¶ãäœæããã¹ããŒã ã«åºã¥ããŠããŒã¿ããŒã¹ãäœæãããŸã ååšããªãå Žåã¯ããã¹ããå®è¡ãããã³ã«æ¥ç¶ãéããããšã§ãã
ããŒã¿ããŒã¹ãã¡ã€ã«ã®ååšã確èªããã«ã¯ã©ãããã°ããã§ããïŒ app-test.pyãæŽæ°ããŸãã
import unittest import os from app import app class BasicTestCase(unittest.TestCase): def test_index(self): tester = app.test_client(self) response = tester.get('/', content_type='html/text') self.assertEqual(response.status_code, 404) def test_database(self): tester = os.path.exists("flaskr.db") self.assertTrue(tester) if __name__ == '__main__': unittest.main()
ãããå®è¡ããŠããã¹ãã倱æããããŒã¿ããŒã¹ãååšããªãããšã確èªããŸãã
次ã«ã app.pyã«æ¬¡ã®ã³ãŒããè¿œå ããŸãã
ãããŠã init_db ()
é¢æ°ãapp.py
ã®æåŸã«è¿œå ããŠã init_db ()
æ°ããããŒã¿ããŒã¹ã§ãµãŒããŒãèµ·åããããã«ããŸãïŒ
if __name__ == '__main__': init_db() app.run()
ããã§ãPython Shellã䜿çšããŠããŒã¿ããŒã¹ãäœæãã init_db()
ãã€ã³ããŒãããŠåŒã³åºãããšãã§ããŸãã
>>> from app import init_db >>> init_db()
ã·ã§ã«ãéããŠããã¹ããå床å®è¡ããŸãã ãã¹ãã«åæ ŒããŸãããïŒ ããã§ãããŒã¿ããŒã¹ãäœæããããšç¢ºä¿¡ã§ããŸããã
ãã³ãã¬ãŒããšãã¥ãŒ
次ã«ãã«ãŒããå®çŸ©ãããã³ãã¬ãŒããšå¯Ÿå¿ãããã¥ãŒãæ§æããå¿
èŠããããŸãã ãŠãŒã¶ãŒã®èŠ³ç¹ããèããŠãã ããã ãŠãŒã¶ãŒãããã°ã«åºå
¥ãã§ããããã«ããå¿
èŠããããŸãã ãã°ã€ã³åŸããŠãŒã¶ãŒã¯æçš¿ãäœæã§ããã¯ãã§ãã ãããŠæåŸã«ããããã®æçš¿ã衚瀺ã§ããã¯ãã§ãã
ãŸã第äžã«ãããã«ã€ããŠããã€ãã®ãã¹ããæžããŸãã
åäœãã¹ã
æçµçãªã³ãŒããèŠãŠãã ããã 説æã®ããã«ã³ã¡ã³ããè¿œå ããŸããã
import unittest import os import tempfile import app class BasicTestCase(unittest.TestCase): def test_index(self): """ . , """ tester = app.app.test_client(self) response = tester.get('/', content_type='html/text') self.assertEqual(response.status_code, 200) def test_database(self): """ , , """ tester = os.path.exists("flaskr.db") self.assertEqual(tester, True) class FlaskrTestCase(unittest.TestCase): def setUp(self): """ """ self.db_fd, app.app.config['DATABASE'] = tempfile.mkstemp() app.app.config['TESTING'] = True self.app = app.app.test_client() app.init_db() def tearDown(self): """ """ os.close(self.db_fd) os.unlink(app.app.config['DATABASE']) def login(self, username, password): """ """ return self.app.post('/login', data=dict( username=username, password=password ), follow_redirects=True) def logout(self): """ """ return self.app.get('/logout', follow_redirects=True)
ããã§ãã¹ããå®è¡ãããšãtest_databaseïŒïŒ `ïŒãé€ããã¹ãŠãã¯ã©ãã·ã¥ããŸãã
python app-test.py .FFFF ====================================================================== FAIL: test_index (__main__.BasicTestCase) initial test. ensure flask was set up correctly ---------------------------------------------------------------------- Traceback (most recent call last): File "app-test.py", line 13, in test_index self.assertEqual(response.status_code, 200) AssertionError: 404 != 200 ====================================================================== FAIL: test_empty_db (__main__.FlaskrTestCase) Ensure database is blank ---------------------------------------------------------------------- Traceback (most recent call last): File "app-test.py", line 51, in test_empty_db assert b'No entries here so far' in rv.data AssertionError ====================================================================== FAIL: test_login_logout (__main__.FlaskrTestCase) Test login and logout using helper functions ---------------------------------------------------------------------- Traceback (most recent call last): File "app-test.py", line 59, in test_login_logout assert b'You were logged in' in rv.data AssertionError ====================================================================== FAIL: test_messages (__main__.FlaskrTestCase) Ensure that user can post messages ---------------------------------------------------------------------- Traceback (most recent call last): File "app-test.py", line 84, in test_messages assert b'<Hello>' in rv.data AssertionError ---------------------------------------------------------------------- Ran 5 tests in 0.088s FAILED (failures=4)
ãã¹ãããã¹ãããŸããã...
ã¬ã³ãŒãã衚瀺
æåã«ã app.pyã®ãšã³ããªã衚瀺ãããã¥ãŒãè¿œå ããŸãã
@app.route('/') def show_entries(): """Searches the database for entries, then displays them.""" db = get_db() cur = db.execute('select * from entries order by id desc') entries = cur.fetchall() return render_template('index.html', entries=entries)
次ã«ãããã³ãã¬ãŒãããã©ã«ããŒã«ç§»åããŠããã®ã³ã³ãã³ãã®index.htmlãã¡ã€ã«ãè¿œå ããŸãã
<!DOCTYPE html> <html> <head> <title>Flaskr</title> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class="page"> <h1>Flaskr-TDD</h1> <div class="metanav"> {% if not session.logged_in %} <a href="{{ url_for('login') }}">log in</a> {% else %} <a href="{{ url_for('logout') }}">log out</a> {% endif %} </div> {% for message in get_flashed_messages() %} <div class="flash">{{ message }}</div> {% endfor %} {% block body %}{% endblock %} {% if session.logged_in %} <form action="{{ url_for('add_entry') }}" method="post" class="add-entry"> <dl> <dt>Title:</dt> <dd><input type="text" size="30" name="title"></dd> <dt>Text:</dt> <dd><textarea name="text" rows="5" cols="40"></textarea></dd> <dd><input type="submit" value="Share"></dd> </dl> </form> {% endif %} <ul class="entries"> {% for entry in entries %} <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li> {% else %} <li><em>No entries yet. Add some!</em></li> {% endfor %} </ul> </div> </body> </html>
ãã¹ããå®è¡ããŸãã 以äžã衚瀺ãããã¯ãã§ãã
Ran 5 tests in 0.131s FAILED (failures=2, errors=2)
ãŠãŒã¶ãŒèªèšŒ
app.pyãã¡ã€ã«ã«è¿œå ããŸãã
@app.route('/login', methods=['GET', 'POST']) def login(): """User login/authentication/session management.""" error = None if request.method == 'POST': if request.form['username'] != app.config['USERNAME']: error = 'Invalid username' elif request.form['password'] != app.config['PASSWORD']: error = 'Invalid password' else: session['logged_in'] = True flash('You were logged in') return redirect(url_for('index')) return render_template('login.html', error=error) @app.route('/logout') def logout(): """User logout/authentication/session management.""" session.pop('logged_in', None) flash('You were logged out') return redirect(url_for('index'))
äžèšã®login()
é¢æ°ã«ã¯ãã«ãŒããGetãŸãã¯Postãªã¯ãšã¹ããåãå
¥ããããšãã§ããããšã瀺ããã³ã¬ãŒã¿ãŒããããŸãã ç°¡åã«èšãã°ãurl /login
ã«ã¢ã¯ã»ã¹ãããšããŠãŒã¶ãŒããèªèšŒãªã¯ãšã¹ããå§ãŸã/login
ã ãããã®ã¿ã€ãã®ãªã¯ãšã¹ãã®éãã¯ç°¡åã§ããGetã¯ãµã€ããžã®ã¢ã¯ã»ã¹ã«äœ¿çšãããPOSTã¯ãµãŒããŒãžã®æ
å ±ã®éä¿¡ã«äœ¿çšãããŸãã ãããã£ãŠããŠãŒã¶ãŒãåã«/login
ã«ã¢ã¯ã»ã¹ãããšãã¯ãGetãªã¯ãšã¹ãã䜿çšããŸããããã°ã€ã³ããããšãããšãPostãªã¯ãšã¹ãã䜿çšãããŸãã
ãã¡ã€ã«ãlogin.htmlãããã³ãã¬ãŒããã©ã«ããŒã«è¿œå ããŸãã
<!DOCTYPE html> <html> <head> <title>Flaskr-TDD | Login</title> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class="page"> <h1>Flaskr</h1> <div class="metanav"> {% if not session.logged_in %} <a href="{{ url_for('login') }}">log in</a> {% else %} <a href="{{ url_for('logout') }}">log out</a> {% endif %} </div> {% for message in get_flashed_messages() %} <div class="flash">{{ message }}</div> {% endfor %} {% block body %}{% endblock %} <h2>Login</h2> {% if error %} <p class="error"><strong>Error:</strong> {{ error }}</p> {% endif %} <form action="{{ url_for('login') }}" method="post"> <dl> <dt>Username:</dt> <dd><input type="text" name="username"></dd> <dt>Password:</dt> <dd><input type="password" name="password"></dd> <dd><input type="submit" value="Login"></dd> </dl> </form> </div> </body> </html>
ãã¹ããå床å®è¡ããŸãã
ããã§ãããã€ãã®ãšã©ãŒã衚瀺ãããã¯ãã§ãïŒ ãšã©ãŒã®1ã€ãèæ
®ããŠãã ããwerkzeug.routing.BuildError: Could not build url for endpoint 'index'. Did you mean 'login' instead?
werkzeug.routing.BuildError: Could not build url for endpoint 'index'. Did you mean 'login' instead?
åºæ¬çã«ãååšããªãindex()
é¢æ°ã«ã¢ã¯ã»ã¹ããããšããŠããŸãã show_entries()
é¢æ°ã®ååãapp.pyãã¡ã€ã«ã®index()
é¢æ°ã«å€æŽãããã¹ããå床å®è¡ããŸãã
Ran 5 tests in 0.070s FAILED (failures=1, errors=2)
次ã«ããã¥ãŒã«ã¬ã³ãŒããè¿œå ããé¢æ°ãè¿œå ããŸãã
@app.route('/add', methods=['POST']) def add_entry(): """Add new post to database.""" if not session.get('logged_in'): abort(401) db = get_db() db.execute( 'insert into entries (title, text) values (?, ?)', [request.form['title'], request.form['text']] ) db.commit() flash('New entry was successfully posted') return redirect(url_for('index'))
ãã¹ããç¹°ãè¿ããŸãïŒ
ããã衚瀺ãããã¯ãã§ãã
====================================================================== FAIL: test_empty_db (__main__.FlaskrTestCase) Ensure database is blank ---------------------------------------------------------------------- Traceback (most recent call last): File "app-test.py", line 49, in test_empty_db assert b'No entries here so far' in rv.data AssertionError ---------------------------------------------------------------------- Ran 5 tests in 0.072s FAILED (failures=1)
ãã®ãšã©ãŒã¯ãã«ãŒãã«ã¢ã¯ã»ã¹ãããšãããããŸã§ãšã³ããªããããŸããã /
ã¡ãã»ãŒãžãè¿ããããšäž»åŒµããŠ/
ãŸãã index.htmlãã³ãã¬ãŒãã確èªããŠãã ããã ããã¹ãã«ã¯ãããšã³ããªã¯ãŸã ãããŸãããè¿œå ããŠãã ããïŒããšæžãããŠããŸãã ãã®ããããã¹ããæŽæ°ããŠããã¹ããå床å®è¡ããŸãã
Ran 5 tests in 0.156s OK
çŽ æŽãããã
è²ãè¿œå ãã
ãéçããã©ã«ããŒã®style.cssãšããååã®æ°ãããã¡ã€ã«ã«æ¬¡ã®ã¹ã¿ã€ã«ãä¿åããŸãã
body { font-family: sans-serif; background: #eee; } a, h1, h2 { color: #377BA8; } h1, h2 { font-family: 'Georgia', serif; margin: 0; } h1 { border-bottom: 2px solid #eee; } h2 { font-size: 1.2em; } .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; padding: 0.8em; background: white; } .entries { list-style: none; margin: 0; padding: 0; } .entries li { margin: 0.8em 1.2em; } .entries li h2 { margin-left: -1em; } .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } .add-entry dl { font-weight: bold; } .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; margin-bottom: 1em; background: #fafafa; } .flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; } .error { background: #F0D6D6; padding: 0.5em; }
ãã¹ã
ã¢ããªã±ãŒã·ã§ã³ãèµ·åãããã°ã€ã³ïŒlogin / password = "admin"ïŒããæçš¿ãäœæããŠãããã°ãçµäºããŸãã 次ã«ããã¹ããå®è¡ããŠããã¹ãŠãåŒãç¶ãæ©èœããããšã確èªããŸãã
jQuery
ããã§jQueryãè¿œå ããŠããµã€ããããå°ãã€ã³ã¿ã©ã¯ãã£ãã«ããŸãã
index.htmlãéããæåã®<li
>ã次ã®ããã«å€æŽããŸãã
<li class="entry"><h2 id={{ entry.id }}>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
ããã§ãå<li>
jQueryã䜿çšã§ããŸãã æåã«ã次ã®ã¹ã¯ãªãããããã¥ã¡ã³ãã®çµäºBodyã¿ã°ã®çŽåã«è¿œå ããå¿
èŠããããŸãã
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> <script type="text/javascript" src="{{url_for('static', filename='main.js') }}"></script>
ãéçããã£ã¬ã¯ããªã«main.jsãã¡ã€ã«ãäœæãã次ã®ã³ãŒããæžã蟌ã¿ãŸãã
$(function() { console.log( "ready!" );
ããŒã¿ããŒã¹ããã¡ãã»ãŒãžãåé€ã§ããããã«ã app.pyã«æ°ããé¢æ°ãè¿œå ããŸãã
@app.route('/delete/<post_id>', methods=['GET']) def delete_entry(post_id): '''Delete post from database''' result = {'status': 0, 'message': 'Error'} try: db = get_db() db.execute('delete from entries where id=' + post_id) db.commit() result = {'status': 1, 'message': "Post Deleted"} except Exception as e: result = {'status': 0, 'message': repr(e)} return jsonify(result)
æåŸã«ãæ°ãããã¹ããäœæããŸãã
def test_delete_message(self): """Ensure the messages are being deleted""" rv = self.app.get('/delete/1') data = json.loads((rv.data).decode('utf-8')) self.assertEqual(data['status'], 1)
import json
ãå¿
ãè¿œå ããŠãã ãã
ãµãŒããŒãèµ·åãã2ã€ã®æ°ãããšã³ããªãè¿œå ããŠããããæåã§ç¢ºèªããŸãã ãããã®ãããããã¯ãªãã¯ããŸãã ã¬ã³ãŒãã¯ãdomãšããŒã¿ããŒã¹ããåé€ããå¿
èŠããããŸãã ãããå確èªããŠãã ããã
次ã«ããã¹ããå®è¡ããŸãã ãã¹ãçµæã¯æ¬¡ã®ããã«ãªããŸãã
$ python app-test.py ...... ---------------------------------------------------------------------- Ran 6 tests in 0.132s OK
å±é
ã¢ããªã±ãŒã·ã§ã³ã¯åäœããŠããŸããããã§åæ¢ããŠãã¢ããªã±ãŒã·ã§ã³ãHerokuã«ãããã€ããªãã§ãã ããã
ãããè¡ãã«ã¯ãæåã«ç»é²ããŠããHeroku Toolbeltãã€ã³ã¹ããŒã«ããŸãã
次ã«ã gunicornãšããWebãµãŒããŒãã€ã³ã¹ããŒã«ããŸãã
$ pip install gunicorn
ãããžã§ã¯ãã®ã«ãŒãã«ProcfileãäœæããŸãã
$ touch Procfile
次ã®ã³ãŒããè¿œå ããŸãã
web: gunicorn app:app
requirements.txtãã¡ã€ã«ãäœæããŠãã¢ããªã±ãŒã·ã§ã³ãæ©èœããããã«ã€ã³ã¹ããŒã«ããå¿
èŠãããå€éšäŸåé¢ä¿ãæå®ããŸãã
$ pip freeze > requirements.txt
.gitignoreãã¡ã€ã«ãäœæããŸãã
$ touch .gitignore
ããŒãžã§ã³ç®¡çã·ã¹ãã ã«å«ããã¹ãã§ã¯ãªããã¡ã€ã«ãšãã©ã«ããŒãè¿œå ããŸãã
env *.pyc *.DS_Store __pycache__
ããŒã«ã«ãªããžããªãè¿œå ããŸãã
$ git init $ git add -A $ git commit -m "initial"
Herokuãå±éããŸãã
$ heroku create $ git push heroku master $ heroku open
ãã¹ãïŒããäžåºŠïŒïŒ
ã¯ã©ãŠãã§ãã¹ããå®è¡ããŸãã heroku open
ã³ãã³ãã¯ããã©ãŠã¶ã§ã¢ããªã±ãŒã·ã§ã³ãéããŸãã
ããŒãã¹ãã©ãã
Bootstrap 3ããã¹ã¿ã€ã«ãæŽæ°ããŸãããã
index.htmlããã³login.htmlã® style.cssãšãã®ãªã³ã¯ãåé€ãããã®ã¹ã¿ã€ã«ãäž¡æ¹ã®ãã¡ã€ã«ã«è¿œå ããŸã
<link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
ããã§ããã¹ãŠã®Bootstrapãã«ããŒã¯ã©ã¹ã«å®å
šã«ã¢ã¯ã»ã¹ã§ããŸãã
login.htmlãã¡ã€ã«ã®ã³ãŒãã次ã®ããã«çœ®ãæããŸãã
<!DOCTYPE html> <html> <head> <title>Flaskr-TDD | Login</title> <link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>Flaskr</h1> {% for message in get_flashed_messages() %} <div class="flash">{{ message }}</div> {% endfor %} <h3>Login</h3> {% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %}</p> <form action="{{ url_for('login') }}" method="post"> <dl> <dt>Username:</dt> <dd><input type="text" name="username"></dd> <dt>Password:</dt> <dd><input type="password" name="password"></dd> <br><br> <dd><input type="submit" class="btn btn-default" value="Login"></dd> <span>Use "admin" for username and password</span> </dl> </form> </div> <script src="//code.jquery.com/jquery-1.10.2.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> <script type="text/javascript" src="{{url_for('static', filename='main.js') }}"></script> </body> </html>
ãããŠindex.htmlã®ã³ãŒããå€æŽããŸãïŒ
<!DOCTYPE html> <html> <head> <title>Flaskr</title> <link rel="stylesheet" type="text/css" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>Flaskr-TDD</h1> {% if not session.logged_in %} <a href="{{ url_for('login') }}">log in</a> {% else %} <a href="{{ url_for('logout') }}">log out</a> {% endif %} {% for message in get_flashed_messages() %} <div class="flash">{{ message }}</div> {% endfor %} {% if session.logged_in %} <form action="{{ url_for('add_entry') }}" method="post" class="add-entry"> <dl> <dt>Title:</dt> <dd><input type="text" size="30" name="title"></dd> <dt>Text:</dt> <dd><textarea name="text" rows="5" cols="40"></textarea></dd> <br><br> <dd><input type="submit" class="btn btn-default" value="Share"></dd> </dl> </form> {% endif %} <br> <ul class="entries"> {% for entry in entries %} <li class="entry"><h2 id={{ entry.id }}>{{ entry.title }}</h2>{{ entry.text|safe }}</li> {% else %} <li><em>No entries yet. Add some!</em></li> {% endfor %} </ul> </div> <script src="//code.jquery.com/jquery-1.10.2.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> <script type="text/javascript" src="{{url_for('static', filename='main.js') }}"></script> </body> </html>
å€æŽã確èªããŠãã ããïŒ
SQLAlchemy
ããŒã¿ããŒã¹ã®ç®¡çãæ¹åããããã«ã Flask-SQLAlchemyãè©ŠããŠã¿ãŸãããã
SQLAlchemyãã€ã³ã¹ããŒã«ãã
Flask-SQLAlchemyã€ã³ã¹ããŒã«ãå®è¡ããŸãã
$ pip install Flask-SQLAlchemy
create_db.pyãã¡ã€ã«ãäœæãã次ã®ã³ãŒããããã«è²Œãä»ããŸãã
ãã®ãã¡ã€ã«ã¯ãæ°ããããŒã¿ããŒã¹ãäœæããããã«äœ¿çšãããŸãã ããã«ãå€ãïŒ flaskr.db ïŒããã³schema.sqlãåé€ããŸã
次ã«ãæ°ããã¹ããŒããçæããæ°ããmodels.pyãã¡ã€ã«ã«æ¬¡ã®ã³ã³ãã³ããè¿œå ããŸãã
from app import db class Flaskr(db.Model): __tablename__ = "flaskr" post_id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String, nullable=False) text = db.Column(db.String, nullable=False) def __init__(self, title, text): self.title = title self.text = text def __repr__(self): return '<title {}>'.format(self.body)
app.pyãæŽæ°ãã
äžéšã®èšå®ã®å€æŽãšãåãã¥ãŒé¢æ°ã§ããŒã¿ããŒã¹ã«ã¢ã¯ã»ã¹ããŠç®¡çããæ段ã«æ³šæããŠãã ãã-åã蟌ã¿SQLã§ã¯ãªãSQLAlchemyã䜿çšããŠã
ããŒã¿ããŒã¹ãäœæãã
ã³ãã³ããå®è¡ããŠãããŒã¿ããŒã¹ãäœæããã³åæåããŸãã
$ python create_db.py
index.htmlãæŽæ°ãã
ãã®è¡ãæŽæ°ããŸãã
<li class="entry"><h2 id={{ entry.post_id }}>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
post_id
泚æããŠpost_id
ã ããŒã¿ããŒã¹ã調ã¹ãŠã察å¿ãããã£ãŒã«ããååšããããšã確èªããŠãã ããã
ãã¹ã
æåŸã«ããã¹ããæŽæ°ããŸãã
import unittest import os from flask import json from app import app, db TEST_DB = 'test.db' class BasicTestCase(unittest.TestCase): def test_index(self): """initial test. ensure flask was set up correctly""" tester = app.test_client(self) response = tester.get('/', content_type='html/text') self.assertEqual(response.status_code, 200) def test_database(self): """initial test. ensure that the database exists""" tester = os.path.exists("flaskr.db") self.assertTrue(tester) class FlaskrTestCase(unittest.TestCase): def setUp(self): """Set up a blank temp database before each test""" basedir = os.path.abspath(os.path.dirname(__file__)) app.config['TESTING'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \ os.path.join(basedir, TEST_DB) self.app = app.test_client() db.create_all() def tearDown(self): """Destroy blank temp database after each test""" db.drop_all() def login(self, username, password): """Login helper function""" return self.app.post('/login', data=dict( username=username, password=password ), follow_redirects=True) def logout(self): """Logout helper function""" return self.app.get('/logout', follow_redirects=True)
setUp()
tearDown()
.
, , .
, ( pip freeze > requirements.txt
), heroku!
ãããã«
- ? .
- Heroku . ãã£ãïŒ
- Flask? Real Python .
- - ? . ãã£ãïŒ
* : Flask