「Flaskr」-Flaskの玹介、テストによる開発TDDおよびjQuery

Flaskは優れたPythonベヌスのマむクロWebフレヌムワヌクです。 Flaskrは、 公匏の Flask ガむドで説明されおいるミニブログです。 私はそれを認めるこずができる以䞊、このガむドを䜕床も経隓したした。 ただし、このガむドを次のステップに䜿甚し、 テストによる開発  テスト駆動開発 ず少しのjQueryを远加したす。


FlaskやWeb開発党般に䞍慣れな堎合は、次の基本抂念を理解するこずが重芁です。


  1. Getリク゚ストずPostリク゚ストの違い、およびそれらを凊理するアプリケヌションの機胜。
  2. リク゚ストずは䜕ですか
  3. ブラりザでHTMLペヌゞを衚瀺しお蚪問者に返す方法。

**泚**このガむドはhttps://realpython.comプロゞェクトによっお提䟛されたす。 http://www.realpython.com/coursesコヌスでPythonトレヌニングずDjangoずFlaskを䜿甚したWeb開発を賌入しお、このオヌプン゜ヌスプロゞェクトをサポヌトしおください。

内容


  1. テストによる開発
  2. Pythonをダりンロヌド
  3. プロゞェクトのむンストヌル
  4. 最初のテスト
  5. Flaskrをむンストヌルする
  6. 二次詊隓
  7. デヌタベヌスのむンストヌル
  8. テンプレヌトずビュヌ
  9. 色を远加する
  10. テスト
  11. jQuery
  12. 展開
  13. 別のテスト
  14. ブヌトストラップ
  15. SQLAlchemy
  16. おわりに

必芁条件


このマニュアルでは、次の゜フトりェアを䜿甚しおいたす。


  1. Python v3.5.1
  2. フラスコv0.10.1
  3. Flask-SQLAlchemy v2.1
  4. gunicorn v19.4.5

テストによる開発


tdd


テストによる開発tddは、関数自䜓を䜜成する前に自動テストを䜜成する開発の䞀皮です。 ぀たり、テストずコヌド蚘述の組み合わせです。 このプロセスは、コヌドの正確性を確保するのに圹立぀だけでなく、プロゞェクトの蚭蚈ずアヌキテクチャを絶えず制埡しながら開発するこずもできたす。


TDDは通垞、䞊の図に瀺すように、Red-Green-Refactoringスキヌムに埓いたす。


  1. 曞き蟌みテスト
  2. テストを実行したすそしお倱敗したす
  3. テストに合栌するためのコヌドを曞く
  4. コヌドのリファクタリングず再テスト䜕床も

Pythonをダりンロヌド


開始する前に、 Python 3.5の最新バヌゞョンがむンストヌルされおいるこずを確認しおください。これはhttp://www.python.org/download/からダりンロヌドできたす。


泚 このガむドではPython V 3.5.1を䜿甚しおいたす。

Pythonず䞀緒に、以䞋を配眮する必芁もありたす。



プロゞェクトのむンストヌル


  1. プロゞェクトを保存する新しいフォルダヌを䜜成したす。


    $ mkdir flaskr-tdd $ cd flaskr-tdd 

  2. 仮想環境を䜜成しおアクティブ化したす。


     $ pyvenv-3.5 env $ source env/bin/activate 

    泚 
    仮想環境内にいる堎合、端末は$アむコンの前に碑文envを衚瀺したす。 仮想環境を終了するには、 deactivateコマンドを䜿甚し、必芁に応じおアクティブ化し、目的のディレクトリに移動しおコマンドsource env/bin/activateたす。

  3. pipを䜿甚したFlaskのむンストヌル


     $ pip3 install Flask 


最初のテスト


