From 55c03ef8082740444892ec8ef4f8616472b8dbe0 Mon Sep 17 00:00:00 2001 From: Jose Daniel Contreras Date: Mon, 3 Apr 2017 13:41:54 -0400 Subject: [PATCH] Revert "Merge master into production (#20)" This reverts commit bce5d2d3008dfaab7fcf4274e48ab058aae621b8. --- .pylintrc | 407 ------------------------------ Dockerfile | 6 - app/__init__.py | 3 + app/config/development.default.py | 15 +- app/config/local.default.py | 29 --- app/config/staging.default.py | 2 +- app/config/uwsgi.ini | 9 +- app/controllers/base.py | 2 +- app/controllers/project.py | 9 +- app/forms/project.py | 1 - app/lib/database.py | 67 +---- app/models/base.py | 51 +--- app/models/client.py | 4 +- app/models/contact.py | 10 +- app/models/document.py | 6 +- app/models/note.py | 4 +- app/models/place.py | 26 +- app/models/project.py | 18 +- app/views/base.py | 1 - docs/API/api.md | 56 ---- requirements-dev.txt | 6 - requirements.txt | 3 + run.py | 2 +- wsgi.py | 3 + 24 files changed, 74 insertions(+), 666 deletions(-) delete mode 100644 .pylintrc delete mode 100644 app/config/local.default.py delete mode 100644 docs/API/api.md delete mode 100644 requirements-dev.txt diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 71e9450..0000000 --- a/.pylintrc +++ /dev/null @@ -1,407 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins=pylint_flask - -# Use multiple processes to speed up Pylint. -jobs=1 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. This option is deprecated -# and it will be removed in Pylint 2.0. -optimize-ast=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=import-star-module-level,coerce-builtin,unicode-builtin,no-absolute-import,input-builtin,reduce-builtin,cmp-builtin,basestring-builtin,cmp-method,setslice-method,unichr-builtin,backtick,old-octal-literal,old-ne-operator,oct-method,map-builtin-not-iterating,raw_input-builtin,apply-builtin,range-builtin-not-iterating,useless-suppression,dict-view-method,filter-builtin-not-iterating,unpacking-in-except,getslice-method,intern-builtin,raising-string,standarderror-builtin,buffer-builtin,coerce-method,old-raise-syntax,file-builtin,dict-iter-method,metaclass-assignment,long-suffix,next-method-called,suppressed-message,long-builtin,delslice-method,execfile-builtin,old-division,nonzero-method,using-cmp-argument,xrange-builtin,hex-method,indexing-exception,print-statement,round-builtin,reload-builtin,parameter-unpacking,zip-builtin-not-iterating - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/Dockerfile b/Dockerfile index 5611b79..a80685c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,10 +11,6 @@ ENV UWSGIREQ=/var/log/uwsgi.req.log # Custom environment variables. These change from project to project. ARG DOMAIN ENV DOMAIN=${DOMAIN} -ARG NUM_PROCESSES -ENV NUM_PROCESSES=${NUM_PROCESSES} -ARG NUM_THREADS -ENV NUM_THREADS=${NUM_THREADS} # Add the nginx reposoitory. We need 1.8 in order to support adding CORS headers to error responses. RUN apt-get update @@ -58,8 +54,6 @@ RUN \ echo "Copying environment variables from $1 to $2."; \ sed -e "s/\$DOMAIN/$DOMAIN/g" \ -e "s@\$CODEROOT@$CODEROOT@g" \ - -e "s@\$NUM_PROCESSES@$NUM_PROCESSES@g" \ - -e "s@\$NUM_THREADS@$NUM_THREADS@g" \ -e "s@\$NGINXERR@$NGINXERR@g" \ -e "s@\$NGINXREQ@$NGINXREQ@g" \ -e "s@\$UWSGIERR@$UWSGIERR@g" \ diff --git a/app/__init__.py b/app/__init__.py index 990886a..cbd49e1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -24,4 +24,7 @@ def create_app(config): from .lib import session session.register(app) + with app.app_context(): + database.db.create_all() + return app diff --git a/app/config/development.default.py b/app/config/development.default.py index 8d06722..5a20208 100644 --- a/app/config/development.default.py +++ b/app/config/development.default.py @@ -1,23 +1,20 @@ -import os - - SQLALCHEMY_TRACK_MODIFICATIONS = False -SQLALCHEMY_DATABASE_URI = os.environ['DBURI'] +SQLALCHEMY_DATABASE_URI = 'postgres:///projectservice' -REDIS_URI = os.environ['REDISURI'] +REDIS_URI = 'redis://127.0.0.1:6379/' DEBUG = True SERVICE_CONFIG = { - 'app_key': os.environ['APP_KEY'], - 'app_secret': os.environ['APP_SECRET'], + 'app_key': '$APP_KEY', + 'app_secret': '$APP_SECRET', 'headers': { 'app_key': 'x-blocpower-app-key', 'app_token': 'x-blocpower-app-token', 'app_secret': 'x-blocpower-app-secret'}, 'urls': { - 'app': 'http://dev.appservice.blocpower.io/', - 'user': 'http://dev.userservice.blocpower.io/'} + 'app': 'http://127.0.0.1:5400', + 'user': 'http://127.0.0.1:5401'} } # AppService diff --git a/app/config/local.default.py b/app/config/local.default.py deleted file mode 100644 index 9c69a5f..0000000 --- a/app/config/local.default.py +++ /dev/null @@ -1,29 +0,0 @@ -SQLALCHEMY_TRACK_MODIFICATIONS = False -SQLALCHEMY_DATABASE_URI = 'postgres:///customer' - -REDIS_URI = 'redis://127.0.0.1:6379/' - -DEBUG = True - -SERVICE_CONFIG = { - 'app_key': '$APP_KEY', - 'app_secret': '$APP_SECRET', - 'headers': { - 'app_key': 'x-blocpower-app-key', - 'app_token': 'x-blocpower-app-token', - 'app_secret': 'x-blocpower-app-secret'}, - 'urls': { - 'app': 'http://127.0.0.1:5400', - 'user': 'http://127.0.0.1:5401'} -} - -# AppService -APP_CACHE_EXPIRY = 60 * 60 # One hour. - -# UserService -GOOGLE_AUTH_HEADER = 'x-blocpower-google-token' -USER_CACHE_EXPIRY = 60 * 60 # One Hour. - -# Blocpower auth information. -HEADER_AUTH_KEY = 'x-blocpower-auth-key' -HEADER_AUTH_TOKEN = 'x-blocpower-auth-token' diff --git a/app/config/staging.default.py b/app/config/staging.default.py index 9a06abb..a4b9ead 100644 --- a/app/config/staging.default.py +++ b/app/config/staging.default.py @@ -6,7 +6,7 @@ SQLALCHEMY_DATABASE_URI = os.environ['DBURI'] REDIS_URI = os.environ['REDISURI'] -DEBUG = True +DEBUG = False SERVICE_CONFIG = { 'app_key': os.environ['APP_KEY'], diff --git a/app/config/uwsgi.ini b/app/config/uwsgi.ini index c996d6b..08d7693 100644 --- a/app/config/uwsgi.ini +++ b/app/config/uwsgi.ini @@ -1,5 +1,5 @@ [uwsgi] -# This is used if no configuration is specified. +# This is used if no configuration is speicified. ini = :base socket = $CODEROOT/uwsgi.sock @@ -7,10 +7,9 @@ socket = $CODEROOT/uwsgi.sock chmod-socket = 666 master = true -# Number of processes and workers should be determined at build time -# Should be based on the type of instance that it is running on -processes = $NUM_PROCESSES -threads = $NUM_THREADS +# This is a best guess. At some point in time, we should run some experiments +# and get something more concrete. +processes = 1 # Set loggers. req-logger = file:$UWSGIREQ diff --git a/app/controllers/base.py b/app/controllers/base.py index a99b010..9168871 100644 --- a/app/controllers/base.py +++ b/app/controllers/base.py @@ -177,5 +177,5 @@ class SalesforceObjectController(RestController): if model: data.update({'id': model.id}) return self.put(model.id, data, filter_data) - + return super(SalesforceObjectController, self).post(data, filter_data) diff --git a/app/controllers/project.py b/app/controllers/project.py index ed407fd..679b02c 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -26,9 +26,8 @@ class ProjectController(RestController): filters = { 'q': lambda d: and_(*[ Project.name.ilike('%{}%'.format(term)) - for term in d['q'].split(' ')]), - 'building_id': lambda d: Project.building_id == d['building_id'], - } + for term in d['q'].split(' ') + ])} def get_form(self, filter_data): """Return the project form.""" @@ -101,7 +100,7 @@ class ProjectController(RestController): address = AddressController().put(address.id, address_data, {}) else: address = AddressController().post(address_data, {}) - + place_data.update({'address_id': address.id}) if place: @@ -109,7 +108,7 @@ class ProjectController(RestController): place = PlaceController().put(place.id, place_data, {}) else: place = PlaceController().post(place_data, {}) - + data.update({'place_id': place.id}) # Create/modify the project diff --git a/app/forms/project.py b/app/forms/project.py index a46bb06..d69d9bf 100644 --- a/app/forms/project.py +++ b/app/forms/project.py @@ -21,4 +21,3 @@ class ProjectForm(Form): wtf.validators.Required(), wtf.validators.AnyOf(Project.states) ]) - building_id = wtf.IntegerField() diff --git a/app/lib/database.py b/app/lib/database.py index 9bb84f0..3de6fb0 100644 --- a/app/lib/database.py +++ b/app/lib/database.py @@ -21,7 +21,7 @@ def register(app): def commit(): """A wrapper for db.session.commit(). - + This rolls back the database session after a failed commit so that the app can continue to request future commits on the same thread. """ @@ -30,68 +30,3 @@ def commit(): except Exception as e: db.session.rollback() raise e - -class ProcTable: - """ProcTable represents a simplified class of SQLAlchemy db.model""" - - def __init__(self, name, *columns): - self.name = name - self.columns = [column for column in columns] - - def get_columns(self): - """List all columns""" - return [column.key for column in self.columns] - -class ProcColumn: - """ProcColumn represents a column similar to SQLAlchemy column""" - - def __init__(self, key): - self.key = key - -def proc(model, method, limit=None, offset=None, **kwargs): - """ - Run stored procedure - - Args: - model (class): The class of the db table - method (str): Method name for stored procedure - kwargs: Arguments for stored proc - Returns: - list: Results of the query - """ - - params = "" - cols = ','.join(str(i) for i in model.__table__.get_columns()) - # By seperating the args like this sanatation will happen automatically - input_args = {} - for key, value in kwargs.items(): - if value is not None: - params += "in_{} := :{}, ".format(key, key) - input_args[key] = str(value) - else: - params += "in_{} := null, ".format(key) - params = params[:-2] # remove last comma and space - - query = "select {} from {}.{}({})".format( - cols, - model.__table_args__['schema'], - method, - params - ) - if limit: - query += ' limit {}'.format(limit) - if offset: - query += ' offset {}'.format(offset) - - try: - results = db.session.execute(query, input_args) - db.session.commit() - - data = [] - for row in results: - data.append(model(**dict(zip(row.keys(), row)))) - - except Exception as err: - raise err - - return data diff --git a/app/models/base.py b/app/models/base.py index 529e2b8..8381060 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -13,8 +13,21 @@ from ..lib import geography from . import columns -class BaseModel: +class Model(object): """A base mixin for all models.""" + @declared_attr + def __tablename__(cls): + """Automatically set the database table name to the class name + lower-cased. + """ + return cls.__name__.lower() + + def __str__(self): + """Provide a sane default for model string representation.""" + return '<{} (id={})>'.format(self.__class__.__name__, self.id) + + id = db.Column(db.Integer, primary_key=True) + def get_dictionary(self): """Return a dictionary representation of the model. @@ -41,23 +54,6 @@ class BaseModel: return d -class Model(BaseModel): - """A base mixin for SQLAlchemy model.""" - @declared_attr - def __tablename__(cls): - """Automatically set the database table name to the class name - lower-cased. - """ - return cls.__name__.lower() - - __table_args__ = {"schema": "public"} - - def __str__(self): - """Provide a sane default for model string representation.""" - return '<{} (id={})>'.format(self.__class__.__name__, self.id) - - id = db.Column(db.Integer, primary_key=True) - class Tracked(object): """A mixin to include tracking datetime fields.""" created = db.Column(columns.Arrow, default=func.now()) @@ -95,22 +91,3 @@ class External(object): """An external-facing model. These have a UUID key.""" key = db.Column( columns.GUID(), index=True, nullable=False, default=uuid.uuid4) - - -class ProjectSchema(BaseModel): - """A mixin that changes the schema to project_service""" - - @declared_attr - def __tablename__(cls): - """Automatically set the database table name to the class name - lower-cased. - """ - return cls.__name__.lower() - - __table_args__ = {"schema": "project_service"} - - def __str__(self): - """Provide a sane default for model string representation.""" - return '<{} (id={})>'.format(self.__class__.__name__, self.id) - - id = db.Column(db.Integer, primary_key=True) diff --git a/app/models/client.py b/app/models/client.py index 3a4ada2..afa2543 100644 --- a/app/models/client.py +++ b/app/models/client.py @@ -1,8 +1,8 @@ """Models for tracking client information.""" from app.lib.database import db -from app.models.base import Model, Tracked, SalesForce, ProjectSchema +from app.models.base import Model, Tracked, SalesForce -class Client(ProjectSchema, Tracked, SalesForce, db.Model): +class Client(Model, Tracked, SalesForce, db.Model): """A client.""" name = db.Column(db.Unicode(255), nullable=False) diff --git a/app/models/contact.py b/app/models/contact.py index a16d788..0fefe9d 100644 --- a/app/models/contact.py +++ b/app/models/contact.py @@ -1,14 +1,14 @@ """Models for dealing with contacts.""" from app.lib.database import db -from app.models.base import Model, Tracked, SalesForce, ProjectSchema +from app.models.base import Model, Tracked, SalesForce -class Contact(ProjectSchema, Tracked, SalesForce, db.Model): +class Contact(Model, Tracked, SalesForce, db.Model): """A contact (i.e an entity you can reach out to).""" name = db.Column(db.Unicode(255), nullable=False) -class ContactMethod(ProjectSchema, Tracked, db.Model): +class ContactMethod(Model, Tracked, db.Model): """A contact method.""" # TODO SQLAlchemy < 1.1 does not support the use of a python enum in Enum # fields, so for now we have to use a list of strings. When 1.1 leaves @@ -24,12 +24,12 @@ class ContactMethod(ProjectSchema, Tracked, db.Model): contact_id = db.Column( db.Integer, db.ForeignKey('contact.id'), nullable=False) method = db.Column( - db.Enum(*methods, name='contact_methods', inherit_schema=True), + db.Enum(*methods, name='contact_methods'), nullable=False) value = db.Column(db.Unicode(255), nullable=False) -class ProjectContact(ProjectSchema, Tracked, db.Model): +class ProjectContact(Model, Tracked, db.Model): """A m2m relationship between project and contact.""" project_id = db.Column( db.Integer, db.ForeignKey('project.id'), nullable=False) diff --git a/app/models/document.py b/app/models/document.py index b604dc4..f9fbb2d 100644 --- a/app/models/document.py +++ b/app/models/document.py @@ -1,15 +1,15 @@ """Models for handling associated documents.""" from app.lib.database import db -from app.models.base import Model, Tracked, ProjectSchema +from app.models.base import Model, Tracked from app.models.columns import GUID -class DocumentSlot(ProjectSchema, Tracked, db.Model): +class DocumentSlot(Model, Tracked, db.Model): """A m2m relationship between the project model and documents (provided by the document service). """ project_id = db.Column( - db.Integer, db.ForeignKey('project_service.project.id'), nullable=False) + db.Integer, db.ForeignKey('project.id'), nullable=False) document_key = db.Column(GUID(), nullable=False) # How the document fits into the project. diff --git a/app/models/note.py b/app/models/note.py index 4385a8e..470c1dc 100644 --- a/app/models/note.py +++ b/app/models/note.py @@ -1,10 +1,10 @@ """Models for making small notes on the project.""" from app.lib.database import db -from app.models.base import Model, Tracked, SalesForce, ProjectSchema +from app.models.base import Model, Tracked, SalesForce from app.models import columns -class Note(ProjectSchema, Tracked, SalesForce, db.Model): +class Note(Model, Tracked, SalesForce, db.Model): """A note.""" project_id = db.Column( db.Integer, diff --git a/app/models/place.py b/app/models/place.py index 625268a..e06c953 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -2,27 +2,27 @@ from sqlalchemy.orm import relationship from app.lib.database import db from app.lib import geography -from app.models.base import Tracked, SalesForce, ProjectSchema +from app.models.base import Model, Tracked, SalesForce -class Address(ProjectSchema, Tracked, db.Model): +class Place(Model, Tracked, SalesForce, db.Model): + """The Place class""" + name = db.Column(db.Unicode(255), nullable=False) + address_id = db.Column( + db.Integer, db.ForeignKey('address.id'), nullable=False) + + address = relationship('Address', uselist=False) + + +class Address(Model, Tracked, db.Model): """The Address class""" street_address = db.Column(db.Unicode(255), nullable=False) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) state = db.Column( - db.Enum(*geography.states, name='geo_states', inherit_schema=True), + db.Enum(*geography.states, name='geo_states'), nullable=False) country = db.Column( - db.Enum(*geography.countries, name='geo_countries', inherit_schema=True), + db.Enum(*geography.countries, name='geo_countries'), nullable=False) postal_code = db.Column(db.Unicode(10)) - - -class Place(ProjectSchema, Tracked, SalesForce, db.Model): - """The Place class""" - name = db.Column(db.Unicode(255), nullable=False) - address_id = db.Column( - db.INTEGER, db.ForeignKey('project_service.address.id'), nullable=False) - - address = relationship('Address', uselist=False) diff --git a/app/models/project.py b/app/models/project.py index 7b71df1..e1203c7 100644 --- a/app/models/project.py +++ b/app/models/project.py @@ -1,18 +1,17 @@ """Models directly-relating to the top-level project.""" import re from app.lib.database import db -from app.models.base import Model, Tracked, SalesForce, ProjectSchema +from app.models.base import Model, Tracked, SalesForce -class Project(ProjectSchema, Tracked, SalesForce, db.Model): +class Project(Model, Tracked, SalesForce, db.Model): """A project.""" client_id = db.Column( - db.Integer, db.ForeignKey('project_service.client.id'), nullable=False) + db.Integer, db.ForeignKey('client.id'), nullable=False) place_id = db.Column( - db.Integer, db.ForeignKey('project_service.place.id'), nullable=False) + db.Integer, db.ForeignKey('place.id'), nullable=False) name = db.Column(db.Unicode(255), nullable=False) - building_id = db.Column(db.Integer, nullable=False) @property def computed_slug(self): @@ -61,7 +60,7 @@ class Project(ProjectSchema, Tracked, SalesForce, db.Model): 'constructed', 'verified', 'paid' ] state = db.Column( - db.Enum(*states, name='project_states', inherit_schema=True), + db.Enum(*states, name='project_states'), default='pending', nullable=False) @@ -72,11 +71,10 @@ class Project(ProjectSchema, Tracked, SalesForce, db.Model): return d -class ProjectStateChange(ProjectSchema, Tracked, db.Model): +class ProjectStateChange(Model, Tracked, db.Model): """A state change of a project.""" - project_id = db.Column( - db.Integer, db.ForeignKey('project_service.project.id'), + project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=False) state = db.Column( - db.Enum(*Project.states, name='project_states', inherit_schema=True), + db.Enum(*Project.states, name='project_states'), nullable=False) diff --git a/app/views/base.py b/app/views/base.py index b885c9c..f37f3d1 100644 --- a/app/views/base.py +++ b/app/views/base.py @@ -70,7 +70,6 @@ class RestView(UnprotectedRestView): """ decorators = (standard_login_need,) - class SalesforceObjectView(UnprotectedRestView): """A view wrapper for Salesforce object that offers API protection""" def get_controller(self): diff --git a/docs/API/api.md b/docs/API/api.md deleted file mode 100644 index 1b62768..0000000 --- a/docs/API/api.md +++ /dev/null @@ -1,56 +0,0 @@ -**Title** ----- - <_Additional information about your API call. Try to use verbs that match both request type (fetching vs modifying) and plurality (one vs multiple)._> - -* **URL** - - <_The URL Structure (path only, no root url)_> - -* **Method:** - - <_The request type_> - - `GET` | `POST` | `DELETE` | `PUT` - -* **URL Params** - - <_If URL params exist, specify them in accordance with name mentioned in URL section. Separate into optional and required. Document data constraints._> - - **Required:** - - `id=[integer]` - - **Optional:** - - `photo_id=[alphanumeric]` - -* **Data Params** - - <_If making a post request, what should the body payload look like? URL Params rules apply here too._> - -* **Success Response:** - - <_What should the status code be on success and is there any returned data? This is useful when people need to to know what their callbacks should expect!_> - - * **Code:** 200
- **Content:** `{ id : 12 }` - -* **Error Response:** - - <_Most endpoints will have many ways they can fail. From unauthorized access, to wrongful parameters etc. All of those should be liste d here. It might seem repetitive, but it helps prevent assumptions from being made where they should be._> - - * **Code:** 401 UNAUTHORIZED
- **Content:** `{ error : "Log in" }` - - OR - - * **Code:** 422 UNPROCESSABLE ENTRY
- **Content:** `{ error : "Email Invalid" }` - -* **Sample Call:** - - <_Just a sample call to your endpoint in a runnable format ($.ajax call or a curl request) - this makes life easier and more predictable._> - -* **Notes:** - - <_This is where all uncertainties, commentary, discussion etc. can go. I recommend timestamping and identifying oneself when leaving comments here._> diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 5660f02..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,6 +0,0 @@ --r requirements.txt -Flask-Testing==0.4.2 -nose==1.3.7 -pytest==2.9.1 -pylint>=1.6.5 -pylint-flask>=0.5 diff --git a/requirements.txt b/requirements.txt index 39b9286..dfb98ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ docutils==0.12 Flask==0.10.1 Flask-Classy==0.6.10 Flask-SQLAlchemy==2.1 +Flask-Testing==0.4.2 GeoAlchemy2==0.3.0 httplib2==0.9.2 itsdangerous==0.24 @@ -18,6 +19,7 @@ Jinja2==2.8 jmespath==0.9.0 MarkupSafe==0.23 needs==1.0.9 +nose==1.3.7 oauth2client==2.0.1 parse==1.6.6 pathspec==0.3.3 @@ -27,6 +29,7 @@ py==1.4.31 pyasn1==0.1.9 pyasn1-modules==0.0.8 pycparser==2.14 +pytest==2.9.1 python-dateutil==2.4.2 PyYAML==3.11 redis==2.10.5 diff --git a/run.py b/run.py index 9e56150..217555d 100644 --- a/run.py +++ b/run.py @@ -2,6 +2,6 @@ from app import create_app from app.lib.database import db -app = create_app('config/local.py') +app = create_app('config/development.py') if __name__ == '__main__': app.run(host='0.0.0.0', port=5302) diff --git a/wsgi.py b/wsgi.py index 294e9eb..d147c76 100644 --- a/wsgi.py +++ b/wsgi.py @@ -9,3 +9,6 @@ env = os.environ['ENVIRONMENT'] # Correctly raise a file not found if the specified environment does not exist. app = create_app('config/{}.py'.format(env)) +with app.app_context(): + # Build the database models. + db.create_all() -- GitLab