From 167d521f02f3b8f256605eb68203d229cd6cadee Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Sun, 14 Apr 2019 19:23:48 +0530 Subject: [PATCH 01/26] =?UTF-8?q?docs:=20update=20contributing=20guideline?= =?UTF-8?q?s=20documentation=20=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 674fad4..9b1724d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,9 +23,14 @@ The scope should be the name of the npm package affected (as perceived by the pe The following is the list of supported scopes: - - **config** + - **core** - **common** + - **app** + - **tests** + - **models** + - **views** + - **manager** - **vcs** - - **core** - - **routes** + - **deps** - **migrations** + - **config** From 38a6bf53381191dd4c702dafa2b24971d9787a24 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 01:54:13 +0530 Subject: [PATCH 02/26] chore(models): add genre model to applications --- app/models/application.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/app/models/application.py b/app/models/application.py index d6d7d0f..4a9a055 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -1,18 +1,36 @@ +from marshmallow import fields, validate from .. import db, ma class Application(db.Model): __tablename__ = 'applications' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(250), nullable=False) + name = db.Column(db.String(100), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) - genre = db.Column(db.String(250), nullable=False) + genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', ondelete='CASCADE'), nullable=False) + genre = db.relationship('Genre', backref=db.backref('applications', lazy='dynamic')) def __init__(self, name, genre): self.name = name self.genre = genre + def __repr__(self): + return '' % self.name + + +class Genre(db.model): + __tablename__ = 'genre' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + class ApplicationSchema(ma.Schema): - class Meta: - fields = ('id', 'name', 'creation_date', 'genre') + id = fields.Integer(dump_only=True) + name = fields.String(required=True, validate=validate.Length(1)) + creation_date = fields.DateTime() + genre_id = fields.Integer(required=True) + + +class GenreSchema(ma.Schema): + id = fields.Integer() + name = fields.String(required=True) From 33fcbdbc7400605b9eef195f56ebc083bc0526c3 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 02:05:17 +0530 Subject: [PATCH 03/26] refactor(app): rename applications to application :truck: --- app/__init__.py | 4 ++-- app/application/__init__.py | 1 + app/{applications => application}/views.py | 6 +++--- app/applications/__init__.py | 1 - app/models/application.py | 4 ++-- migrations/versions/2b5013b1ff2e_.py | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 app/application/__init__.py rename app/{applications => application}/views.py (61%) delete mode 100644 app/applications/__init__.py diff --git a/app/__init__.py b/app/__init__.py index cdbfbba..ec4f52e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -26,7 +26,7 @@ def create_app(config_name): from .main import main as main_blueprint app.register_blueprint(main_blueprint, url_prefix=root + '/') - from .applications import applications as applications_blueprint - app.register_blueprint(applications_blueprint, url_prefix=root + '/applications') + from .application import application as application_blueprint + app.register_blueprint(application_blueprint, url_prefix=root + '/application') return app diff --git a/app/application/__init__.py b/app/application/__init__.py new file mode 100644 index 0000000..5007883 --- /dev/null +++ b/app/application/__init__.py @@ -0,0 +1 @@ +from app.application.views import application # noqa diff --git a/app/applications/views.py b/app/application/views.py similarity index 61% rename from app/applications/views.py rename to app/application/views.py index 054ff53..5600497 100644 --- a/app/applications/views.py +++ b/app/application/views.py @@ -1,13 +1,13 @@ from flask import Blueprint, jsonify -applications = Blueprint('applications', __name__) +application = Blueprint('application', __name__) -@applications.route('/', methods=['GET']) +@application.route('/', methods=['GET']) def get_applications(): return jsonify({"message": "Get Application New API"}) -@applications.route('/', methods=['POST']) +@application.route('/', methods=['POST']) def create_application(): return jsonify({"message": "Created Application New API"}) diff --git a/app/applications/__init__.py b/app/applications/__init__.py deleted file mode 100644 index ff34c6c..0000000 --- a/app/applications/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from app.applications.views import applications # noqa diff --git a/app/models/application.py b/app/models/application.py index 4a9a055..c05bef1 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -3,12 +3,12 @@ class Application(db.Model): - __tablename__ = 'applications' + __tablename__ = 'application' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', ondelete='CASCADE'), nullable=False) - genre = db.relationship('Genre', backref=db.backref('applications', lazy='dynamic')) + genre = db.relationship('Genre', backref=db.backref('application', lazy='dynamic')) def __init__(self, name, genre): self.name = name diff --git a/migrations/versions/2b5013b1ff2e_.py b/migrations/versions/2b5013b1ff2e_.py index 48a3cf6..ab741f2 100644 --- a/migrations/versions/2b5013b1ff2e_.py +++ b/migrations/versions/2b5013b1ff2e_.py @@ -18,7 +18,7 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('applications', + op.create_table('application', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=250), nullable=False), sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), @@ -30,5 +30,5 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('applications') + op.drop_table('application') # ### end Alembic commands ### From eee4ad91149b7e90db41a9003280f7b1f02f763f Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 02:22:20 +0530 Subject: [PATCH 04/26] chore(models): add application type to application model --- app/models/application.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/app/models/application.py b/app/models/application.py index c05bef1..e79c198 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -6,13 +6,17 @@ class Application(db.Model): __tablename__ = 'application' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) + type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', ondelete='CASCADE'), nullable=False) + type = db.relationship('Type', backref=db.backref('application', lazy='dynamic')) + description = db.Column(db.String(250), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', ondelete='CASCADE'), nullable=False) genre = db.relationship('Genre', backref=db.backref('application', lazy='dynamic')) - def __init__(self, name, genre): + def __init__(self, name, description, genre_id): self.name = name - self.genre = genre + self.description = description + self.genre_id = genre_id def __repr__(self): return '' % self.name @@ -24,9 +28,17 @@ class Genre(db.model): name = db.Column(db.String(100), nullable=False) +class Type(db.model): + __tablename__ = 'application_type' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + + class ApplicationSchema(ma.Schema): id = fields.Integer(dump_only=True) - name = fields.String(required=True, validate=validate.Length(1)) + name = fields.String(required=True, validate=validate.Length(1, 100)) + type_id = fields.Integer(required=True) + description = fields.String(required=True, validate=validate.Length(1, 250)) creation_date = fields.DateTime() genre_id = fields.Integer(required=True) @@ -34,3 +46,8 @@ class ApplicationSchema(ma.Schema): class GenreSchema(ma.Schema): id = fields.Integer() name = fields.String(required=True) + + +class TypeSchema(ma.Schema): + id = fields.Integer() + name = fields.String(required=True) From 55a273de5b3602b2a05211ad8cf69a59077ec842 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 05:04:31 +0530 Subject: [PATCH 05/26] chore(models): add method to generate genre meta from file --- app/models/application.py | 55 +++++++++++++++++++++++++++++++++++++-- meta/genre.meta.json | 37 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 meta/genre.meta.json diff --git a/app/models/application.py b/app/models/application.py index e79c198..77c36a6 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -1,9 +1,36 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# (c) Copyright 2019 Brion Mario. +# (c) This file is part of the CSSI REST API and is made available under MIT license. +# (c) For more information, see https://github.com/brionmario/cssi-api/blob/master/LICENSE.txt +# (c) Please forward any queries to the given email address. email: brion@apareciumlabs.com + +"""Models and Schemas for Applications + +This module handles contains the different db models and schemas +for applications. + +Attributes: + GENRE_META_FILE_PATH (str): Path for the file containing genre meta + TYPE_META_FILE_PATH (str): Path for the file containing application type meta + +Authors: + Brion Mario + +""" + +import os +import json from marshmallow import fields, validate from .. import db, ma +GENRE_META_FILE_PATH = 'meta/genre.meta.json' + class Application(db.Model): __tablename__ = 'application' + id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', ondelete='CASCADE'), nullable=False) @@ -22,17 +49,41 @@ def __repr__(self): return '' % self.name -class Genre(db.model): +class Genre(db.Model): __tablename__ = 'genre' + id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) + @classmethod + def seed(cls): + if os.path.exists(GENRE_META_FILE_PATH): + with open(GENRE_META_FILE_PATH) as genre_meta_json: + data = json.load(genre_meta_json) + for genre in data['genre']: + genre = Genre(name=genre['name']) + genre.save() + else: + # TODO: Add exception + print("Couldn't locate meta file") + + def save(self): + db.session.add(self) + db.session.commit() + + def __repr__(self): + return '' % self.name + -class Type(db.model): +class Type(db.Model): __tablename__ = 'application_type' + id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) + def __repr__(self): + return '' % self.name + class ApplicationSchema(ma.Schema): id = fields.Integer(dump_only=True) diff --git a/meta/genre.meta.json b/meta/genre.meta.json new file mode 100644 index 0000000..eba977c --- /dev/null +++ b/meta/genre.meta.json @@ -0,0 +1,37 @@ +{ + "genre": [ + { + "name": "Puzzle" + }, + { + "name": "Arcade" + }, + { + "name": "Action" + }, + { + "name": "Family" + }, + { + "name": "Educational" + }, + { + "name": "Adventure" + }, + { + "name": "Strategy" + }, + { + "name": "Board" + }, + { + "name": "Simulation" + }, + { + "name": "Trivia" + }, + { + "name": "Other" + } + ] +} \ No newline at end of file From c44510896cb6a6ed507220adbaaff180dbca229c Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 05:06:12 +0530 Subject: [PATCH 06/26] chore(manager): add command to generate db metadata --- .vscode/settings.json | 3 +++ manage.py | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..45d444e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "venv/bin/python3.7" +} \ No newline at end of file diff --git a/manage.py b/manage.py index a5097f5..dba3dbf 100644 --- a/manage.py +++ b/manage.py @@ -26,10 +26,23 @@ manager.add_command('db', MigrateCommand) +@manager.command +def create_metadata(): + """Create the table metadata. + + Application types and Genres need to be added to the database + in order for new applications to be added. + """ + from app.models import Genre + + Genre.seed() @manager.command def test(): - """Run the unit tests.""" + """Run the unit tests. + + This function will run the unit tests in the tests package. + """ import unittest tests = unittest.TestLoader().discover('tests') @@ -38,9 +51,9 @@ def test(): @manager.command def recreate_db(): - """ - Recreates a local database. You probably should not use this on - production. + """Recreates a local database + + Not safe to use in production. """ db.drop_all() db.create_all() From 4c1ac0c92870664a4ece1794f16cfda9d9a0edd3 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 05:06:43 +0530 Subject: [PATCH 07/26] chore(migrations): update migrations :alembic: --- migrations/versions/2b5013b1ff2e_.py | 34 ------------------- migrations/versions/f797ad9cfebb_.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 34 deletions(-) delete mode 100644 migrations/versions/2b5013b1ff2e_.py create mode 100644 migrations/versions/f797ad9cfebb_.py diff --git a/migrations/versions/2b5013b1ff2e_.py b/migrations/versions/2b5013b1ff2e_.py deleted file mode 100644 index ab741f2..0000000 --- a/migrations/versions/2b5013b1ff2e_.py +++ /dev/null @@ -1,34 +0,0 @@ -"""empty message - -Revision ID: 2b5013b1ff2e -Revises: -Create Date: 2019-04-14 16:51:50.295795 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '2b5013b1ff2e' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('application', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=250), nullable=False), - sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), - sa.Column('genre', sa.String(length=250), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('application') - # ### end Alembic commands ### diff --git a/migrations/versions/f797ad9cfebb_.py b/migrations/versions/f797ad9cfebb_.py new file mode 100644 index 0000000..7c3202b --- /dev/null +++ b/migrations/versions/f797ad9cfebb_.py @@ -0,0 +1,50 @@ +"""empty message + +Revision ID: f797ad9cfebb +Revises: +Create Date: 2019-04-15 04:48:02.172663 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f797ad9cfebb' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('application_type', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('genre', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('application', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('type_id', sa.Integer(), nullable=False), + sa.Column('description', sa.String(length=250), nullable=False), + sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.Column('genre_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['type_id'], ['application_type.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('application') + op.drop_table('genre') + op.drop_table('application_type') + # ### end Alembic commands ### From 42bda1ab496b940115e1a5868462496bb921db38 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 05:07:52 +0530 Subject: [PATCH 08/26] fix(app): fix table recognition issue in migration :bug: --- app/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index ec4f52e..5fb8b4b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -17,8 +17,11 @@ def create_app(config_name): app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # disabling sqlalchemy event system CONFIG[config_name].init_app(app) + root = CONFIG[config_name].APPLICATION_ROOT + from app.models import Application, Genre, Type + # Set up extensions db.init_app(app) From 8548a0d2e36c76ba4953486a1ac965047e1f4789 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 07:02:34 +0530 Subject: [PATCH 09/26] chore(app): add method to generate app type meta from file --- app/__init__.py | 2 +- app/models/application.py | 33 ++++++++++++++++++++++++++------- manage.py | 3 ++- meta/application_type.meta.json | 13 +++++++++++++ 4 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 meta/application_type.meta.json diff --git a/app/__init__.py b/app/__init__.py index 5fb8b4b..62e67fc 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -20,7 +20,7 @@ def create_app(config_name): root = CONFIG[config_name].APPLICATION_ROOT - from app.models import Application, Genre, Type + from app.models import Application, Genre, ApplicationType # Set up extensions db.init_app(app) diff --git a/app/models/application.py b/app/models/application.py index 77c36a6..53d39f6 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -13,7 +13,7 @@ Attributes: GENRE_META_FILE_PATH (str): Path for the file containing genre meta - TYPE_META_FILE_PATH (str): Path for the file containing application type meta + APPLICATION_TYPE_META_FILE_PATH (str): Path for the file containing application type meta Authors: Brion Mario @@ -26,7 +26,7 @@ from .. import db, ma GENRE_META_FILE_PATH = 'meta/genre.meta.json' - +APPLICATION_TYPE_META_FILE_PATH = 'meta/application_type.meta.json' class Application(db.Model): __tablename__ = 'application' @@ -34,14 +34,15 @@ class Application(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', ondelete='CASCADE'), nullable=False) - type = db.relationship('Type', backref=db.backref('application', lazy='dynamic')) + type = db.relationship('ApplicationType', backref=db.backref('application', lazy='dynamic')) description = db.Column(db.String(250), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', ondelete='CASCADE'), nullable=False) genre = db.relationship('Genre', backref=db.backref('application', lazy='dynamic')) - def __init__(self, name, description, genre_id): + def __init__(self, name, type_id, description, genre_id): self.name = name + self.type_id = type_id self.description = description self.genre_id = genre_id @@ -60,9 +61,10 @@ def seed(cls): if os.path.exists(GENRE_META_FILE_PATH): with open(GENRE_META_FILE_PATH) as genre_meta_json: data = json.load(genre_meta_json) - for genre in data['genre']: - genre = Genre(name=genre['name']) + for item in data['genre']: + genre = Genre(name=item['name']) genre.save() + print("Adding genre metadata: {}".format(genre)) else: # TODO: Add exception print("Couldn't locate meta file") @@ -75,12 +77,29 @@ def __repr__(self): return '' % self.name -class Type(db.Model): +class ApplicationType(db.Model): __tablename__ = 'application_type' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) + @classmethod + def seed(cls): + if os.path.exists(APPLICATION_TYPE_META_FILE_PATH): + with open(APPLICATION_TYPE_META_FILE_PATH) as app_type_meta_json: + data = json.load(app_type_meta_json) + for item in data['types']: + app_type = ApplicationType(name=item['name']) + app_type.save() + print("Adding application type metadata: {}".format(app_type)) + else: + # TODO: Add exception + print("Couldn't locate meta file") + + def save(self): + db.session.add(self) + db.session.commit() + def __repr__(self): return '' % self.name diff --git a/manage.py b/manage.py index dba3dbf..e7c3b85 100644 --- a/manage.py +++ b/manage.py @@ -33,9 +33,10 @@ def create_metadata(): Application types and Genres need to be added to the database in order for new applications to be added. """ - from app.models import Genre + from app.models import Genre, ApplicationType Genre.seed() + ApplicationType.seed() @manager.command def test(): diff --git a/meta/application_type.meta.json b/meta/application_type.meta.json new file mode 100644 index 0000000..ded6fef --- /dev/null +++ b/meta/application_type.meta.json @@ -0,0 +1,13 @@ +{ + "types": [ + { + "name": "vr" + }, + { + "name": "ar" + }, + { + "name": "mr" + } + ] +} \ No newline at end of file From 275776b93dc918af412f1929f53afc1686bf86a1 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 07:02:56 +0530 Subject: [PATCH 10/26] chore(migrations): update migrations :alembic: --- migrations/versions/{f797ad9cfebb_.py => 62e81fb2ed55_.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename migrations/versions/{f797ad9cfebb_.py => 62e81fb2ed55_.py} (94%) diff --git a/migrations/versions/f797ad9cfebb_.py b/migrations/versions/62e81fb2ed55_.py similarity index 94% rename from migrations/versions/f797ad9cfebb_.py rename to migrations/versions/62e81fb2ed55_.py index 7c3202b..b2ff1b0 100644 --- a/migrations/versions/f797ad9cfebb_.py +++ b/migrations/versions/62e81fb2ed55_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: f797ad9cfebb +Revision ID: 62e81fb2ed55 Revises: -Create Date: 2019-04-15 04:48:02.172663 +Create Date: 2019-04-15 05:51:49.623040 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'f797ad9cfebb' +revision = '62e81fb2ed55' down_revision = None branch_labels = None depends_on = None From 54d5d2a48937e30ca8e09e58047053af84ae71a2 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 07:03:34 +0530 Subject: [PATCH 11/26] chore(views): implement application GET and POST methods --- app/application/views.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/application/views.py b/app/application/views.py index 5600497..55d4e9f 100644 --- a/app/application/views.py +++ b/app/application/views.py @@ -1,13 +1,33 @@ -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, request +from app.models import Application, ApplicationType, ApplicationSchema +from marshmallow import Schema, fields, ValidationError, pre_load +from .. import db application = Blueprint('application', __name__) +applications_schema = ApplicationSchema(many=True) +application_schema = ApplicationSchema() + @application.route('/', methods=['GET']) def get_applications(): - return jsonify({"message": "Get Application New API"}) + applications = Application.query.all() + applications = applications_schema.dump(applications).data + return jsonify({"status": "success", "data": applications}), 200 @application.route('/', methods=['POST']) def create_application(): - return jsonify({"message": "Created Application New API"}) + name = request.json['name'] + type_id = request.json['type_id'] + description = request.json['description'] + genre_id = request.json['genre_id'] + + new_application = Application(name=name, type_id=type_id, description=description, genre_id=genre_id) + + db.session.add(new_application) + db.session.commit() + + result = application_schema.dump(new_application).data + + return jsonify({"message": "Created new application {}.".format(name), "data": result}) From a2f7a8ea831ce962193c9a312f093c8c2cdd7f2e Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:15:19 +0530 Subject: [PATCH 12/26] refactor(models): refactor application models and schemas --- app/models/application.py | 87 +++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/app/models/application.py b/app/models/application.py index 53d39f6..5118748 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -28,26 +28,19 @@ GENRE_META_FILE_PATH = 'meta/genre.meta.json' APPLICATION_TYPE_META_FILE_PATH = 'meta/application_type.meta.json' + class Application(db.Model): __tablename__ = 'application' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) - type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', ondelete='CASCADE'), nullable=False) - type = db.relationship('ApplicationType', backref=db.backref('application', lazy='dynamic')) + app_type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', use_alter=True, name='fk_app_type_id'), nullable=False) description = db.Column(db.String(250), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) - genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', ondelete='CASCADE'), nullable=False) - genre = db.relationship('Genre', backref=db.backref('application', lazy='dynamic')) - - def __init__(self, name, type_id, description, genre_id): - self.name = name - self.type_id = type_id - self.description = description - self.genre_id = genre_id + genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', use_alter=True, name='fk_genre_id'), nullable=False) def __repr__(self): - return '' % self.name + return self.name class Genre(db.Model): @@ -55,69 +48,93 @@ class Genre(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) + display_name = db.Column(db.String(250), nullable=False) + applications = db.relationship('Application', backref='genre', lazy='dynamic') @classmethod def seed(cls): - if os.path.exists(GENRE_META_FILE_PATH): - with open(GENRE_META_FILE_PATH) as genre_meta_json: - data = json.load(genre_meta_json) - for item in data['genre']: - genre = Genre(name=item['name']) - genre.save() - print("Adding genre metadata: {}".format(genre)) + if cls.is_table_empty(cls): + if os.path.exists(GENRE_META_FILE_PATH): + with open(GENRE_META_FILE_PATH) as genre_meta_json: + data = json.load(genre_meta_json) + for item in data['genre']: + genre = Genre(name=item['name'], display_name=item['display_name']) + genre.save() + print("Adding genre metadata: {}".format(genre)) + else: + # TODO: Add exception + print("Couldn't locate meta file") else: - # TODO: Add exception - print("Couldn't locate meta file") + print('Table is already filled') def save(self): db.session.add(self) db.session.commit() + def is_table_empty(self): + if not self.query.all(): + return True + return False + def __repr__(self): - return '' % self.name + return self.display_name class ApplicationType(db.Model): __tablename__ = 'application_type' id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) + name = db.Column(db.String(50), nullable=False) + display_name = db.Column(db.String(100), nullable=False) + display_name_full = db.Column(db.String(250), nullable=False) + applications = db.relationship('Application', backref='app_type', lazy='dynamic') @classmethod def seed(cls): - if os.path.exists(APPLICATION_TYPE_META_FILE_PATH): - with open(APPLICATION_TYPE_META_FILE_PATH) as app_type_meta_json: - data = json.load(app_type_meta_json) - for item in data['types']: - app_type = ApplicationType(name=item['name']) - app_type.save() - print("Adding application type metadata: {}".format(app_type)) + if cls.is_table_empty(cls): + if os.path.exists(APPLICATION_TYPE_META_FILE_PATH): + with open(APPLICATION_TYPE_META_FILE_PATH) as app_type_meta_json: + data = json.load(app_type_meta_json) + for item in data['types']: + app_type = ApplicationType(name=item['name'], display_name=item['display_name'], display_name_full=item['display_name_full']) + app_type.save() + print("Adding application type metadata: {}".format(app_type)) + else: + # TODO: Add exception + print("Couldn't locate meta file") else: - # TODO: Add exception - print("Couldn't locate meta file") + print('Table is already filled') def save(self): db.session.add(self) db.session.commit() + def is_table_empty(self): + if not self.query.all(): + return True + return False + def __repr__(self): - return '' % self.name + return self.display_name_full class ApplicationSchema(ma.Schema): id = fields.Integer(dump_only=True) name = fields.String(required=True, validate=validate.Length(1, 100)) - type_id = fields.Integer(required=True) + app_type = fields.String(required=True) description = fields.String(required=True, validate=validate.Length(1, 250)) creation_date = fields.DateTime() - genre_id = fields.Integer(required=True) + genre = fields.String(required=True) class GenreSchema(ma.Schema): id = fields.Integer() name = fields.String(required=True) + display_name = fields.String(required=True) -class TypeSchema(ma.Schema): +class ApplicationTypeSchema(ma.Schema): id = fields.Integer() name = fields.String(required=True) + display_name = fields.String(required=True) + display_name_full = fields.String(required=True) From 65e67afb300d728705a626ef231de64b28b1d240 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:18:08 +0530 Subject: [PATCH 13/26] chore(views): implement type and genre relationship in application view --- app/application/views.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/application/views.py b/app/application/views.py index 55d4e9f..c5d324a 100644 --- a/app/application/views.py +++ b/app/application/views.py @@ -1,6 +1,5 @@ from flask import Blueprint, jsonify, request -from app.models import Application, ApplicationType, ApplicationSchema -from marshmallow import Schema, fields, ValidationError, pre_load +from app.models import Application, ApplicationType, ApplicationSchema, Genre from .. import db application = Blueprint('application', __name__) @@ -19,11 +18,11 @@ def get_applications(): @application.route('/', methods=['POST']) def create_application(): name = request.json['name'] - type_id = request.json['type_id'] + app_type = ApplicationType.query.filter_by(name=request.json['app_type']).first() description = request.json['description'] - genre_id = request.json['genre_id'] + genre = Genre.query.filter_by(name=request.json['genre']).first() - new_application = Application(name=name, type_id=type_id, description=description, genre_id=genre_id) + new_application = Application(name=name, app_type=app_type, description=description, genre=genre) db.session.add(new_application) db.session.commit() From 41fff1c145cb7d5959432e6b0594886d1c93171e Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:19:12 +0530 Subject: [PATCH 14/26] chore(meta): change application meta structure --- meta/application_type.meta.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/meta/application_type.meta.json b/meta/application_type.meta.json index ded6fef..8aa8220 100644 --- a/meta/application_type.meta.json +++ b/meta/application_type.meta.json @@ -1,13 +1,19 @@ { "types": [ { - "name": "vr" + "name": "vr", + "display_name": "VR", + "display_name_full": "Virtual Reality" }, { - "name": "ar" + "name": "ar", + "display_name": "AR", + "display_name_full": "Augmented Reality" }, { - "name": "mr" + "name": "mr", + "display_name": "VR", + "display_name_full": "Mixed Reality" } ] } \ No newline at end of file From 14d5270acc29173b0bdd7d531b89449771d30535 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:19:25 +0530 Subject: [PATCH 15/26] chore(meta): change genre meta structure --- meta/genre.meta.json | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/meta/genre.meta.json b/meta/genre.meta.json index eba977c..23dcf17 100644 --- a/meta/genre.meta.json +++ b/meta/genre.meta.json @@ -1,37 +1,48 @@ { "genre": [ { - "name": "Puzzle" + "name": "puzzle", + "display_name": "Puzzle" }, { - "name": "Arcade" + "name": "arcade", + "display_name": "Arcade" }, { - "name": "Action" + "name": "action", + "display_name": "Action" }, { - "name": "Family" + "name": "family", + "display_name": "Family" }, { - "name": "Educational" + "name": "educational", + "display_name": "Educational" }, { - "name": "Adventure" + "name": "adventure", + "display_name": "Adventure" }, { - "name": "Strategy" + "name": "strategy", + "display_name": "Strategy" }, { - "name": "Board" + "name": "board", + "display_name": "Board" }, { - "name": "Simulation" + "name": "simulation", + "display_name": "Simulation" }, { - "name": "Trivia" + "name": "trivia", + "display_name": "Trivia" }, { - "name": "Other" + "name": "other", + "display_name": "Other" } ] } \ No newline at end of file From 3c4634ab20fc8a96e632b874479ad639acc7ba48 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:19:50 +0530 Subject: [PATCH 16/26] chore(migrations): update migrations :alembic: --- migrations/versions/2140400898af_.py | 53 +++++++++++++++++++ migrations/versions/6d973c15bfc3_.py | 40 ++++++++++++++ .../{62e81fb2ed55_.py => 7fbc96b0d7b1_.py} | 26 ++++----- migrations/versions/f3daeef03f0a_.py | 30 +++++++++++ 4 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 migrations/versions/2140400898af_.py create mode 100644 migrations/versions/6d973c15bfc3_.py rename migrations/versions/{62e81fb2ed55_.py => 7fbc96b0d7b1_.py} (85%) create mode 100644 migrations/versions/f3daeef03f0a_.py diff --git a/migrations/versions/2140400898af_.py b/migrations/versions/2140400898af_.py new file mode 100644 index 0000000..1985def --- /dev/null +++ b/migrations/versions/2140400898af_.py @@ -0,0 +1,53 @@ +"""empty message + +Revision ID: 2140400898af +Revises: 6d973c15bfc3 +Create Date: 2019-04-15 17:54:58.955924 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2140400898af' +down_revision = '6d973c15bfc3' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('application', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('app_type_id', sa.Integer(), nullable=False), + sa.Column('description', sa.String(length=250), nullable=False), + sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.Column('genre_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['app_type_id'], ['application_type.id'], name='fk_app_type_id', use_alter=True), + sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], name='fk_genre_id', use_alter=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('application_type', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('display_name', sa.String(length=100), nullable=False), + sa.Column('display_name_full', sa.String(length=250), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('genre', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('display_name', sa.String(length=250), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('genre') + op.drop_table('application_type') + op.drop_table('application') + # ### end Alembic commands ### diff --git a/migrations/versions/6d973c15bfc3_.py b/migrations/versions/6d973c15bfc3_.py new file mode 100644 index 0000000..22bb3a3 --- /dev/null +++ b/migrations/versions/6d973c15bfc3_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: 6d973c15bfc3 +Revises: 7fbc96b0d7b1 +Create Date: 2019-04-15 17:52:44.231615 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '6d973c15bfc3' +down_revision = '7fbc96b0d7b1' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('application', sa.Column('app_type_id', sa.Integer(), nullable=False)) + op.create_foreign_key('fk_genre_id', 'application', 'genre', ['genre_id'], ['id'], use_alter=True) + op.create_foreign_key('fk_app_type_id', 'application', 'application_type', ['app_type_id'], ['id'], use_alter=True) + op.drop_column('application', 'type_id') + op.add_column('application_type', sa.Column('display_name', sa.String(length=100), nullable=False)) + op.add_column('application_type', sa.Column('display_name_full', sa.String(length=250), nullable=False)) + op.add_column('genre', sa.Column('display_name', sa.String(length=250), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('genre', 'display_name') + op.drop_column('application_type', 'display_name_full') + op.drop_column('application_type', 'display_name') + op.add_column('application', sa.Column('type_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) + op.drop_constraint('fk_app_type_id', 'application', type_='foreignkey') + op.drop_constraint('fk_genre_id', 'application', type_='foreignkey') + op.drop_column('application', 'app_type_id') + # ### end Alembic commands ### diff --git a/migrations/versions/62e81fb2ed55_.py b/migrations/versions/7fbc96b0d7b1_.py similarity index 85% rename from migrations/versions/62e81fb2ed55_.py rename to migrations/versions/7fbc96b0d7b1_.py index b2ff1b0..268ba4e 100644 --- a/migrations/versions/62e81fb2ed55_.py +++ b/migrations/versions/7fbc96b0d7b1_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 62e81fb2ed55 +Revision ID: 7fbc96b0d7b1 Revises: -Create Date: 2019-04-15 05:51:49.623040 +Create Date: 2019-04-15 09:04:38.049734 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '62e81fb2ed55' +revision = '7fbc96b0d7b1' down_revision = None branch_labels = None depends_on = None @@ -18,25 +18,25 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('application_type', + op.create_table('application', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('type_id', sa.Integer(), nullable=False), + sa.Column('description', sa.String(length=250), nullable=False), + sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.Column('genre_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], name='fk_genre_id', use_alter=True), + sa.ForeignKeyConstraint(['type_id'], ['application_type.id'], name='fk_application_type_id', use_alter=True), sa.PrimaryKeyConstraint('id') ) - op.create_table('genre', + op.create_table('application_type', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=100), nullable=False), sa.PrimaryKeyConstraint('id') ) - op.create_table('application', + op.create_table('genre', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('type_id', sa.Integer(), nullable=False), - sa.Column('description', sa.String(length=250), nullable=False), - sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), - sa.Column('genre_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['type_id'], ['application_type.id'], ondelete='CASCADE'), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### @@ -44,7 +44,7 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('application') op.drop_table('genre') op.drop_table('application_type') + op.drop_table('application') # ### end Alembic commands ### diff --git a/migrations/versions/f3daeef03f0a_.py b/migrations/versions/f3daeef03f0a_.py new file mode 100644 index 0000000..39d7d61 --- /dev/null +++ b/migrations/versions/f3daeef03f0a_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: f3daeef03f0a +Revises: 2140400898af +Create Date: 2019-04-15 18:05:02.990082 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f3daeef03f0a' +down_revision = '2140400898af' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_foreign_key('fk_app_type_id', 'application', 'application_type', ['app_type_id'], ['id'], use_alter=True) + op.create_foreign_key('fk_genre_id', 'application', 'genre', ['genre_id'], ['id'], use_alter=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('fk_genre_id', 'application', type_='foreignkey') + op.drop_constraint('fk_app_type_id', 'application', type_='foreignkey') + # ### end Alembic commands ### From d1102c61260e5893ce96f80a67e9d36ad7f493b7 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:35:10 +0530 Subject: [PATCH 17/26] refactor(models): put models in separate files :recycle: --- app/__init__.py | 1 + app/models/__init__.py | 2 + app/models/application.py | 91 ---------------------------------- app/models/application_type.py | 51 +++++++++++++++++++ app/models/genre.py | 49 ++++++++++++++++++ 5 files changed, 103 insertions(+), 91 deletions(-) create mode 100644 app/models/application_type.py create mode 100644 app/models/genre.py diff --git a/app/__init__.py b/app/__init__.py index 62e67fc..9cd13c3 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -20,6 +20,7 @@ def create_app(config_name): root = CONFIG[config_name].APPLICATION_ROOT + # flask migrate doesn't recognize the tables without this import from app.models import Application, Genre, ApplicationType # Set up extensions diff --git a/app/models/__init__.py b/app/models/__init__.py index e405ef7..bdfa087 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -4,3 +4,5 @@ """ from .application import * # noqa +from .application_type import * # noqa +from .genre import * # noqa diff --git a/app/models/application.py b/app/models/application.py index 5118748..c242e51 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -25,9 +25,6 @@ from marshmallow import fields, validate from .. import db, ma -GENRE_META_FILE_PATH = 'meta/genre.meta.json' -APPLICATION_TYPE_META_FILE_PATH = 'meta/application_type.meta.json' - class Application(db.Model): __tablename__ = 'application' @@ -43,81 +40,6 @@ def __repr__(self): return self.name -class Genre(db.Model): - __tablename__ = 'genre' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) - display_name = db.Column(db.String(250), nullable=False) - applications = db.relationship('Application', backref='genre', lazy='dynamic') - - @classmethod - def seed(cls): - if cls.is_table_empty(cls): - if os.path.exists(GENRE_META_FILE_PATH): - with open(GENRE_META_FILE_PATH) as genre_meta_json: - data = json.load(genre_meta_json) - for item in data['genre']: - genre = Genre(name=item['name'], display_name=item['display_name']) - genre.save() - print("Adding genre metadata: {}".format(genre)) - else: - # TODO: Add exception - print("Couldn't locate meta file") - else: - print('Table is already filled') - - def save(self): - db.session.add(self) - db.session.commit() - - def is_table_empty(self): - if not self.query.all(): - return True - return False - - def __repr__(self): - return self.display_name - - -class ApplicationType(db.Model): - __tablename__ = 'application_type' - - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(50), nullable=False) - display_name = db.Column(db.String(100), nullable=False) - display_name_full = db.Column(db.String(250), nullable=False) - applications = db.relationship('Application', backref='app_type', lazy='dynamic') - - @classmethod - def seed(cls): - if cls.is_table_empty(cls): - if os.path.exists(APPLICATION_TYPE_META_FILE_PATH): - with open(APPLICATION_TYPE_META_FILE_PATH) as app_type_meta_json: - data = json.load(app_type_meta_json) - for item in data['types']: - app_type = ApplicationType(name=item['name'], display_name=item['display_name'], display_name_full=item['display_name_full']) - app_type.save() - print("Adding application type metadata: {}".format(app_type)) - else: - # TODO: Add exception - print("Couldn't locate meta file") - else: - print('Table is already filled') - - def save(self): - db.session.add(self) - db.session.commit() - - def is_table_empty(self): - if not self.query.all(): - return True - return False - - def __repr__(self): - return self.display_name_full - - class ApplicationSchema(ma.Schema): id = fields.Integer(dump_only=True) name = fields.String(required=True, validate=validate.Length(1, 100)) @@ -125,16 +47,3 @@ class ApplicationSchema(ma.Schema): description = fields.String(required=True, validate=validate.Length(1, 250)) creation_date = fields.DateTime() genre = fields.String(required=True) - - -class GenreSchema(ma.Schema): - id = fields.Integer() - name = fields.String(required=True) - display_name = fields.String(required=True) - - -class ApplicationTypeSchema(ma.Schema): - id = fields.Integer() - name = fields.String(required=True) - display_name = fields.String(required=True) - display_name_full = fields.String(required=True) diff --git a/app/models/application_type.py b/app/models/application_type.py new file mode 100644 index 0000000..085dae2 --- /dev/null +++ b/app/models/application_type.py @@ -0,0 +1,51 @@ +import os +import json +from marshmallow import fields, validate +from .. import db, ma + +APPLICATION_TYPE_META_FILE_PATH = 'meta/application_type.meta.json' + + +class ApplicationType(db.Model): + __tablename__ = 'application_type' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), nullable=False) + display_name = db.Column(db.String(100), nullable=False) + display_name_full = db.Column(db.String(250), nullable=False) + applications = db.relationship('Application', backref='app_type', lazy='dynamic') + + @classmethod + def seed(cls): + if cls.is_table_empty(cls): + if os.path.exists(APPLICATION_TYPE_META_FILE_PATH): + with open(APPLICATION_TYPE_META_FILE_PATH) as app_type_meta_json: + data = json.load(app_type_meta_json) + for item in data['types']: + app_type = ApplicationType(name=item['name'], display_name=item['display_name'], display_name_full=item['display_name_full']) + app_type.save() + print("Adding application type metadata: {}".format(app_type)) + else: + # TODO: Add exception + print("Couldn't locate meta file") + else: + print('Table is already filled') + + def save(self): + db.session.add(self) + db.session.commit() + + def is_table_empty(self): + if not self.query.all(): + return True + return False + + def __repr__(self): + return self.display_name_full + + +class ApplicationTypeSchema(ma.Schema): + id = fields.Integer() + name = fields.String(required=True) + display_name = fields.String(required=True) + display_name_full = fields.String(required=True) diff --git a/app/models/genre.py b/app/models/genre.py new file mode 100644 index 0000000..f32d315 --- /dev/null +++ b/app/models/genre.py @@ -0,0 +1,49 @@ +import os +import json +from marshmallow import fields +from .. import db, ma + +GENRE_META_FILE_PATH = 'meta/genre.meta.json' + + +class Genre(db.Model): + __tablename__ = 'genre' + + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), nullable=False) + display_name = db.Column(db.String(250), nullable=False) + applications = db.relationship('Application', backref='genre', lazy='dynamic') + + @classmethod + def seed(cls): + if cls.is_table_empty(cls): + if os.path.exists(GENRE_META_FILE_PATH): + with open(GENRE_META_FILE_PATH) as genre_meta_json: + data = json.load(genre_meta_json) + for item in data['genre']: + genre = Genre(name=item['name'], display_name=item['display_name']) + genre.save() + print("Adding genre metadata: {}".format(genre)) + else: + # TODO: Add exception + print("Couldn't locate meta file") + else: + print('Table is already filled') + + def save(self): + db.session.add(self) + db.session.commit() + + def is_table_empty(self): + if not self.query.all(): + return True + return False + + def __repr__(self): + return self.display_name + + +class GenreSchema(ma.Schema): + id = fields.Integer() + name = fields.String(required=True) + display_name = fields.String(required=True) From 08d68b96fdddc0079904e1f394ee3276a7ae9467 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:36:21 +0530 Subject: [PATCH 18/26] chore(migrations): update migrations :alembic: --- .../{2140400898af_.py => 0a532bf5a8f5_.py} | 10 ++-- migrations/versions/6d973c15bfc3_.py | 40 --------------- migrations/versions/7fbc96b0d7b1_.py | 50 ------------------- migrations/versions/f3daeef03f0a_.py | 30 ----------- 4 files changed, 5 insertions(+), 125 deletions(-) rename migrations/versions/{2140400898af_.py => 0a532bf5a8f5_.py} (92%) delete mode 100644 migrations/versions/6d973c15bfc3_.py delete mode 100644 migrations/versions/7fbc96b0d7b1_.py delete mode 100644 migrations/versions/f3daeef03f0a_.py diff --git a/migrations/versions/2140400898af_.py b/migrations/versions/0a532bf5a8f5_.py similarity index 92% rename from migrations/versions/2140400898af_.py rename to migrations/versions/0a532bf5a8f5_.py index 1985def..bc68b96 100644 --- a/migrations/versions/2140400898af_.py +++ b/migrations/versions/0a532bf5a8f5_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 2140400898af -Revises: 6d973c15bfc3 -Create Date: 2019-04-15 17:54:58.955924 +Revision ID: 0a532bf5a8f5 +Revises: +Create Date: 2019-04-15 18:32:43.823721 """ from alembic import op @@ -10,8 +10,8 @@ # revision identifiers, used by Alembic. -revision = '2140400898af' -down_revision = '6d973c15bfc3' +revision = '0a532bf5a8f5' +down_revision = None branch_labels = None depends_on = None diff --git a/migrations/versions/6d973c15bfc3_.py b/migrations/versions/6d973c15bfc3_.py deleted file mode 100644 index 22bb3a3..0000000 --- a/migrations/versions/6d973c15bfc3_.py +++ /dev/null @@ -1,40 +0,0 @@ -"""empty message - -Revision ID: 6d973c15bfc3 -Revises: 7fbc96b0d7b1 -Create Date: 2019-04-15 17:52:44.231615 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '6d973c15bfc3' -down_revision = '7fbc96b0d7b1' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('application', sa.Column('app_type_id', sa.Integer(), nullable=False)) - op.create_foreign_key('fk_genre_id', 'application', 'genre', ['genre_id'], ['id'], use_alter=True) - op.create_foreign_key('fk_app_type_id', 'application', 'application_type', ['app_type_id'], ['id'], use_alter=True) - op.drop_column('application', 'type_id') - op.add_column('application_type', sa.Column('display_name', sa.String(length=100), nullable=False)) - op.add_column('application_type', sa.Column('display_name_full', sa.String(length=250), nullable=False)) - op.add_column('genre', sa.Column('display_name', sa.String(length=250), nullable=False)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('genre', 'display_name') - op.drop_column('application_type', 'display_name_full') - op.drop_column('application_type', 'display_name') - op.add_column('application', sa.Column('type_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) - op.drop_constraint('fk_app_type_id', 'application', type_='foreignkey') - op.drop_constraint('fk_genre_id', 'application', type_='foreignkey') - op.drop_column('application', 'app_type_id') - # ### end Alembic commands ### diff --git a/migrations/versions/7fbc96b0d7b1_.py b/migrations/versions/7fbc96b0d7b1_.py deleted file mode 100644 index 268ba4e..0000000 --- a/migrations/versions/7fbc96b0d7b1_.py +++ /dev/null @@ -1,50 +0,0 @@ -"""empty message - -Revision ID: 7fbc96b0d7b1 -Revises: -Create Date: 2019-04-15 09:04:38.049734 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7fbc96b0d7b1' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('application', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('type_id', sa.Integer(), nullable=False), - sa.Column('description', sa.String(length=250), nullable=False), - sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), - sa.Column('genre_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], name='fk_genre_id', use_alter=True), - sa.ForeignKeyConstraint(['type_id'], ['application_type.id'], name='fk_application_type_id', use_alter=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('application_type', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('genre', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('genre') - op.drop_table('application_type') - op.drop_table('application') - # ### end Alembic commands ### diff --git a/migrations/versions/f3daeef03f0a_.py b/migrations/versions/f3daeef03f0a_.py deleted file mode 100644 index 39d7d61..0000000 --- a/migrations/versions/f3daeef03f0a_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: f3daeef03f0a -Revises: 2140400898af -Create Date: 2019-04-15 18:05:02.990082 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'f3daeef03f0a' -down_revision = '2140400898af' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_foreign_key('fk_app_type_id', 'application', 'application_type', ['app_type_id'], ['id'], use_alter=True) - op.create_foreign_key('fk_genre_id', 'application', 'genre', ['genre_id'], ['id'], use_alter=True) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('fk_genre_id', 'application', type_='foreignkey') - op.drop_constraint('fk_app_type_id', 'application', type_='foreignkey') - # ### end Alembic commands ### From f87f498c1dd876c0e23e4f70fb16e7e1cf3a64fe Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 18:51:35 +0530 Subject: [PATCH 19/26] chore(views): add validations to application routes --- app/application/views.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/app/application/views.py b/app/application/views.py index c5d324a..49c8d9c 100644 --- a/app/application/views.py +++ b/app/application/views.py @@ -12,15 +12,33 @@ def get_applications(): applications = Application.query.all() applications = applications_schema.dump(applications).data - return jsonify({"status": "success", "data": applications}), 200 + return jsonify({'status': 'success', 'message': None, 'data': applications}), 200 @application.route('/', methods=['POST']) def create_application(): - name = request.json['name'] - app_type = ApplicationType.query.filter_by(name=request.json['app_type']).first() - description = request.json['description'] - genre = Genre.query.filter_by(name=request.json['genre']).first() + json_data = request.get_json(force=True) + + if not json_data: + return jsonify({'status': 'error', 'message': 'No input was provided.'}), 400 + + # Validate and deserialize input + data, errors = application_schema.load(json_data) + if errors: + return jsonify({'status': 'error', 'message': 'Incorrect format of data provided.', 'data': errors}), 422 + + name = data['name'] + app_type = ApplicationType.query.filter_by(name=data['app_type']).first() + description = data['description'] + genre = Genre.query.filter_by(name=data['genre']).first() + + # validate application type + if not app_type: + return {'status': 'error', 'message': 'Invalid Application Type'}, 400 + + # validate genre + if not genre: + return {'status': 'error', 'message': 'Invalid Genre Type'}, 400 new_application = Application(name=name, app_type=app_type, description=description, genre=genre) @@ -29,4 +47,4 @@ def create_application(): result = application_schema.dump(new_application).data - return jsonify({"message": "Created new application {}.".format(name), "data": result}) + return jsonify({'status': 'success', 'message': 'Created new application {}.'.format(name), 'data': result}), 201 From 9de44eae0b6fb5a34c9529dd1be483a9b2e97676 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 21:22:32 +0530 Subject: [PATCH 20/26] feat(views): implement basic application routes :sparkles: --- app/application/views.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/app/application/views.py b/app/application/views.py index 49c8d9c..3945316 100644 --- a/app/application/views.py +++ b/app/application/views.py @@ -1,22 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# (c) Copyright 2019 Brion Mario. +# (c) This file is part of the CSSI REST API and is made available under MIT license. +# (c) For more information, see https://github.com/brionmario/cssi-api/blob/master/LICENSE.txt +# (c) Please forward any queries to the given email address. email: brion@apareciumlabs.com + +"""Application routes module + +This modules contains all the different routes to interact with applications. + +Authors: + Brion Mario + +""" + from flask import Blueprint, jsonify, request from app.models import Application, ApplicationType, ApplicationSchema, Genre from .. import db application = Blueprint('application', __name__) -applications_schema = ApplicationSchema(many=True) -application_schema = ApplicationSchema() +application_schema = ApplicationSchema(strict=True) +applications_schema = ApplicationSchema(many=True, strict=True) @application.route('/', methods=['GET']) def get_applications(): + """Get a list of all the Applications""" applications = Application.query.all() - applications = applications_schema.dump(applications).data - return jsonify({'status': 'success', 'message': None, 'data': applications}), 200 + result = applications_schema.dump(applications).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + +@application.route('/', methods=['GET']) +def get_application(id): + """Get info on an Applications when an id is passed in""" + application = Application.query.get(id) + result = application_schema.dump(application).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 @application.route('/', methods=['POST']) def create_application(): + """Create a new Application""" json_data = request.get_json(force=True) if not json_data: From 77e49ae727da0c90da9029961d539eb7e8f2212c Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 21:44:17 +0530 Subject: [PATCH 21/26] refactor(app): change folder structure :truck: --- app/__init__.py | 4 ++-- app/application/__init__.py | 1 - app/main/__init__.py | 1 - app/routes/__init__.py | 2 ++ app/{application/views.py => routes/application.py} | 2 +- app/{main/views.py => routes/main.py} | 0 6 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 app/application/__init__.py delete mode 100644 app/main/__init__.py create mode 100644 app/routes/__init__.py rename app/{application/views.py => routes/application.py} (99%) rename app/{main/views.py => routes/main.py} (100%) diff --git a/app/__init__.py b/app/__init__.py index 9cd13c3..17c2cdc 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -27,10 +27,10 @@ def create_app(config_name): db.init_app(app) # Create app blueprints - from .main import main as main_blueprint + from app.routes.main import main as main_blueprint app.register_blueprint(main_blueprint, url_prefix=root + '/') - from .application import application as application_blueprint + from app.routes.application import application as application_blueprint app.register_blueprint(application_blueprint, url_prefix=root + '/application') return app diff --git a/app/application/__init__.py b/app/application/__init__.py deleted file mode 100644 index 5007883..0000000 --- a/app/application/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from app.application.views import application # noqa diff --git a/app/main/__init__.py b/app/main/__init__.py deleted file mode 100644 index 59dee14..0000000 --- a/app/main/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from app.main.views import main # noqa diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 0000000..b48234a --- /dev/null +++ b/app/routes/__init__.py @@ -0,0 +1,2 @@ +from app.routes.main import main # noqa +from app.routes.application import application # noqa diff --git a/app/application/views.py b/app/routes/application.py similarity index 99% rename from app/application/views.py rename to app/routes/application.py index 3945316..a21f598 100644 --- a/app/application/views.py +++ b/app/routes/application.py @@ -17,7 +17,7 @@ from flask import Blueprint, jsonify, request from app.models import Application, ApplicationType, ApplicationSchema, Genre -from .. import db +from app import db application = Blueprint('application', __name__) diff --git a/app/main/views.py b/app/routes/main.py similarity index 100% rename from app/main/views.py rename to app/routes/main.py From a2f368f1807ad4f4e5bc00edf88c5fe1985cb53c Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Mon, 15 Apr 2019 22:30:51 +0530 Subject: [PATCH 22/26] refactor(app): move routes to v1 folder :truck: --- app/__init__.py | 4 ++-- app/routes/__init__.py | 3 +-- app/routes/v1/__init__.py | 2 ++ app/routes/{ => v1}/application.py | 0 app/routes/{ => v1}/main.py | 0 5 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 app/routes/v1/__init__.py rename app/routes/{ => v1}/application.py (100%) rename app/routes/{ => v1}/main.py (100%) diff --git a/app/__init__.py b/app/__init__.py index 17c2cdc..66c4caf 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -27,10 +27,10 @@ def create_app(config_name): db.init_app(app) # Create app blueprints - from app.routes.main import main as main_blueprint + from app.routes.v1 import main as main_blueprint app.register_blueprint(main_blueprint, url_prefix=root + '/') - from app.routes.application import application as application_blueprint + from app.routes.v1 import application as application_blueprint app.register_blueprint(application_blueprint, url_prefix=root + '/application') return app diff --git a/app/routes/__init__.py b/app/routes/__init__.py index b48234a..8d55024 100644 --- a/app/routes/__init__.py +++ b/app/routes/__init__.py @@ -1,2 +1 @@ -from app.routes.main import main # noqa -from app.routes.application import application # noqa +from app.routes.v1 import * # noqa diff --git a/app/routes/v1/__init__.py b/app/routes/v1/__init__.py new file mode 100644 index 0000000..0d00b41 --- /dev/null +++ b/app/routes/v1/__init__.py @@ -0,0 +1,2 @@ +from app.routes.v1.application import application # noqa +from app.routes.v1.main import main # noqa diff --git a/app/routes/application.py b/app/routes/v1/application.py similarity index 100% rename from app/routes/application.py rename to app/routes/v1/application.py diff --git a/app/routes/main.py b/app/routes/v1/main.py similarity index 100% rename from app/routes/main.py rename to app/routes/v1/main.py From adf5311b1c4793109de1d77aaae0872253680c2c Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 00:03:09 +0530 Subject: [PATCH 23/26] chore(app): add unique identifier and developer to applications model --- app/models/application.py | 32 ++++--------------- app/models/application_type.py | 2 +- app/routes/application.py | 10 ++++-- .../{0a532bf5a8f5_.py => 1ee028de452a_.py} | 12 ++++--- 4 files changed, 21 insertions(+), 35 deletions(-) rename migrations/versions/{0a532bf5a8f5_.py => 1ee028de452a_.py} (80%) diff --git a/app/models/application.py b/app/models/application.py index c242e51..876b4c5 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -1,27 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# (c) Copyright 2019 Brion Mario. -# (c) This file is part of the CSSI REST API and is made available under MIT license. -# (c) For more information, see https://github.com/brionmario/cssi-api/blob/master/LICENSE.txt -# (c) Please forward any queries to the given email address. email: brion@apareciumlabs.com - -"""Models and Schemas for Applications - -This module handles contains the different db models and schemas -for applications. - -Attributes: - GENRE_META_FILE_PATH (str): Path for the file containing genre meta - APPLICATION_TYPE_META_FILE_PATH (str): Path for the file containing application type meta - -Authors: - Brion Mario - -""" - -import os -import json from marshmallow import fields, validate from .. import db, ma @@ -31,7 +7,9 @@ class Application(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) - app_type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', use_alter=True, name='fk_app_type_id'), nullable=False) + identifier = db.Column(db.String(100), nullable=False) + developer = db.Column(db.String(100), nullable=False) + type_id = db.Column(db.Integer, db.ForeignKey('application_type.id', use_alter=True, name='fk_type_id'), nullable=False) description = db.Column(db.String(250), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', use_alter=True, name='fk_genre_id'), nullable=False) @@ -43,7 +21,9 @@ def __repr__(self): class ApplicationSchema(ma.Schema): id = fields.Integer(dump_only=True) name = fields.String(required=True, validate=validate.Length(1, 100)) - app_type = fields.String(required=True) + identifier = fields.String() + developer = fields.String(required=True, validate=validate.Length(1, 100)) + type = fields.String(required=True) description = fields.String(required=True, validate=validate.Length(1, 250)) creation_date = fields.DateTime() genre = fields.String(required=True) diff --git a/app/models/application_type.py b/app/models/application_type.py index 085dae2..a161911 100644 --- a/app/models/application_type.py +++ b/app/models/application_type.py @@ -13,7 +13,7 @@ class ApplicationType(db.Model): name = db.Column(db.String(50), nullable=False) display_name = db.Column(db.String(100), nullable=False) display_name_full = db.Column(db.String(250), nullable=False) - applications = db.relationship('Application', backref='app_type', lazy='dynamic') + applications = db.relationship('Application', backref='type', lazy='dynamic') @classmethod def seed(cls): diff --git a/app/routes/application.py b/app/routes/application.py index a21f598..2cfeec4 100644 --- a/app/routes/application.py +++ b/app/routes/application.py @@ -15,6 +15,7 @@ """ +import uuid from flask import Blueprint, jsonify, request from app.models import Application, ApplicationType, ApplicationSchema, Genre from app import db @@ -55,19 +56,22 @@ def create_application(): return jsonify({'status': 'error', 'message': 'Incorrect format of data provided.', 'data': errors}), 422 name = data['name'] - app_type = ApplicationType.query.filter_by(name=data['app_type']).first() + identifier = str(uuid.uuid4().hex) + developer = data['developer'] + type = ApplicationType.query.filter_by(name=data['type']).first() description = data['description'] genre = Genre.query.filter_by(name=data['genre']).first() + print(identifier) # validate application type - if not app_type: + if not type: return {'status': 'error', 'message': 'Invalid Application Type'}, 400 # validate genre if not genre: return {'status': 'error', 'message': 'Invalid Genre Type'}, 400 - new_application = Application(name=name, app_type=app_type, description=description, genre=genre) + new_application = Application(name=name, identifier=identifier, developer=developer, type=type, description=description, genre=genre) db.session.add(new_application) db.session.commit() diff --git a/migrations/versions/0a532bf5a8f5_.py b/migrations/versions/1ee028de452a_.py similarity index 80% rename from migrations/versions/0a532bf5a8f5_.py rename to migrations/versions/1ee028de452a_.py index bc68b96..8c8f074 100644 --- a/migrations/versions/0a532bf5a8f5_.py +++ b/migrations/versions/1ee028de452a_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 0a532bf5a8f5 +Revision ID: 1ee028de452a Revises: -Create Date: 2019-04-15 18:32:43.823721 +Create Date: 2019-04-15 23:52:00.910648 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '0a532bf5a8f5' +revision = '1ee028de452a' down_revision = None branch_labels = None depends_on = None @@ -21,12 +21,14 @@ def upgrade(): op.create_table('application', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('app_type_id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=100), nullable=False), + sa.Column('developer', sa.String(length=100), nullable=False), + sa.Column('type_id', sa.Integer(), nullable=False), sa.Column('description', sa.String(length=250), nullable=False), sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), sa.Column('genre_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['app_type_id'], ['application_type.id'], name='fk_app_type_id', use_alter=True), sa.ForeignKeyConstraint(['genre_id'], ['genre.id'], name='fk_genre_id', use_alter=True), + sa.ForeignKeyConstraint(['type_id'], ['application_type.id'], name='fk_type_id', use_alter=True), sa.PrimaryKeyConstraint('id') ) op.create_table('application_type', From db9f8098863252bd06cec33fc0d68cf395f22b2f Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 00:59:12 +0530 Subject: [PATCH 24/26] chore(models): add questionnaire and session models --- app/models/application.py | 1 + app/models/questionnaire.py | 23 +++++++++++++++++++++++ app/models/session.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 app/models/questionnaire.py create mode 100644 app/models/session.py diff --git a/app/models/application.py b/app/models/application.py index 876b4c5..733c488 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -13,6 +13,7 @@ class Application(db.Model): description = db.Column(db.String(250), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', use_alter=True, name='fk_genre_id'), nullable=False) + sessions = db.relationship('Session', backref='application', lazy='dynamic') def __repr__(self): return self.name diff --git a/app/models/questionnaire.py b/app/models/questionnaire.py new file mode 100644 index 0000000..092eeca --- /dev/null +++ b/app/models/questionnaire.py @@ -0,0 +1,23 @@ +from marshmallow import fields, validate +from .. import db, ma + + +class Questionnaire(db.Model): + __tablename__ = 'questionnaire' + + id = db.Column(db.Integer, primary_key=True) + pre = db.Column(db.String, nullable=False) + post = db.Column(db.String, nullable=False) + creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) + session_id = db.Column(db.Integer, db.ForeignKey('session.id', use_alter=True, name='fk_session_id'), nullable=False) + session = db.relationship('Session', backref='questionnaire', lazy='dynamic') + + def __repr__(self): + return self.id + + +class ApplicationSchema(ma.Schema): + id = fields.Integer(dump_only=True) + pre = fields.String(required=True, validate=validate.Length(1)) + post = fields.String(required=False) + creation_date = fields.DateTime() diff --git a/app/models/session.py b/app/models/session.py new file mode 100644 index 0000000..a5309c6 --- /dev/null +++ b/app/models/session.py @@ -0,0 +1,29 @@ +from marshmallow import fields, validate +from .. import db, ma + + +class Session(db.Model): + __tablename__ = 'session' + + id = db.Column(db.Integer, primary_key=True) + app_id = db.Column(db.Integer, db.ForeignKey('application.id', use_alter=True, name='fk_app_id'), nullable=False) + creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) + expected_emotions = db.Column(db.String, nullable=False) + cssi_score = db.Column(db.Float, nullable=False, default=0) + latency_scores = db.Column(db.String, nullable=False, default={}) + total_latency_score = db.Column(db.Float, nullable=False, default=0) + sentiment_scores = db.Column(db.String, nullable=False, default={}) + total_sentiment_score = db.Column(db.Float, nullable=False, default=0) + questionnaire_id = db.Column(db.Integer, db.ForeignKey('questionnaire.id', use_alter=True, name='fk_questionnaire_id'), nullable=False) + questionnaire = db.relationship('Questionnaire', backref='session', lazy='dynamic') + questionnaire_scores = db.Column(db.String, nullable=True, default={}) + total_questionnaire_score = db.Column(db.Float, nullable=False, default=0) + + def __repr__(self): + return self.name + + +class SessionSchema(ma.Schema): + id = fields.Integer(dump_only=True) + creation_date = fields.DateTime() + expected_emotions = fields.String(required=True) From 97392b3937410197a98cd0c33d8bca4468155ee8 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 05:12:47 +0530 Subject: [PATCH 25/26] feat(core): implement sessions related functionality :sparkes: --- app/__init__.py | 8 ++++- app/models/__init__.py | 2 ++ app/models/application.py | 18 +++++++--- app/models/application_type.py | 7 +++- app/models/genre.py | 6 +++- app/models/questionnaire.py | 25 ++++++++----- app/models/session.py | 24 ++++++++----- app/routes/v1/__init__.py | 2 ++ app/routes/v1/application.py | 12 +++---- app/routes/v1/questionnaire.py | 58 ++++++++++++++++++++++++++++++ app/routes/v1/session.py | 65 ++++++++++++++++++++++++++++++++++ 11 files changed, 198 insertions(+), 29 deletions(-) create mode 100644 app/routes/v1/questionnaire.py create mode 100644 app/routes/v1/session.py diff --git a/app/__init__.py b/app/__init__.py index 66c4caf..eed71a8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -21,7 +21,7 @@ def create_app(config_name): root = CONFIG[config_name].APPLICATION_ROOT # flask migrate doesn't recognize the tables without this import - from app.models import Application, Genre, ApplicationType + from app.models import Application, Genre, ApplicationType, Session, Questionnaire # Set up extensions db.init_app(app) @@ -33,4 +33,10 @@ def create_app(config_name): from app.routes.v1 import application as application_blueprint app.register_blueprint(application_blueprint, url_prefix=root + '/application') + from app.routes.v1 import session as session_blueprint + app.register_blueprint(session_blueprint, url_prefix=root + '/session') + + from app.routes.v1 import questionnaire as questionnaire_blueprint + app.register_blueprint(questionnaire_blueprint, url_prefix=root + '/questionnaire') + return app diff --git a/app/models/__init__.py b/app/models/__init__.py index bdfa087..2316650 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -6,3 +6,5 @@ from .application import * # noqa from .application_type import * # noqa from .genre import * # noqa +from .session import * # noqa +from .questionnaire import * # noqa diff --git a/app/models/application.py b/app/models/application.py index 733c488..50b2754 100644 --- a/app/models/application.py +++ b/app/models/application.py @@ -1,4 +1,6 @@ from marshmallow import fields, validate +from .application_type import ApplicationTypeSchema +from .genre import GenreSchema from .. import db, ma @@ -13,10 +15,18 @@ class Application(db.Model): description = db.Column(db.String(250), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) genre_id = db.Column(db.Integer, db.ForeignKey('genre.id', use_alter=True, name='fk_genre_id'), nullable=False) - sessions = db.relationship('Session', backref='application', lazy='dynamic') + sessions = db.relationship('Session', backref='app', lazy='dynamic') + + def __init__(self, name, identifier, developer, type, description, genre): + self.name = name + self.identifier = identifier + self.developer = developer + self.type = type + self.description = description + self.genre = genre def __repr__(self): - return self.name + return '' % self.id class ApplicationSchema(ma.Schema): @@ -24,7 +34,7 @@ class ApplicationSchema(ma.Schema): name = fields.String(required=True, validate=validate.Length(1, 100)) identifier = fields.String() developer = fields.String(required=True, validate=validate.Length(1, 100)) - type = fields.String(required=True) + type = fields.Nested(ApplicationTypeSchema, dump_only=True) description = fields.String(required=True, validate=validate.Length(1, 250)) creation_date = fields.DateTime() - genre = fields.String(required=True) + genre = fields.Nested(GenreSchema, dump_only=True) diff --git a/app/models/application_type.py b/app/models/application_type.py index a161911..c93015a 100644 --- a/app/models/application_type.py +++ b/app/models/application_type.py @@ -15,6 +15,11 @@ class ApplicationType(db.Model): display_name_full = db.Column(db.String(250), nullable=False) applications = db.relationship('Application', backref='type', lazy='dynamic') + def __init__(self, name, display_name, display_name_full): + self.name = name + self.display_name = display_name + self.display_name_full = display_name_full + @classmethod def seed(cls): if cls.is_table_empty(cls): @@ -41,7 +46,7 @@ def is_table_empty(self): return False def __repr__(self): - return self.display_name_full + return '' % self.id class ApplicationTypeSchema(ma.Schema): diff --git a/app/models/genre.py b/app/models/genre.py index f32d315..89a0ecf 100644 --- a/app/models/genre.py +++ b/app/models/genre.py @@ -14,6 +14,10 @@ class Genre(db.Model): display_name = db.Column(db.String(250), nullable=False) applications = db.relationship('Application', backref='genre', lazy='dynamic') + def __init__(self, name, display_name): + self.name = name + self.display_name = display_name + @classmethod def seed(cls): if cls.is_table_empty(cls): @@ -40,7 +44,7 @@ def is_table_empty(self): return False def __repr__(self): - return self.display_name + return '' % self.id class GenreSchema(ma.Schema): diff --git a/app/models/questionnaire.py b/app/models/questionnaire.py index 092eeca..3ae3522 100644 --- a/app/models/questionnaire.py +++ b/app/models/questionnaire.py @@ -6,18 +6,27 @@ class Questionnaire(db.Model): __tablename__ = 'questionnaire' id = db.Column(db.Integer, primary_key=True) - pre = db.Column(db.String, nullable=False) - post = db.Column(db.String, nullable=False) + pre = db.Column(db.JSON, nullable=False) + post = db.Column(db.JSON, nullable=False, default={}) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) - session_id = db.Column(db.Integer, db.ForeignKey('session.id', use_alter=True, name='fk_session_id'), nullable=False) - session = db.relationship('Session', backref='questionnaire', lazy='dynamic') + session = db.relationship("Session", uselist=False, backref="questionnaire") + + def __init__(self, pre, post): + self.pre = pre + self.post = post def __repr__(self): - return self.id + return '' % self.id + + +class SymptomSchema(ma.Schema): + name = fields.String(required=False) + display_name = fields.String(required=False) + score = fields.String(required=False) -class ApplicationSchema(ma.Schema): +class QuestionnaireSchema(ma.Schema): id = fields.Integer(dump_only=True) - pre = fields.String(required=True, validate=validate.Length(1)) - post = fields.String(required=False) + pre = fields.List(fields.Nested(SymptomSchema), dump_only=True) + post = fields.List(fields.Nested(SymptomSchema), dump_only=True) creation_date = fields.DateTime() diff --git a/app/models/session.py b/app/models/session.py index a5309c6..d71089b 100644 --- a/app/models/session.py +++ b/app/models/session.py @@ -1,5 +1,7 @@ from marshmallow import fields, validate from .. import db, ma +from .application import ApplicationSchema +from .questionnaire import QuestionnaireSchema class Session(db.Model): @@ -8,22 +10,28 @@ class Session(db.Model): id = db.Column(db.Integer, primary_key=True) app_id = db.Column(db.Integer, db.ForeignKey('application.id', use_alter=True, name='fk_app_id'), nullable=False) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(), nullable=False) - expected_emotions = db.Column(db.String, nullable=False) + expected_emotions = db.Column(db.JSON, nullable=False) + questionnaire_id = db.Column(db.Integer, db.ForeignKey('questionnaire.id', use_alter=True, name='fk_questionnaire_id'), nullable=False) cssi_score = db.Column(db.Float, nullable=False, default=0) - latency_scores = db.Column(db.String, nullable=False, default={}) + latency_scores = db.Column(db.JSON, nullable=False, default={}) total_latency_score = db.Column(db.Float, nullable=False, default=0) - sentiment_scores = db.Column(db.String, nullable=False, default={}) + sentiment_scores = db.Column(db.JSON, nullable=False, default={}) total_sentiment_score = db.Column(db.Float, nullable=False, default=0) - questionnaire_id = db.Column(db.Integer, db.ForeignKey('questionnaire.id', use_alter=True, name='fk_questionnaire_id'), nullable=False) - questionnaire = db.relationship('Questionnaire', backref='session', lazy='dynamic') - questionnaire_scores = db.Column(db.String, nullable=True, default={}) + questionnaire_scores = db.Column(db.JSON, nullable=True, default={}) total_questionnaire_score = db.Column(db.Float, nullable=False, default=0) + def __init__(self, app, expected_emotions, questionnaire): + self.app = app + self.expected_emotions = expected_emotions + self.questionnaire = questionnaire + def __repr__(self): - return self.name + return '' % self.id class SessionSchema(ma.Schema): id = fields.Integer(dump_only=True) creation_date = fields.DateTime() - expected_emotions = fields.String(required=True) + expected_emotions = fields.List(fields.String(), dump_only=True) + app = fields.Nested(ApplicationSchema, dump_only=True) + questionnaire = fields.Nested(QuestionnaireSchema, dump_only=True) diff --git a/app/routes/v1/__init__.py b/app/routes/v1/__init__.py index 0d00b41..ebfd221 100644 --- a/app/routes/v1/__init__.py +++ b/app/routes/v1/__init__.py @@ -1,2 +1,4 @@ from app.routes.v1.application import application # noqa from app.routes.v1.main import main # noqa +from app.routes.v1.session import session # noqa +from app.routes.v1.questionnaire import questionnaire # noqa diff --git a/app/routes/v1/application.py b/app/routes/v1/application.py index 2cfeec4..f1f60dc 100644 --- a/app/routes/v1/application.py +++ b/app/routes/v1/application.py @@ -45,6 +45,7 @@ def get_application(id): @application.route('/', methods=['POST']) def create_application(): """Create a new Application""" + json_data = request.get_json(force=True) if not json_data: @@ -55,14 +56,13 @@ def create_application(): if errors: return jsonify({'status': 'error', 'message': 'Incorrect format of data provided.', 'data': errors}), 422 - name = data['name'] + name = request.json['name'] identifier = str(uuid.uuid4().hex) - developer = data['developer'] - type = ApplicationType.query.filter_by(name=data['type']).first() - description = data['description'] - genre = Genre.query.filter_by(name=data['genre']).first() + developer = request.json['developer'] + type = ApplicationType.query.filter_by(id=request.json['type']).first() + description = request.json['description'] + genre = Genre.query.filter_by(id=request.json['genre']).first() - print(identifier) # validate application type if not type: return {'status': 'error', 'message': 'Invalid Application Type'}, 400 diff --git a/app/routes/v1/questionnaire.py b/app/routes/v1/questionnaire.py new file mode 100644 index 0000000..4acb097 --- /dev/null +++ b/app/routes/v1/questionnaire.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# (c) Copyright 2019 Brion Mario. +# (c) This file is part of the CSSI REST API and is made available under MIT license. +# (c) For more information, see https://github.com/brionmario/cssi-api/blob/master/LICENSE.txt +# (c) Please forward any queries to the given email address. email: brion@apareciumlabs.com + +"""Application routes module + +This modules contains all the different routes to interact with applications. + +Authors: + Brion Mario + +""" + +import uuid +from flask import Blueprint, jsonify, request +from app.models import Questionnaire, ApplicationType, QuestionnaireSchema, Genre +from app import db + +questionnaire = Blueprint('questionnaire', __name__) + +questionnaire_schema = QuestionnaireSchema(strict=True) +questionnaires_schema = QuestionnaireSchema(many=True, strict=True) + + +@questionnaire.route('/', methods=['GET']) +def get_questionnaire_list(): + """Get a list of all the Questionnaire""" + questionnaires = Questionnaire.query.all() + result = questionnaires_schema.dump(questionnaires).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + +@questionnaire.route('/', methods=['GET']) +def get_questionnaire(id): + """Get questionnaire when an id is passed in""" + questionnaire = Questionnaire.query.get(id) + result = questionnaire_schema.dump(questionnaire).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + +@questionnaire.route('/', methods=['POST']) +def create_questionnaire(): + """Create a new Questionnaire""" + pre = request.json['pre'] + post = request.json['post'] + + new_questionnaire = Questionnaire(pre=pre, post=post) + + db.session.add(new_questionnaire) + db.session.commit() + + result = questionnaire_schema.dump(new_questionnaire).data + + return jsonify({'status': 'success', 'message': 'Created new questionnaire', 'data': result}), 201 diff --git a/app/routes/v1/session.py b/app/routes/v1/session.py new file mode 100644 index 0000000..0930393 --- /dev/null +++ b/app/routes/v1/session.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# (c) Copyright 2019 Brion Mario. +# (c) This file is part of the CSSI REST API and is made available under MIT license. +# (c) For more information, see https://github.com/brionmario/cssi-api/blob/master/LICENSE.txt +# (c) Please forward any queries to the given email address. email: brion@apareciumlabs.com + +"""Session routes module + +This modules contains all the different routes to interact with Sessions. + +Authors: + Brion Mario + +""" + +from flask import Blueprint, jsonify, request +from app.models import Session, SessionSchema, Application, Questionnaire +from app import db + +session = Blueprint('session', __name__) + +session_schema = SessionSchema(strict=True) +sessions_schema = SessionSchema(many=True, strict=True) + + +@session.route('/', methods=['GET']) +def get_sessions_list(): + """Get a list of all the sessions""" + sessions = Session.query.all() + result = sessions_schema.dump(sessions).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + +@session.route('/', methods=['GET']) +def get_session(id): + """Get info on a session when an id is passed in""" + session = Session.query.get(id) + result = sessions_schema.dump(session).data + return jsonify({'status': 'success', 'message': None, 'data': result}), 200 + + +@session.route('/', methods=['POST']) +def create_session(): + """Create a new Session""" + + app = Application.query.filter_by(id=request.json['app']).first() + questionnaire = Questionnaire.query.filter_by(id=request.json['questionnaire_id']).first() + expected_emotions = request.json['expected_emotions'] + + print(questionnaire) + + # validate application type + if not app: + return {'status': 'error', 'message': 'Invalid application.'}, 400 + + new_session = Session(app=app, expected_emotions=expected_emotions, questionnaire=questionnaire) + + db.session.add(new_session) + db.session.commit() + + result = session_schema.dump(new_session).data + + return jsonify({'status': 'success', 'message': 'Created new session for application with id of {}.'.format(request.json['app']), 'data': result}), 201 From 79ec5c63b78a57ec0e7a3cd07de2aa17f585dfa3 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 16 Apr 2019 05:13:06 +0530 Subject: [PATCH 26/26] chore(migrations): update migrations :alembic: --- migrations/versions/89ee48680e79_.py | 42 +++++++++++++++++++ .../{1ee028de452a_.py => cb018c771a53_.py} | 34 +++++++++++++-- 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 migrations/versions/89ee48680e79_.py rename migrations/versions/{1ee028de452a_.py => cb018c771a53_.py} (52%) diff --git a/migrations/versions/89ee48680e79_.py b/migrations/versions/89ee48680e79_.py new file mode 100644 index 0000000..4f39656 --- /dev/null +++ b/migrations/versions/89ee48680e79_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: 89ee48680e79 +Revises: cb018c771a53 +Create Date: 2019-04-16 04:22:10.614121 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '89ee48680e79' +down_revision = 'cb018c771a53' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_foreign_key('fk_type_id', 'application', 'application_type', ['type_id'], ['id'], use_alter=True) + op.create_foreign_key('fk_genre_id', 'application', 'genre', ['genre_id'], ['id'], use_alter=True) + op.alter_column('questionnaire', 'post', + existing_type=mysql.TEXT(), + nullable=True) + op.drop_column('questionnaire', 'session_id') + op.create_foreign_key('fk_app_id', 'session', 'application', ['app_id'], ['id'], use_alter=True) + op.create_foreign_key('fk_questionnaire_id', 'session', 'questionnaire', ['questionnaire_id'], ['id'], use_alter=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('fk_questionnaire_id', 'session', type_='foreignkey') + op.drop_constraint('fk_app_id', 'session', type_='foreignkey') + op.add_column('questionnaire', sa.Column('session_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) + op.alter_column('questionnaire', 'post', + existing_type=mysql.TEXT(), + nullable=False) + op.drop_constraint('fk_genre_id', 'application', type_='foreignkey') + op.drop_constraint('fk_type_id', 'application', type_='foreignkey') + # ### end Alembic commands ### diff --git a/migrations/versions/1ee028de452a_.py b/migrations/versions/cb018c771a53_.py similarity index 52% rename from migrations/versions/1ee028de452a_.py rename to migrations/versions/cb018c771a53_.py index 8c8f074..bd2e305 100644 --- a/migrations/versions/1ee028de452a_.py +++ b/migrations/versions/cb018c771a53_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 1ee028de452a +Revision ID: cb018c771a53 Revises: -Create Date: 2019-04-15 23:52:00.910648 +Create Date: 2019-04-16 01:05:44.197617 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '1ee028de452a' +revision = 'cb018c771a53' down_revision = None branch_labels = None depends_on = None @@ -44,11 +44,39 @@ def upgrade(): sa.Column('display_name', sa.String(length=250), nullable=False), sa.PrimaryKeyConstraint('id') ) + op.create_table('questionnaire', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('pre', sa.TEXT(), nullable=False), + sa.Column('post', sa.TEXT(), nullable=False), + sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.Column('session_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['session_id'], ['session.id'], name='fk_session_id', use_alter=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('session', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('app_id', sa.Integer(), nullable=False), + sa.Column('creation_date', sa.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False), + sa.Column('expected_emotions', sa.TEXT(), nullable=False), + sa.Column('cssi_score', sa.Float(), nullable=False), + sa.Column('latency_scores', sa.TEXT(), nullable=False), + sa.Column('total_latency_score', sa.Float(), nullable=False), + sa.Column('sentiment_scores', sa.TEXT(), nullable=False), + sa.Column('total_sentiment_score', sa.Float(), nullable=False), + sa.Column('questionnaire_id', sa.Integer(), nullable=False), + sa.Column('questionnaire_scores', sa.TEXT(), nullable=True), + sa.Column('total_questionnaire_score', sa.Float(), nullable=False), + sa.ForeignKeyConstraint(['app_id'], ['application.id'], name='fk_app_id', use_alter=True), + sa.ForeignKeyConstraint(['questionnaire_id'], ['questionnaire.id'], name='fk_questionnaire_id', use_alter=True), + sa.PrimaryKeyConstraint('id') + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('session') + op.drop_table('questionnaire') op.drop_table('genre') op.drop_table('application_type') op.drop_table('application')