From a837c72a9d32f9046e31415280206ba8b057d6a6 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 24 Oct 2016 23:53:14 -0200 Subject: [PATCH 01/22] Improve ansible provision playbook --- contrib/ansible_playbooks/handlers.yml | 24 ++++++++++++ contrib/ansible_playbooks/provision.yml | 42 +++++++++++++++++++++ contrib/ansible_playbooks/secret/uwsgi.conf | 4 +- contrib/ansible_playbooks/secret/vars.yml | 13 ++++--- 4 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 contrib/ansible_playbooks/handlers.yml diff --git a/contrib/ansible_playbooks/handlers.yml b/contrib/ansible_playbooks/handlers.yml new file mode 100644 index 0000000..3f62489 --- /dev/null +++ b/contrib/ansible_playbooks/handlers.yml @@ -0,0 +1,24 @@ +--- +- name: restart nginx + service: name=nginx state=restarted + become_user: root + +- name: stop nginx + service: name=nginx state=stopped + become_user: root + +- name: start nginx + service: name=nginx state=started + become_user: root + +- name: reload nginx + service: name=nginx state=reloaded + become_user: root + +- name: restart supervisor + service: name=supervisor state=restarted + become_user: root + +# - name: restart gunicorn +# action: service name={{ project_name }}_gunicorn state=restarted +# sudo_user: root diff --git a/contrib/ansible_playbooks/provision.yml b/contrib/ansible_playbooks/provision.yml index 585fe89..55a4990 100644 --- a/contrib/ansible_playbooks/provision.yml +++ b/contrib/ansible_playbooks/provision.yml @@ -34,5 +34,47 @@ - name: Pull sources from the repository. git: repo={{ project_repo }} dest={{ project_root }} version={{ branch }} accept_hostkey=True force=yes + - name: Create virtualenv + shell: /usr/bin/python3.5 -m venv {{ virtualenv_directory }} + + - name: Install requirements of Python project. + pip: requirements={{ project_root }}/requirements.txt virtualenv={{ virtualenv_directory }} + + - name: Upload robots.txt configuration file. + copy: src=secret/robots.txt dest={{ project_root }} + - name: Change permissions. file: dest={{ project_home }} owner={{ project_name }} group={{ project_name }} recurse=yes + + - name: Install supervisor uwsgi configuration file. + copy: src=secret/uwsgi.conf dest=/etc/supervisor/conf.d/{{ project_name }}_uwsgi.conf + notify: restart supervisor + + ## Dependent tasks + + - name: Find default configurations of nginx sites-enabled. + find: paths="/etc/nginx/sites-enabled" + register: result + + - name: Remove files from sites-enabled of nginx. + file: path={{ item.path }} state=absent + with_items: + - "{{ result.files }}" + + ## + + - name: Upload nginx configuration file to sites-available. + copy: src=secret/nginx.conf dest=/etc/nginx/sites-available/{{ project_name }}.conf + + - name: Upload nginx configuration file to sites-enabled. + copy: src=secret/nginx.conf dest=/etc/nginx/sites-enabled/{{ project_name }}.conf + notify: restart nginx + + - name: Enable supervisor on start. + shell: update-rc.d supervisor defaults && update-rc.d supervisor enable + + - name: Create database. + shell: SECRET_KEY={{ secret_key }} SERVER_NAME={{ server_name }} DATABASE_URI={{ database_uri }} python db_create.py + + handlers: + - include: handlers.yml diff --git a/contrib/ansible_playbooks/secret/uwsgi.conf b/contrib/ansible_playbooks/secret/uwsgi.conf index 0dfe4fc..98bcc5c 100644 --- a/contrib/ansible_playbooks/secret/uwsgi.conf +++ b/contrib/ansible_playbooks/secret/uwsgi.conf @@ -1,5 +1,5 @@ [program:uwsgi_flasktutorial] -command=/home/flasktutorial/.venv/bin/uwsgi --master --socket=/home/flasktutorial/uwsgi.sock --chmod-socket=666 --workers=4 --pythonpath=/home/flasktutorial/project/ --wsgi=hci_api.wsgi --enable-threads --single-interpreter --stats /home/flasktutorial/uwsgistats.sock +command=/home/flasktutorial/.venv/bin/uwsgi --master --socket=/home/flasktutorial/uwsgi.sock --chmod-socket=666 --workers=4 --pythonpath=/home/flasktutorial/project/ --wsgi=tvseries:create_app\(\) --enable-threads --single-interpreter --stats /home/flasktutorial/uwsgistats.sock user=flasktutorial numprocs=1 stdout_logfile=/home/flasktutorial/flasktutorial.log @@ -7,5 +7,5 @@ stderr_logfile=/home/flasktutorial/flasktutorial_error.log autostart=true autorestart=true stopsignal=QUIT -environment=SECRET_KEY="",SERVER_NAME="",DATABASE_URI="sqlite:///tvseries.sqlite3" +environment=SECRET_KEY="",SERVER_NAME="",DATABASE_URI="sqlite:///tvseries.sqlite3" diff --git a/contrib/ansible_playbooks/secret/vars.yml b/contrib/ansible_playbooks/secret/vars.yml index 16ec712..ad8419a 100644 --- a/contrib/ansible_playbooks/secret/vars.yml +++ b/contrib/ansible_playbooks/secret/vars.yml @@ -4,12 +4,15 @@ project_home: /home/flasktutorial project_root: /home/flasktutorial/project virtualenv_directory: /home/flasktutorial/.venv project_repo: git@github.com:rafaelhenrique/flask_tutorial.git -private_key_github: /home//.ssh/id_rsa -public_key_github: /home//.ssh/id_rsa.pub -management_user: +private_key_github: /home/rafael/.ssh/id_rsa +public_key_github: /home/rafael/.ssh/id_rsa.pub +management_user: rafael +secret_key: '' +server_name: '' +database_uri: 'sqlite:///tvseries.sqlite3' production_variables: - SECRET_KEY: '' - SERVER_NAME: '' + SECRET_KEY: '' + SERVER_NAME: '' DATABASE_URI: 'sqlite:///tvseries.sqlite3' system_packages: - build-essential From 4924b747ae8094fb4f72fae07cc5ba9c9f14053d Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 24 Oct 2016 23:54:38 -0200 Subject: [PATCH 02/22] Add 10.0 on README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 87540e6..5876d8c 100644 --- a/README.md +++ b/README.md @@ -94,4 +94,10 @@ More information? Follow explanatory videos below. - See explanation about release: https://github.com/rafaelhenrique/flask_tutorial/releases/tag/9.0 - See video: https://www.youtube.com/watch?v=8hZQGDSA1Yo +## 10.0 - Automating deployment with Ansible (part 2)! + +- See release code: https://github.com/rafaelhenrique/flask_tutorial/tree/10.0 +- See explanation about release: https://github.com/rafaelhenrique/flask_tutorial/releases/tag/10.0 +- See video: https://www.youtube.com/watch?v=guKkJmTGVgc + From 8991323a40d3fd0621306503f8007490a75fa7cd Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Sun, 6 Nov 2016 11:36:34 -0200 Subject: [PATCH 03/22] Using create_app on db_create --- db_create.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/db_create.py b/db_create.py index 175f1d9..b4e6b56 100644 --- a/db_create.py +++ b/db_create.py @@ -1,4 +1,7 @@ -from tvseries import app, db +from tvseries import create_app +from tvseries.ext import db + +app = create_app() db.app = app db.create_all() From f336b2b1e2a0eb7fe244967f53ddb78d26ca005d Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 7 Nov 2016 09:35:45 -0200 Subject: [PATCH 04/22] Some fixes on Ansible provision --- contrib/ansible_playbooks/Makefile | 2 +- contrib/ansible_playbooks/provision.yml | 20 ++++++++++++++++---- contrib/ansible_playbooks/secret/hosts | 2 +- contrib/ansible_playbooks/secret/nginx.conf | 4 ++-- contrib/ansible_playbooks/secret/uwsgi.conf | 2 +- contrib/ansible_playbooks/secret/vars.yml | 16 ++++++++-------- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/contrib/ansible_playbooks/Makefile b/contrib/ansible_playbooks/Makefile index 7734153..54a06fa 100644 --- a/contrib/ansible_playbooks/Makefile +++ b/contrib/ansible_playbooks/Makefile @@ -1,4 +1,4 @@ -provision: +provision: install-python-remote @ansible-playbook provision.yml -v --ask-become-pass deploy: diff --git a/contrib/ansible_playbooks/provision.yml b/contrib/ansible_playbooks/provision.yml index 55a4990..9054dc1 100644 --- a/contrib/ansible_playbooks/provision.yml +++ b/contrib/ansible_playbooks/provision.yml @@ -15,8 +15,8 @@ - name: Update system. apt: update_cache=yes - # - name: Upgrade system. - # apt: upgrade=dist + - name: Upgrade system. + apt: upgrade=dist - name: Install required system packages. apt: pkg={{ item }} state=installed update-cache=yes allow_unauthenticated=yes @@ -37,6 +37,9 @@ - name: Create virtualenv shell: /usr/bin/python3.5 -m venv {{ virtualenv_directory }} + - name: Upgrade PIP. + shell: "{{ virtualenv_directory }}/bin/pip install --upgrade pip" + - name: Install requirements of Python project. pip: requirements={{ project_root }}/requirements.txt virtualenv={{ virtualenv_directory }} @@ -70,11 +73,20 @@ copy: src=secret/nginx.conf dest=/etc/nginx/sites-enabled/{{ project_name }}.conf notify: restart nginx + - name: Create supervisor start scripts. + shell: update-rc.d supervisor defaults + - name: Enable supervisor on start. - shell: update-rc.d supervisor defaults && update-rc.d supervisor enable + shell: update-rc.d supervisor enable - name: Create database. - shell: SECRET_KEY={{ secret_key }} SERVER_NAME={{ server_name }} DATABASE_URI={{ database_uri }} python db_create.py + shell: "SECRET_KEY={{ secret_key }} SERVER_NAME={{ server_name }} DATABASE_URI={{ database_uri }} {{ virtualenv_directory }}/bin/python3.5 {{ project_root }}/db_create.py" + + - name: Run collect static. + shell: "SECRET_KEY={{ secret_key }} SERVER_NAME={{ server_name }} DATABASE_URI={{ database_uri }} {{ virtualenv_directory }}/bin/python3.5 {{ project_root }}/manage.py collect" + + - name: Change permissions. + file: dest={{ project_home }} owner={{ project_name }} group={{ project_name }} recurse=yes handlers: - include: handlers.yml diff --git a/contrib/ansible_playbooks/secret/hosts b/contrib/ansible_playbooks/secret/hosts index f08a762..94b089b 100644 --- a/contrib/ansible_playbooks/secret/hosts +++ b/contrib/ansible_playbooks/secret/hosts @@ -1,2 +1,2 @@ [virtualbox-vms] -vm01 ansible_port=22 ansible_ssh_host= environment_type=production branch=master ansible_ssh_pass= ansible_ssh_user= +vm01 ansible_port=22 ansible_ssh_host= environment_type=production branch=master ansible_ssh_pass= ansible_ssh_user= diff --git a/contrib/ansible_playbooks/secret/nginx.conf b/contrib/ansible_playbooks/secret/nginx.conf index 9b5407f..378ac89 100644 --- a/contrib/ansible_playbooks/secret/nginx.conf +++ b/contrib/ansible_playbooks/secret/nginx.conf @@ -6,7 +6,7 @@ server { listen 80; -# server_name flasktutorial.abraseucodigo.com.br; + server_name flasktutorial.abraseucodigo.com.br; location / { client_max_body_size 5M; @@ -16,7 +16,7 @@ server { location /static { autoindex on; - alias /home/flasktutorial/project/staticfiles; + alias /home/flasktutorial/project/static; } location /robots.txt { diff --git a/contrib/ansible_playbooks/secret/uwsgi.conf b/contrib/ansible_playbooks/secret/uwsgi.conf index 98bcc5c..d29aea8 100644 --- a/contrib/ansible_playbooks/secret/uwsgi.conf +++ b/contrib/ansible_playbooks/secret/uwsgi.conf @@ -7,5 +7,5 @@ stderr_logfile=/home/flasktutorial/flasktutorial_error.log autostart=true autorestart=true stopsignal=QUIT -environment=SECRET_KEY="",SERVER_NAME="",DATABASE_URI="sqlite:///tvseries.sqlite3" +environment=SECRET_KEY="",SERVER_NAME="",DATABASE_URI="sqlite:///tvseries.sqlite3" diff --git a/contrib/ansible_playbooks/secret/vars.yml b/contrib/ansible_playbooks/secret/vars.yml index ad8419a..756d899 100644 --- a/contrib/ansible_playbooks/secret/vars.yml +++ b/contrib/ansible_playbooks/secret/vars.yml @@ -4,16 +4,16 @@ project_home: /home/flasktutorial project_root: /home/flasktutorial/project virtualenv_directory: /home/flasktutorial/.venv project_repo: git@github.com:rafaelhenrique/flask_tutorial.git -private_key_github: /home/rafael/.ssh/id_rsa -public_key_github: /home/rafael/.ssh/id_rsa.pub -management_user: rafael -secret_key: '' -server_name: '' +private_key_github: /home//.ssh/id_rsa +public_key_github: /home//.ssh/id_rsa.pub +management_user: +secret_key: '' +server_name: '' database_uri: 'sqlite:///tvseries.sqlite3' production_variables: - SECRET_KEY: '' - SERVER_NAME: '' - DATABASE_URI: 'sqlite:///tvseries.sqlite3' + SECRET_KEY: "{{ secret_key }}" + SERVER_NAME: "{{ server_name }}" + DATABASE_URI: "{{ database_uri }}" system_packages: - build-essential - python3 From 573c08fbf47394276e231717522d6f4c058fdb58 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Tue, 15 Nov 2016 01:15:35 -0200 Subject: [PATCH 05/22] Add comment about OS version --- contrib/ansible_playbooks/provision.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contrib/ansible_playbooks/provision.yml b/contrib/ansible_playbooks/provision.yml index 9054dc1..20b92a7 100644 --- a/contrib/ansible_playbooks/provision.yml +++ b/contrib/ansible_playbooks/provision.yml @@ -1,4 +1,7 @@ --- +# This playbook works fine on Ubuntu 16.04.1 x86_64 +# not tested in another versions + - hosts: virtualbox-vms vars_files: - secret/vars.yml From ec129d69858b7b6761dfa5b24c8b29cf484a4d03 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Tue, 15 Nov 2016 01:16:04 -0200 Subject: [PATCH 06/22] Change repository of project --- contrib/ansible_playbooks/secret/vars.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/ansible_playbooks/secret/vars.yml b/contrib/ansible_playbooks/secret/vars.yml index 756d899..23e5014 100644 --- a/contrib/ansible_playbooks/secret/vars.yml +++ b/contrib/ansible_playbooks/secret/vars.yml @@ -3,7 +3,7 @@ project_name: flasktutorial project_home: /home/flasktutorial project_root: /home/flasktutorial/project virtualenv_directory: /home/flasktutorial/.venv -project_repo: git@github.com:rafaelhenrique/flask_tutorial.git +project_repo: git@github.com:python-sorocaba/flask_tutorial.git private_key_github: /home//.ssh/id_rsa public_key_github: /home//.ssh/id_rsa.pub management_user: From 4473484709d0420a9605d3a96b69eebc4364b214 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Tue, 15 Nov 2016 01:17:01 -0200 Subject: [PATCH 07/22] Add Flask-Migrate to project --- db_create.sh | 13 +++++++++++++ manage.py | 7 +++++++ requirements.txt | 2 ++ tvseries/core/models.py | 2 +- 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100755 db_create.sh diff --git a/db_create.sh b/db_create.sh new file mode 100755 index 0000000..b25cc52 --- /dev/null +++ b/db_create.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Generate migrations directories and files +python manage.py db init +# Generate migration to model +python manage.py db migrate +# Show sql script +python manage.py db upgrade --sql +# Run online sql +python manage.py db upgrade +# Move database to project tvseries +mv tvseries.sqlite3 tvseries + diff --git a/manage.py b/manage.py index d5e317d..36cd9bc 100644 --- a/manage.py +++ b/manage.py @@ -1,12 +1,19 @@ from flask_script import Manager from flask_collect import Collect +from flask_migrate import Migrate, MigrateCommand from tvseries import create_app +from tvseries.ext import db from tvseries.config import DevelopmentConfig app = create_app(config=DevelopmentConfig) manager = Manager(app) +# migrate command +migrate = Migrate(app, db) +manager.add_command('db', MigrateCommand) + +# collect command collect = Collect() collect.init_app(app) diff --git a/requirements.txt b/requirements.txt index e2683c5..b6c8d3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,5 @@ pytest-flask==0.10.0 python-decouple==3.0 uWSGI==2.0.14 Flask-Collect==1.3.2 +ansible==2.2.0.0 +Flask-Migrate==2.0.1 diff --git a/tvseries/core/models.py b/tvseries/core/models.py index 3cd2587..5057adf 100644 --- a/tvseries/core/models.py +++ b/tvseries/core/models.py @@ -3,7 +3,7 @@ class TVSerie(db.Model): __table_args__ = {'sqlite_autoincrement': True} - id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), + id = db.Column(db.Integer(), nullable=False, unique=True, autoincrement=True, primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False) From ca32393af982e4f4c92f7e57305b0951117d0aa3 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Tue, 15 Nov 2016 01:20:16 -0200 Subject: [PATCH 08/22] Add 11.0 on README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5876d8c..d49398d 100644 --- a/README.md +++ b/README.md @@ -100,4 +100,9 @@ More information? Follow explanatory videos below. - See explanation about release: https://github.com/rafaelhenrique/flask_tutorial/releases/tag/10.0 - See video: https://www.youtube.com/watch?v=guKkJmTGVgc +## 11.0 - Migrate databases (part 1)! + +- See release code: https://github.com/rafaelhenrique/flask_tutorial/tree/11.0 +- See explanation about release: https://github.com/rafaelhenrique/flask_tutorial/releases/tag/11.0 +- See video: https://www.youtube.com/watch?v=k1IagndK9F8 From 85494f1fef5f85353a4276a3ce2967050416cedf Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 28 Nov 2016 21:52:47 -0200 Subject: [PATCH 09/22] Create script to up postgres docker container --- contrib/create_postgres_database.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 contrib/create_postgres_database.sh diff --git a/contrib/create_postgres_database.sh b/contrib/create_postgres_database.sh new file mode 100755 index 0000000..41edaff --- /dev/null +++ b/contrib/create_postgres_database.sh @@ -0,0 +1,8 @@ +#!/bin/bash +PORT=5432 +NAME=postgresql_flask_tutorial +VOLUME=$(pwd)/data_postgres +POSTGRES_PASSWORD=123 + +mkdir -p $VOLUME +docker run -t -i -p $PORT:$PORT --name $NAME -v $VOLUME:/var/lib/postgresql/data -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD postgres From fb3a0db023161fbf5b08147dfac1b56989918bf6 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 28 Nov 2016 22:01:20 -0200 Subject: [PATCH 10/22] Remove autoincrement sqlite paramether from model --- tvseries/core/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tvseries/core/models.py b/tvseries/core/models.py index 5057adf..c269674 100644 --- a/tvseries/core/models.py +++ b/tvseries/core/models.py @@ -2,7 +2,6 @@ class TVSerie(db.Model): - __table_args__ = {'sqlite_autoincrement': True} id = db.Column(db.Integer(), nullable=False, unique=True, autoincrement=True, primary_key=True) From 9a6542458716332bb283f8e07f1f0fbfde9fe26a Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 28 Nov 2016 22:03:31 -0200 Subject: [PATCH 11/22] Add psycopg2 to requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index b6c8d3e..ef7c4a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ uWSGI==2.0.14 Flask-Collect==1.3.2 ansible==2.2.0.0 Flask-Migrate==2.0.1 +psycopg2==2.6.2 From d523748c60ded3e6895e3d52cb01e1bc85f88f09 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 28 Nov 2016 22:12:53 -0200 Subject: [PATCH 12/22] Add initial migrations --- migrations/README | 1 + migrations/alembic.ini | 45 ++++++++++++++ migrations/env.py | 87 ++++++++++++++++++++++++++++ migrations/script.py.mako | 24 ++++++++ migrations/versions/c23d2a740fc7_.py | 37 ++++++++++++ 5 files changed, 194 insertions(+) create mode 100755 migrations/README create mode 100644 migrations/alembic.ini create mode 100755 migrations/env.py create mode 100755 migrations/script.py.mako create mode 100644 migrations/versions/c23d2a740fc7_.py diff --git a/migrations/README b/migrations/README new file mode 100755 index 0000000..98e4f9c --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..f8ed480 --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100755 index 0000000..4593816 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,87 @@ +from __future__ import with_statement +from alembic import context +from sqlalchemy import engine_from_config, pool +from logging.config import fileConfig +import logging + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from flask import current_app +config.set_main_option('sqlalchemy.url', + current_app.config.get('SQLALCHEMY_DATABASE_URI')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure(url=url) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.readthedocs.org/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + engine = engine_from_config(config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + connection = engine.connect() + context.configure(connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args) + + try: + with context.begin_transaction(): + context.run_migrations() + finally: + connection.close() + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100755 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/c23d2a740fc7_.py b/migrations/versions/c23d2a740fc7_.py new file mode 100644 index 0000000..9567ba2 --- /dev/null +++ b/migrations/versions/c23d2a740fc7_.py @@ -0,0 +1,37 @@ +"""empty message + +Revision ID: c23d2a740fc7 +Revises: +Create Date: 2016-11-28 22:03:49.441776 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c23d2a740fc7' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('tv_serie', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('episodies_number', sa.Integer(), nullable=False), + sa.Column('author', sa.String(length=50), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id'), + sa.UniqueConstraint('name') + ) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_table('tv_serie') + ### end Alembic commands ### From 73368ad3ad5cd24fee54b1f2614c420990515332 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Mon, 28 Nov 2016 22:34:55 -0200 Subject: [PATCH 13/22] Add year field on TVSerie model --- migrations/versions/e66884bb260f_.py | 30 ++++++++++++++++++++++++++++ tvseries/core/models.py | 1 + tvseries/tests/test_core.py | 7 +++++-- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/e66884bb260f_.py diff --git a/migrations/versions/e66884bb260f_.py b/migrations/versions/e66884bb260f_.py new file mode 100644 index 0000000..1b829d9 --- /dev/null +++ b/migrations/versions/e66884bb260f_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: e66884bb260f +Revises: c23d2a740fc7 +Create Date: 2016-11-28 22:19:19.418769 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e66884bb260f' +down_revision = 'c23d2a740fc7' +branch_labels = None +depends_on = None + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('tv_serie', sa.Column('year', sa.Date(), nullable=True)) + op.create_unique_constraint(None, 'tv_serie', ['id']) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + # op.drop_constraint(None, 'tv_serie', type_='unique') + op.drop_column('tv_serie', 'year') + ### end Alembic commands ### diff --git a/tvseries/core/models.py b/tvseries/core/models.py index c269674..1dcac9a 100644 --- a/tvseries/core/models.py +++ b/tvseries/core/models.py @@ -9,6 +9,7 @@ class TVSerie(db.Model): description = db.Column(db.Text, nullable=True) episodies_number = db.Column(db.Integer, nullable=False, default=1) author = db.Column(db.String(50), nullable=False) + year = db.Column(db.Date) def __repr__(self): if self.description: diff --git a/tvseries/tests/test_core.py b/tvseries/tests/test_core.py index 2cfa8c1..8f136a9 100644 --- a/tvseries/tests/test_core.py +++ b/tvseries/tests/test_core.py @@ -1,5 +1,6 @@ import pytest +from datetime import date from tvseries.core.models import TVSerie from tvseries.config import TestConfig @@ -79,7 +80,8 @@ def test_insert_on_model_tvserie(self, db): ) serie = TVSerie(name="Game of Thrones", description=description, - author="George R.R. Martin") + author="George R.R. Martin", + year=date(2011, 1, 1)) db.session.add(serie) db.session.commit() assert TVSerie.query.count() == 1 @@ -102,7 +104,8 @@ def test_repr_on_model_tvserie(self, db): ) serie = TVSerie(name="Game of Thrones", description=description, - author="George R.R. Martin") + author="George R.R. Martin", + year=date(2011, 1, 1)) assert repr(serie) == ( "TVSerie(id=None, name='Game of Thrones', " "description='Há muito t...', episodies_number=None)" From 901a52db5c1481b20c291f37fe62a15084f20bc8 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Tue, 29 Nov 2016 08:32:46 -0200 Subject: [PATCH 14/22] Add 12.0 on README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index d49398d..e8b87d2 100644 --- a/README.md +++ b/README.md @@ -106,3 +106,9 @@ More information? Follow explanatory videos below. - See explanation about release: https://github.com/rafaelhenrique/flask_tutorial/releases/tag/11.0 - See video: https://www.youtube.com/watch?v=k1IagndK9F8 +## 12.0 - Migrate databases (part 2)! + +- See release code: https://github.com/rafaelhenrique/flask_tutorial/tree/12.0 +- See explanation about release: https://github.com/rafaelhenrique/flask_tutorial/releases/tag/12.0 +- See video: https://www.youtube.com/watch?v=t4dGoI4S4SE + From 7c069dc2e4b64ec709343af0b3d9ff51f4b96bbe Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Tue, 29 Nov 2016 08:54:06 -0200 Subject: [PATCH 15/22] Fix link of travis badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8b87d2..8187b05 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/rafaelhenrique/flask_tutorial.svg?branch=master)](https://travis-ci.org/rafaelhenrique/flask_tutorial) +[![Build Status](https://travis-ci.org/python-sorocaba/flask_tutorial.svg?branch=master)](https://travis-ci.org/python-sorocaba/flask_tutorial) # Flask Tutorial From 70a27c1f49c8ec9de76bb7b7f7344f2b97d00a20 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Wed, 7 Dec 2016 21:52:01 -0200 Subject: [PATCH 16/22] Add explanation how to run queries manually --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 8187b05..2bbbf4c 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,34 @@ $ python manage.py runserver More information? Follow explanatory videos below. +## How to execute queries manually? + +Inside on your virtualenv: +``` +$ python manage.py shell +``` + +Import db and model: +``` +from tvseries.ext import db +from tvseries.core.models import TVSerie +``` + +Bind your session with your application: +``` +db.app = app +``` + +Quering example: +``` +db.session.query(TVSerie).all() +``` + +If operation is "create", "update" or "delete" dont forget to run commit operation to confirm operation: +``` +db.session.commit() +``` + ## 1.0 - Flask and the most basic application of the world! - See release code: https://github.com/rafaelhenrique/flask_tutorial/tree/1.0 From fcff27c9f5a8f58512bd584ac5b2c6ff78414287 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Wed, 7 Dec 2016 23:31:02 -0200 Subject: [PATCH 17/22] Implements CSRF verification --- tvseries/__init__.py | 3 ++- tvseries/config.py | 2 ++ tvseries/ext.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tvseries/__init__.py b/tvseries/__init__.py index eeda833..4f1a4a1 100644 --- a/tvseries/__init__.py +++ b/tvseries/__init__.py @@ -1,7 +1,7 @@ from flask import Flask from tvseries import config -from tvseries.ext import db +from tvseries.ext import db, csrf from tvseries.core import core_blueprint @@ -10,4 +10,5 @@ def create_app(config=config.ProductionConfig): app.config.from_object(config) app.register_blueprint(core_blueprint, url_prefix='/') db.init_app(app) + csrf.init_app(app) return app diff --git a/tvseries/config.py b/tvseries/config.py index daa8efb..3d13fd4 100644 --- a/tvseries/config.py +++ b/tvseries/config.py @@ -11,6 +11,7 @@ class BaseConfig(object): SQLALCHEMY_TRACK_MODIFICATIONS = True COLLECT_STATIC_ROOT = os.path.join(BASE_DIR, "static") COLLECT_STORAGE = 'flask_collect.storage.file' + WTF_CSRF_ENABLED = True DEBUG = False TESTING = False @@ -22,6 +23,7 @@ class DevelopmentConfig(BaseConfig): class TestConfig(BaseConfig): SQLALCHEMY_DATABASE_URI = 'sqlite:///tvseries-test.sqlite3' TESTING = True + WTF_CSRF_ENABLED = False class ProductionConfig(BaseConfig): diff --git a/tvseries/ext.py b/tvseries/ext.py index f0b13d6..1007acf 100644 --- a/tvseries/ext.py +++ b/tvseries/ext.py @@ -1,3 +1,5 @@ from flask_sqlalchemy import SQLAlchemy +from flask_wtf.csrf import CsrfProtect db = SQLAlchemy() +csrf = CsrfProtect() From 0f588dfded13fa1dffc472698799ff53419dc484 Mon Sep 17 00:00:00 2001 From: Rafael Henrique da Silva Correia Date: Thu, 8 Dec 2016 00:37:30 -0200 Subject: [PATCH 18/22] Implements TVSerieForm using Flask-WTF --- requirements.txt | 1 + tvseries/core/forms.py | 10 ++++++ tvseries/core/templates/add.html | 53 ++++++++++++++++++++++++++++---- tvseries/core/views.py | 20 ++++++------ tvseries/tests/test_core.py | 28 ++++++++++++----- tvseries/tests/test_forms.py | 38 +++++++++++++++++++++++ 6 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 tvseries/core/forms.py create mode 100644 tvseries/tests/test_forms.py diff --git a/requirements.txt b/requirements.txt index ef7c4a9..e909afc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ Flask-Collect==1.3.2 ansible==2.2.0.0 Flask-Migrate==2.0.1 psycopg2==2.6.2 +Flask-WTF==0.13.1 diff --git a/tvseries/core/forms.py b/tvseries/core/forms.py new file mode 100644 index 0000000..dccd3d0 --- /dev/null +++ b/tvseries/core/forms.py @@ -0,0 +1,10 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, DateField + + +class TVSerieForm(FlaskForm): + name = StringField('name') + description = StringField('description') + episodies_number = StringField('episodies_number') + author = StringField('author') + year = DateField('year') diff --git a/tvseries/core/templates/add.html b/tvseries/core/templates/add.html index ff38af4..2d7fca6 100644 --- a/tvseries/core/templates/add.html +++ b/tvseries/core/templates/add.html @@ -1,6 +1,7 @@ {% extends 'base.html' %} {% block content %} -
+ + {{ form.csrf_token }}
Adicionar série de TV
@@ -8,20 +9,60 @@
- + {{ form.name }} + {% if form.name.errors %} +
    + {% for error in form.name.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %}