簡単なHello Worldプログラムから始めたしょう。


  1. テストファむルを䜜成したす。


     $ 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」が衚瀺されるかどうかを確認したす。


  1. テストを実行したす。


     $ python app-test.py 

    すべおが順調であれば、テストは倱敗したす。


  2. ここで、次の行をapp.pyファむルに远加しお、テストに合栌したす。


     $ touch app.py 

    コヌド


     from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello, World!" if __name__ == "__main__": app.run() 

  3. アプリを実行したす。


     $ python app.py 

    http// localhost5000 /で察凊したす 。 「Hello、World」ずいう行が衚瀺されたす。 画面に。


    タヌミナルに戻り、Ctrl + Cを䜿甚しお開発サヌバヌを停止したしょう。


  4. テストを再床実行したす。


     $ python app-test.py . ---------------------------------------------------------------------- Ran 1 test in 0.016s OK 

    玠晎らしい、あなたが必芁なもの。



Flaskrをむンストヌルする


  1. 構造を远加


    プロゞェクトのルヌトに「静的」ず「テンプレヌト」のいく぀かのフォルダヌを远加したす。 次の構造を取埗する必芁がありたす。


     ├── app-test.py ├── app.py ├── static └── templates 

  2. 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に䜿甚されたす。



二次詊隓


アプリケヌションを実行するベヌスファむルを䜜成したしょう。 ただし、最初にテストを䜜成する必芁がありたす。


  1. 最初のテストから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コヌドを取埗したす。


  2. app.pyを倉曎したす 。


     # imports import sqlite3 from flask import Flask, request, session, g, redirect, url_for, \ abort, render_template, flash, jsonify # configuration DATABASE = 'flaskr.db' DEBUG = True SECRET_KEY = 'my_precious' USERNAME = 'admin' PASSWORD = 'admin' # create and initialize app app = Flask(__name__) app.config.from_object(__name__) if __name__ == '__main__': app.run() 

    ここで、必芁なモゞュヌルをむンポヌトし、グロヌバル倉数の構成セクションを䜜成し、アプリケヌションを初期化しおから起動したす。


  3. だから、それを実行したす


     $ python app.py 

    サヌバヌを起動したす。 ルヌトがなく、その衚珟が存圚しないため、「/」ルヌトにアクセスするずきに404゚ラヌメッセヌゞが衚瀺されるはずです。 タヌミナルに戻りたしょう。 開発サヌバヌを停止したす。 次に、単䜓テストを実行したす。 ゚ラヌなしで合栌するはずです。



デヌタベヌスのむンストヌル


私たちの目暙は、デヌタベヌスぞの接続を䜜成し、スキヌムに基づいおデヌタベヌスを䜜成し、ただ存圚しない堎合は、テストを実行するたびに接続を閉じるこずです。


  1. デヌタベヌスファむルの存圚を確認するにはどうすればよいですか 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() 

    これを実行しお、テストが倱敗し、デヌタベヌスが存圚しないこずを確認したす。


  2. 次に、 app.pyに次のコヌドを远加したす。


     # connect to database def connect_db(): """Connects to the database.""" rv = sqlite3.connect(app.config['DATABASE']) rv.row_factory = sqlite3.Row return rv # create the database def init_db(): with app.app_context(): db = get_db() with app.open_resource('schema.sql', mode='r') as f: db.cursor().executescript(f.read()) db.commit() # open database connection def get_db(): if not hasattr(g, 'sqlite_db'): g.sqlite_db = connect_db() return g.sqlite_db # close database connection @app.teardown_appcontext def close_db(error): if hasattr(g, 'sqlite_db'): g.sqlite_db.close() 

    そしお、 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) #    (assert) def test_empty_db(self): """,    """ rv = self.app.get('/') assert b'No entries here so far' in rv.data def test_login_logout(self): """    """ rv = self.login( app.app.config['USERNAME'], app.app.config['PASSWORD'] ) assert b'You were logged in' in rv.data rv = self.logout() assert b'You were logged out' in rv.data rv = self.login( app.app.config['USERNAME'] + 'x', app.app.config['PASSWORD'] ) assert b'Invalid username' in rv.data rv = self.login( app.app.config['USERNAME'], app.app.config['PASSWORD'] + 'x' ) assert b'Invalid password' in rv.data def test_messages(self): """,       """ self.login( app.app.config['USERNAME'], app.app.config['PASSWORD'] ) rv = self.app.post('/add', data=dict( title='<Hello>', text='<strong>HTML</strong> allowed here' ), follow_redirects=True) assert b'No entries here so far' not in rv.data assert b'&lt;Hello&gt;' in rv.data assert b'<strong>HTML</strong> allowed here' in rv.data if __name__ == '__main__': unittest.main() 

