「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



All Articles