- + {{ form.description }} + {% if form.description.errors %} +
    + {% for error in form.description.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %}
- + {{ form.author }} + {% if form.author.errors %} +
    + {% for error in form.author.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %}
- -
+ {{ form.episodies_number }} + {% if form.episodies_number.errors %} +
    + {% for error in form.episodies_number.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %} +
+
+ + {{ form.year }} + * formato: YYYY-MM-DD + {% if form.year.errors %} +
    + {% for error in form.year.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %} +
diff --git a/tvseries/core/views.py b/tvseries/core/views.py index 7347dcd..ecfcfa5 100644 --- a/tvseries/core/views.py +++ b/tvseries/core/views.py @@ -1,11 +1,12 @@ import os from random import choice -from flask import render_template, url_for, redirect, request +from flask import render_template, url_for, redirect from tvseries.ext import db from tvseries.core import core_blueprint from tvseries.core.models import TVSerie +from tvseries.core.forms import TVSerieForm @core_blueprint.route('') @@ -20,16 +21,17 @@ def home(name=None): @core_blueprint.route('add', methods=['GET', 'POST']) def add(): - if request.method == 'POST': - name = request.form.to_dict().get('serie-name') - description = request.form.to_dict().get('serie-description') - author = request.form.to_dict().get('serie-author') - episodies_number = request.form.to_dict().get('serie-episodes_number') - + form = TVSerieForm() + if form.validate_on_submit(): + name = form.name.data + description = form.description.data + author = form.author.data + episodies_number = form.episodies_number.data + year = form.year.data serie = TVSerie(name=name, description=description, author=author, - episodies_number=episodies_number) + episodies_number=episodies_number, year=year) db.session.add(serie) db.session.commit() return redirect('/') - return render_template('add.html') + return render_template('add.html', form=form) diff --git a/tvseries/tests/test_core.py b/tvseries/tests/test_core.py index 8f136a9..3f1a1df 100644 --- a/tvseries/tests/test_core.py +++ b/tvseries/tests/test_core.py @@ -26,23 +26,37 @@ def test_get_add_status_code(self): def test_get_add_content(self): response = self.client.get("/add") expected = ( - 'name="serie-name" id="id_serie-name"', - 'name="serie-description" id="id_serie-description"', - 'name="serie-author" id="id_serie-author"', - 'name="serie-episodies_number" id="id_serie-episodies_number"', + '', + '', + '', + '', + '', ) for field in expected: assert field in response.data.decode('utf-8') def test_post_add(self, db): response = self.client.post("/add", data={ - "serie-name": "Game of Thrones", - "serie-author": "George R.R. Martin", - "serie-description": "Teste", + "name": "Game of Thrones", + "description": "Teste", + "author": "George R.R. Martin", + "episodies_number": "60", + "year": date(2011, 1, 1) }) result = TVSerie.query.filter(TVSerie.name == 'Game of Thrones') assert response.status_code == 302 and result.count() == 1 + def test_post_add_with_invalid_data(self, db): + response = self.client.post("/add", data={ + "name": "Game of Thrones", + "description": "Teste", + "author": "George R.R. Martin", + "episodies_number": "60", + "year": "aaaaaaa" + }) + result = TVSerie.query.filter(TVSerie.name == 'Game of Thrones') + assert response.status_code == 200 and result.count() == 0 + def test_navbar(self, db): response = self.client.get("/") assert ('