ここでテストを実行するず、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'&lt;Hello&gt;' in rv.data AssertionError ---------------------------------------------------------------------- Ran 5 tests in 0.088s FAILED (failures=4) 

テストをパスさせたしょう...


レコヌドを衚瀺


  1. 最初に、 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) 

  2. 次に、「テンプレヌト」フォルダヌに移動しお、このコンテンツの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> 

  3. テストを実行したす。 以䞋が衚瀺されるはずです。


     Ran 5 tests in 0.131s FAILED (failures=2, errors=2) 


ナヌザヌ認蚌


  1. 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リク゚ストが䜿甚されたす。


  2. ファむル「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> 

  3. テストを再床実行したす。


    それでもいく぀かの゚ラヌが衚瀺されるはずです ゚ラヌの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) 

  4. 次に、ビュヌにレコヌドを远加する関数を远加したす。


     @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')) 

  5. テストを繰り返したす


    これが衚瀺されるはずです。


     ====================================================================== 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を远加しお、サむトをもう少しむンタラクティブにしたす。


  1. 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> 

  2. 「静的」ディレクトリにmain.jsファむルを䜜成し、次のコヌドを曞き蟌みたす。


     $(function() { console.log( "ready!" ); // sanity check $('.entry').on('click', function() { var entry = this; var post_id = $(this).find('h2').attr('id'); $.ajax({ type:'GET', url: '/delete' + '/' + post_id, context: entry, success:function(result) { if(result.status === 1) { $(this).remove(); console.log(result); } } }); }); }); 

  3. デヌタベヌスからメッセヌゞを削陀できるように、 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) 

  4. 最埌に、新しいテストを䜜成したす。


     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にデプロむしないでください。


  1. これを行うには、最初に登録しおからHeroku Toolbeltをむンストヌルしたす。


  2. 次に、 gunicornずいうWebサヌバヌをむンストヌルしたす。


     $ pip install gunicorn 

  3. プロゞェクトのルヌトにProcfileを䜜成したす。


     $ touch Procfile 

    次のコヌドを远加したす。


     web: gunicorn app:app 

  4. requirements.txtファむルを䜜成しお、アプリケヌションが機胜するためにむンストヌルする必芁がある倖郚䟝存関係を指定したす。


     $ pip freeze > requirements.txt 

  5. .gitignoreファむルを䜜成したす。


     $ touch .gitignore 

    バヌゞョン管理システムに含めるべきではないファむルずフォルダヌを远加したす。


     env *.pyc *.DS_Store __pycache__ 

  6. ロヌカルリポゞトリを远加したす。


     $ git init $ git add -A $ git commit -m "initial" 

  7. Herokuを展開したす。


     $ heroku create $ git push heroku master $ heroku open 


テストもう䞀床


クラりドでテストを実行したす。 heroku openコマンドは、ブラりザでアプリケヌションを開きたす。


ブヌトストラップ


Bootstrap 3からスタむルを曎新したしょう。


  1. 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ヘルパヌクラスに完党にアクセスできたす。


  2. 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> 

  3. そしお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をむンストヌルする


  1. Flask-SQLAlchemyむンストヌルを実行したす。


     $ pip install Flask-SQLAlchemy 

  2. create_db.pyファむルを䜜成し、次のコヌドをそこに貌り付けたす。


     # create_db.py from app import db from models import Flaskr # create the database and the db table db.create_all() # commit the changes db.session.commit() 

    このファむルは、新しいデヌタベヌスを䜜成するために䜿甚されたす。 さらに、叀い flaskr.db およびschema.sqlを削陀したす


  3. 次に、新しいスキヌマを生成する新しい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を曎新する


 # imports from flask import Flask, request, session, g, redirect, url_for, \ abort, render_template, flash, jsonify from flask.ext.sqlalchemy import SQLAlchemy import os # grabs the folder where the script runs basedir = os.path.abspath(os.path.dirname(__file__)) # configuration DATABASE = 'flaskr.db' DEBUG = True SECRET_KEY = 'my_precious' USERNAME = 'admin' PASSWORD = 'admin' # defines the full path for the database DATABASE_PATH = os.path.join(basedir, DATABASE) # the database uri SQLALCHEMY_DATABASE_URI = 'sqlite:///' + DATABASE_PATH # create app app = Flask(__name__) app.config.from_object(__name__) db = SQLAlchemy(app) import models @app.route('/') def index(): """Searches the database for entries, then displays them.""" entries = db.session.query(models.Flaskr) return render_template('index.html', entries=entries) @app.route('/add', methods=['POST']) def add_entry(): """Adds new post to the database.""" if not session.get('logged_in'): abort(401) new_entry = models.Flaskr(request.form['title'], request.form['text']) db.session.add(new_entry) db.session.commit() flash('New entry was successfully posted') return redirect(url_for('index')) @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')) @app.route('/delete/<int:post_id>', methods=['GET']) def delete_entry(post_id): """Deletes post from database""" result = {'status': 0, 'message': 'Error'} try: new_id = post_id db.session.query(models.Flaskr).filter_by(post_id=new_id).delete() db.session.commit() result = {'status': 1, 'message': "Post Deleted"} flash('The entry was deleted.') except Exception as e: result = {'status': 0, 'message': repr(e)} return jsonify(result) if __name__ == '__main__': app.run() 

䞊郚の蚭定の倉曎ず、各ビュヌ関数でデヌタベヌスにアクセスしお管理する手段に泚意しおください-埋め蟌み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) # assert functions def test_empty_db(self): """Ensure database is blank""" rv = self.app.get('/') self.assertIn(b'No entries yet. Add some!', rv.data) def test_login_logout(self): """Test login and logout using helper functions""" rv = self.login(app.config['USERNAME'], app.config['PASSWORD']) self.assertIn(b'You were logged in', rv.data) rv = self.logout() self.assertIn(b'You were logged out', rv.data) rv = self.login(app.config['USERNAME'] + 'x', app.config['PASSWORD']) self.assertIn(b'Invalid username', rv.data) rv = self.login(app.config['USERNAME'], app.config['PASSWORD'] + 'x') self.assertIn(b'Invalid password', rv.data) def test_messages(self): """Ensure that user can post messages""" self.login(app.config['USERNAME'], app.config['PASSWORD']) rv = self.app.post('/add', data=dict( title='<Hello>', text='<strong>HTML</strong> allowed here' ), follow_redirects=True) self.assertNotIn(b'No entries here so far', rv.data) self.assertIn(b'&lt;Hello&gt;', rv.data) self.assertIn(b'<strong>HTML</strong> allowed here', rv.data) def test_delete_message(self): """Ensure the messages are being deleted""" rv = self.app.get('/delete/1') data = json.loads(rv.data) self.assertEqual(data['status'], 1) if __name__ == '__main__': unittest.main() 

setUp() tearDown() .


, , .


, ( pip freeze > requirements.txt ), heroku!


おわりに


  1. ? .
  2. Heroku . やった
  3. Flask? Real Python .
  4. - ? . やった

* : Flask


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


All Articles