From 24d3a6ac6ec533d1ce4c3ebe95fed378b0fe9ec7 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Mon, 21 Mar 2016 16:58:46 -0400 Subject: [PATCH 01/48] [[#115944293]] --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c16b943..f5d3943 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,4 @@ config.json *.db # PyCharm -.idea ->>>>>>> d818a0e1981417ebc795923ddc504dc124f62a16 +.idea \ No newline at end of file -- GitLab From b924be41d5853f346553ee2ea4936b8e6cd77263 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Mon, 21 Mar 2016 17:17:48 -0400 Subject: [PATCH 02/48] [[#115944293]] - Added GeoAlchemy2 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2f33974..4f1139e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,3 +38,4 @@ wcwidth==0.1.6 websocket-client==0.35.0 Werkzeug==0.11.4 WTForms==2.1 +geoalchemy2==0.2.6 -- GitLab From 908ea9e4cdb106db7546e70a0ea5fb133cdacd50 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Mon, 21 Mar 2016 17:18:20 -0400 Subject: [PATCH 03/48] [[#115944293]] Missing import --- app/controllers/project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/project.py b/app/controllers/project.py index c1aaf53..f338345 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -1,4 +1,5 @@ """Controllers for managing top-level projects.""" +from werkzeug.exceptions import BadRequest from app.lib.database import db from app.controllers.base import RestController from app.models.project import Project, ProjectStateChange -- GitLab From c4b6371c46f4b6b9a70398ea2434afde998703a7 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Mon, 21 Mar 2016 17:19:13 -0400 Subject: [PATCH 04/48] [[#115944293]] New Models, Controllers, Views: Address, Location, Place --- app/controllers/address.py | 73 ++++++++++++++++++++++++++++ app/forms/address.py | 87 +++++++++++++++++++++++++++++++++ app/models/address.py | 42 ++++++++++++++++ app/models/contact.py | 16 ++++++ app/views/address.py | 99 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 317 insertions(+) create mode 100644 app/controllers/address.py create mode 100644 app/forms/address.py create mode 100644 app/models/address.py create mode 100644 app/models/contact.py create mode 100644 app/views/address.py diff --git a/app/controllers/address.py b/app/controllers/address.py new file mode 100644 index 0000000..938ed10 --- /dev/null +++ b/app/controllers/address.py @@ -0,0 +1,73 @@ +from werkzeug.exceptions import BadRequest + +from app.lib.database import db +from app.controllers.base import RestController +from app.models.address import Address, Location, Place +from app.forms.address import CreateAddressForm, CreateLocationForm, CreatePlaceForm, UpdateAddressForm, \ + UpdateLocationForm, UpdatePlaceForm + + +class AddressController(RestController): + """ + The Address controller. + """ + Model = Address + + def get_form(self, filter_data): + """ + :param filter_data: + :type filter_data: Dict + :return: A form that either creates or updates an Address + :rtype: Form + """ + try: + return { + 'create': CreateAddressForm, + 'update': UpdateAddressForm + }[filter_data.get('form')] + except KeyError: + raise BadRequest('Invalid form.') + + +class LocationController(RestController): + """ + The Location controller. + """ + Model = Location + + def get_form(self, filter_data): + """ + :param filter_data: + :type filter_data: Dict + :return: A form that either creates or updates a Location + :rtype: Form + """ + try: + return { + 'create': CreateLocationForm, + 'update': UpdateLocationForm + }[filter_data.get('form')] + except KeyError: + raise BadRequest('Invalid form.') + + +class PlaceController(RestController): + """ + The Place controller. + """ + Model = Location + + def get_form(self, filter_data): + """ + :param filter_data: + :type filter_data: Dict + :return: A form that either creates or updates a Place + :rtype: Form + """ + try: + return { + 'create': CreatePlaceForm, + 'update': UpdatePlaceForm + }[filter_data.get('form')] + except KeyError: + raise BadRequest('Invalid form.') diff --git a/app/forms/address.py b/app/forms/address.py new file mode 100644 index 0000000..d54d26f --- /dev/null +++ b/app/forms/address.py @@ -0,0 +1,87 @@ +"""Forms for validating Addresses, Locations, Places.""" +import wtforms as wtf +from app.forms.base import Form + + +class CreateAddressForm(Form): + """ + A form for posting new Addresses. + """ + + sales_force_id = wtf.StringField( + validators=[wtf.validators.Length(max=255)]) + name = wtf.StringField( + validators=[ + wtf.validators.Required(), + wtf.validators.Length(max=255) + ] + ) + + +class UpdateAddressForm(Form): + """ + A form for manipulating existing Addresses. + """ + + name = wtf.StringField( + validators=[ + wtf.validators.Required(), + wtf.validators.Length(max=255) + ] + ) + + +class CreateLocationForm(Form): + """ + A form for posting new Locations. + """ + + sales_force_id = wtf.StringField( + validators=[wtf.validators.Length(max=255)]) + name = wtf.StringField( + validators=[ + wtf.validators.Required(), + wtf.validators.Length(max=255) + ] + ) + + +class UpdateLocationForm(Form): + """ + A form for manipulating existing Locations. + """ + + name = wtf.StringField( + validators=[ + wtf.validators.Required(), + wtf.validators.Length(max=255) + ] + ) + + +class CreatePlaceForm(Form): + """ + A form for posting new Places. + """ + + sales_force_id = wtf.StringField( + validators=[wtf.validators.Length(max=255)]) + name = wtf.StringField( + validators=[ + wtf.validators.Required(), + wtf.validators.Length(max=255) + ] + ) + + +class UpdatePlaceForm(Form): + """ + A form for manipulating existing Places. + """ + + name = wtf.StringField( + validators=[ + wtf.validators.Required(), + wtf.validators.Length(max=255) + ] + ) \ No newline at end of file diff --git a/app/models/address.py b/app/models/address.py new file mode 100644 index 0000000..c74103b --- /dev/null +++ b/app/models/address.py @@ -0,0 +1,42 @@ +from geoalchemy2 import Geometry + +from app.lib.database import db +from app.models.base import Model, Tracked + + +class Address(Model, Tracked, db.Model): + """ + The Address class + """ + street_name = db.Column(db.Unicode(255)) + street_number = db.Column(db.Unicode(255)) + unit_number = db.Column(db.Unicode(255), index=True) + city = db.Column(db.Unicode(255), nullable=False) + county = db.Column(db.Unicode(255)) + state_id = db.Column(db.Integer, db.ForeignKey('state.id'), nullable=False) + postal_code = db.Column(db.Integer) + + +class Location(Model, Tracked, db.Model): + """ + The Location class + """ + name = db.Column(db.Unicode(255)) + coords = db.Column(Geometry('POINT')) + + +class State(Model, Tracked, db.Model): + """ + Each State and Territory in the USA + """ + name = db.Column(db.Unicode(255), index=True) + code = db.Column(db.Integer, index=True) + + +class Place(Model, Tracked, db.Model): + """ + The Place class + """ + name = db.Column(db.Unicode(255)) + address_id = db.Column(db.Integer, db.ForeignKey('address.id'), nullable=False) + location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) diff --git a/app/models/contact.py b/app/models/contact.py new file mode 100644 index 0000000..dac6138 --- /dev/null +++ b/app/models/contact.py @@ -0,0 +1,16 @@ +from app.lib.database import db +from app.models.base import Model, Tracked + + +class Contact(Model, Tracked, db.Model): + """ + Contact class. + """ + user_types = ['person', 'business'] + + name = db.Column(db.Unicode(255)) + first_name = db.Column(db.Unicode(255)) + middle_name = db.Column(db.Unicode(255)) + last_name = db.Column(db.Unicode(255)) + phone_number = db.Column(db.Unicode(255)) + type = db.Column(db.Enum(*user_types), default='person', nullable=False) \ No newline at end of file diff --git a/app/views/address.py b/app/views/address.py new file mode 100644 index 0000000..573fcb3 --- /dev/null +++ b/app/views/address.py @@ -0,0 +1,99 @@ +from werkzeug.exceptions import MethodNotAllowed +from flask import request +from app.views.base import RestView +from app.controllers.address import AddressController, LocationController, PlaceController +from app.permissions.base import FormNeed + + +class AddressView(RestView): + def get_controller(self): + """ + Return an instance of the Address controller. + """ + return AddressController() + + def post(self): + """ + Checks for a create form before posting. + """ + form = request.args.get('form') + with FormNeed('create', form): + return super(AddressView, self).post() + + def put(self, id_): + form = request.args.get('form') + with FormNeed('update', form): + return super(AddressView, self).put(id_) + + def delete(self, id_): + """ + Not implemented. + :param id_: The object id + :type id_: str + """ + raise MethodNotAllowed( + 'Addresses cannot be deleted. Cancel the project instead.' + ) + + +class LocationView(RestView): + def get_controller(self): + """ + Return an instance of the Location controller. + """ + return AddressController() + + def post(self): + """ + Checks for a create form before posting. + """ + form = request.args.get('form') + with FormNeed('create', form): + return super(LocationView, self).post() + + def put(self, id_): + form = request.args.get('form') + with FormNeed('update', form): + return super(LocationView, self).put(id_) + + def delete(self, id_): + """ + Not implemented. + :param id_: The object id + :type id_: str + """ + raise MethodNotAllowed( + 'Locations cannot be deleted. Cancel the project instead.' + ) + + +class PlaceView(RestView): + def get_controller(self): + """ + Return an instance of the Place controller. + """ + return AddressController() + + def post(self): + """ + Checks for a create form before posting. + """ + form = request.args.get('form') + with FormNeed('create', form): + return super(PlaceView, self).post() + + def put(self, id_): + form = request.args.get('form') + with FormNeed('update', form): + return super(PlaceView, self).put(id_) + + def delete(self, id_): + """ + Not implemented. + :param id_: The object id + :type id_: str + """ + raise MethodNotAllowed( + 'Places cannot be deleted. Cancel the project instead.' + ) + -- GitLab From c4cb08e2810c7ca3b7016c6b136fe8ff3a90a4fb Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Mon, 21 Mar 2016 17:58:20 -0400 Subject: [PATCH 05/48] [[#115944293]] Deleted unnecessary class --- app/controllers/{address.py => place.py} | 55 +++++++++--------------- app/forms/{address.py => place.py} | 0 app/models/contact.py | 16 ------- app/models/{address.py => place.py} | 20 ++++----- app/views/{address.py => place.py} | 0 5 files changed, 29 insertions(+), 62 deletions(-) rename app/controllers/{address.py => place.py} (65%) rename app/forms/{address.py => place.py} (100%) delete mode 100644 app/models/contact.py rename app/models/{address.py => place.py} (85%) rename app/views/{address.py => place.py} (100%) diff --git a/app/controllers/address.py b/app/controllers/place.py similarity index 65% rename from app/controllers/address.py rename to app/controllers/place.py index 938ed10..cc296f0 100644 --- a/app/controllers/address.py +++ b/app/controllers/place.py @@ -1,73 +1,60 @@ from werkzeug.exceptions import BadRequest -from app.lib.database import db from app.controllers.base import RestController -from app.models.address import Address, Location, Place +from app.models.place import Address, Location, Place from app.forms.address import CreateAddressForm, CreateLocationForm, CreatePlaceForm, UpdateAddressForm, \ UpdateLocationForm, UpdatePlaceForm -class AddressController(RestController): - """ - The Address controller. - """ - Model = Address +class PlaceController(RestController): + """The Place controller. """ + Model = Place def get_form(self, filter_data): """ - :param filter_data: - :type filter_data: Dict - :return: A form that either creates or updates an Address - :rtype: Form + Args: + filter_data - A dictionary """ try: return { - 'create': CreateAddressForm, - 'update': UpdateAddressForm + 'create': CreatePlaceForm, + 'update': UpdatePlaceForm }[filter_data.get('form')] except KeyError: raise BadRequest('Invalid form.') -class LocationController(RestController): - """ - The Location controller. - """ - Model = Location +class AddressController(RestController): + """The Address controller.""" + Model = Address def get_form(self, filter_data): """ - :param filter_data: - :type filter_data: Dict - :return: A form that either creates or updates a Location - :rtype: Form + Args: + filter_data - A dictionary """ try: return { - 'create': CreateLocationForm, - 'update': UpdateLocationForm + 'create': CreateAddressForm, + 'update': UpdateAddressForm }[filter_data.get('form')] except KeyError: raise BadRequest('Invalid form.') -class PlaceController(RestController): - """ - The Place controller. - """ +class LocationController(RestController): + """The Location controller. """ Model = Location def get_form(self, filter_data): """ - :param filter_data: - :type filter_data: Dict - :return: A form that either creates or updates a Place - :rtype: Form + Args: + filter_data - A dictionary """ try: return { - 'create': CreatePlaceForm, - 'update': UpdatePlaceForm + 'create': CreateLocationForm, + 'update': UpdateLocationForm }[filter_data.get('form')] except KeyError: raise BadRequest('Invalid form.') diff --git a/app/forms/address.py b/app/forms/place.py similarity index 100% rename from app/forms/address.py rename to app/forms/place.py diff --git a/app/models/contact.py b/app/models/contact.py deleted file mode 100644 index dac6138..0000000 --- a/app/models/contact.py +++ /dev/null @@ -1,16 +0,0 @@ -from app.lib.database import db -from app.models.base import Model, Tracked - - -class Contact(Model, Tracked, db.Model): - """ - Contact class. - """ - user_types = ['person', 'business'] - - name = db.Column(db.Unicode(255)) - first_name = db.Column(db.Unicode(255)) - middle_name = db.Column(db.Unicode(255)) - last_name = db.Column(db.Unicode(255)) - phone_number = db.Column(db.Unicode(255)) - type = db.Column(db.Enum(*user_types), default='person', nullable=False) \ No newline at end of file diff --git a/app/models/address.py b/app/models/place.py similarity index 85% rename from app/models/address.py rename to app/models/place.py index c74103b..e3fe709 100644 --- a/app/models/address.py +++ b/app/models/place.py @@ -5,9 +5,8 @@ from app.models.base import Model, Tracked class Address(Model, Tracked, db.Model): - """ - The Address class - """ + """The Address class""" + street_name = db.Column(db.Unicode(255)) street_number = db.Column(db.Unicode(255)) unit_number = db.Column(db.Unicode(255), index=True) @@ -18,25 +17,22 @@ class Address(Model, Tracked, db.Model): class Location(Model, Tracked, db.Model): - """ - The Location class - """ + """The Location class""" + name = db.Column(db.Unicode(255)) coords = db.Column(Geometry('POINT')) class State(Model, Tracked, db.Model): - """ - Each State and Territory in the USA - """ + """Each State and Territory in the USA""" + name = db.Column(db.Unicode(255), index=True) code = db.Column(db.Integer, index=True) class Place(Model, Tracked, db.Model): - """ - The Place class - """ + """The Place class""" + name = db.Column(db.Unicode(255)) address_id = db.Column(db.Integer, db.ForeignKey('address.id'), nullable=False) location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) diff --git a/app/views/address.py b/app/views/place.py similarity index 100% rename from app/views/address.py rename to app/views/place.py -- GitLab From 310806005c1230a275a70341d6ad24e0bc151bd2 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Mon, 21 Mar 2016 17:59:23 -0400 Subject: [PATCH 06/48] [[#115944293]] Updated Docstrings. Using 'place' as class name --- app/controllers/place.py | 8 ++- app/models/place.py | 16 +++--- app/views/place.py | 108 ++++++++++++++++++++++----------------- 3 files changed, 74 insertions(+), 58 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index cc296f0..00aca41 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -1,13 +1,14 @@ from werkzeug.exceptions import BadRequest from app.controllers.base import RestController -from app.models.place import Address, Location, Place -from app.forms.address import CreateAddressForm, CreateLocationForm, CreatePlaceForm, UpdateAddressForm, \ +from app.forms.place import CreateAddressForm, CreateLocationForm, CreatePlaceForm, UpdateAddressForm, \ UpdateLocationForm, UpdatePlaceForm +from app.models.place import Address, Location, Place class PlaceController(RestController): """The Place controller. """ + Model = Place def get_form(self, filter_data): @@ -15,6 +16,7 @@ class PlaceController(RestController): Args: filter_data - A dictionary """ + try: return { 'create': CreatePlaceForm, @@ -26,6 +28,7 @@ class PlaceController(RestController): class AddressController(RestController): """The Address controller.""" + Model = Address def get_form(self, filter_data): @@ -44,6 +47,7 @@ class AddressController(RestController): class LocationController(RestController): """The Location controller. """ + Model = Location def get_form(self, filter_data): diff --git a/app/models/place.py b/app/models/place.py index e3fe709..318f25b 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -4,6 +4,14 @@ from app.lib.database import db from app.models.base import Model, Tracked +class Place(Model, Tracked, db.Model): + """The Place class""" + + name = db.Column(db.Unicode(255)) + address_id = db.Column(db.Integer, db.ForeignKey('address.id'), nullable=False) + location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) + + class Address(Model, Tracked, db.Model): """The Address class""" @@ -28,11 +36,3 @@ class State(Model, Tracked, db.Model): name = db.Column(db.Unicode(255), index=True) code = db.Column(db.Integer, index=True) - - -class Place(Model, Tracked, db.Model): - """The Place class""" - - name = db.Column(db.Unicode(255)) - address_id = db.Column(db.Integer, db.ForeignKey('address.id'), nullable=False) - location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) diff --git a/app/views/place.py b/app/views/place.py index 573fcb3..fe83f43 100644 --- a/app/views/place.py +++ b/app/views/place.py @@ -1,99 +1,111 @@ -from werkzeug.exceptions import MethodNotAllowed from flask import request -from app.views.base import RestView -from app.controllers.address import AddressController, LocationController, PlaceController +from werkzeug.exceptions import MethodNotAllowed + +from app.controllers.place import AddressController, LocationController, PlaceController from app.permissions.base import FormNeed +from app.views.base import RestView -class AddressView(RestView): +class PlaceView(RestView): def get_controller(self): - """ - Return an instance of the Address controller. - """ - return AddressController() + """Return an instance of the Place controller.""" + + return PlaceController() def post(self): - """ - Checks for a create form before posting. - """ + """Checks for a create form before posting.""" + form = request.args.get('form') with FormNeed('create', form): - return super(AddressView, self).post() + return super(PlaceView, self).post() def put(self, id_): + """ + Args: + id_ - The object ID + """ + form = request.args.get('form') with FormNeed('update', form): - return super(AddressView, self).put(id_) + return super(PlaceView, self).put(id_) def delete(self, id_): + """Not implemented. + + Args: + id_ - The object ID """ - Not implemented. - :param id_: The object id - :type id_: str - """ + raise MethodNotAllowed( - 'Addresses cannot be deleted. Cancel the project instead.' + 'Places cannot be deleted. Cancel the project instead.' ) -class LocationView(RestView): +class AddressView(RestView): def get_controller(self): - """ - Return an instance of the Location controller. - """ + """Return an instance of the Address controller.""" + return AddressController() def post(self): - """ - Checks for a create form before posting. - """ + """Checks for a create form before posting.""" + form = request.args.get('form') with FormNeed('create', form): - return super(LocationView, self).post() + return super(AddressView, self).post() def put(self, id_): + """ + Args: + id_ - The object ID + """ + form = request.args.get('form') with FormNeed('update', form): - return super(LocationView, self).put(id_) + return super(AddressView, self).put(id_) def delete(self, id_): + """Not implemented. + + Args: + id_ - The object ID """ - Not implemented. - :param id_: The object id - :type id_: str - """ + raise MethodNotAllowed( - 'Locations cannot be deleted. Cancel the project instead.' + 'Addresses cannot be deleted. Cancel the project instead.' ) -class PlaceView(RestView): +class LocationView(RestView): def get_controller(self): - """ - Return an instance of the Place controller. - """ - return AddressController() + """Return an instance of the Location controller.""" + + return LocationController() def post(self): - """ - Checks for a create form before posting. - """ + """Checks for a create form before posting.""" + form = request.args.get('form') with FormNeed('create', form): - return super(PlaceView, self).post() + return super(LocationView, self).post() def put(self, id_): + """ + Args: + id_ - The object ID + """ + form = request.args.get('form') with FormNeed('update', form): - return super(PlaceView, self).put(id_) + return super(LocationView, self).put(id_) def delete(self, id_): + """Not implemented. + + Args: + id_ - The object ID """ - Not implemented. - :param id_: The object id - :type id_: str - """ + raise MethodNotAllowed( - 'Places cannot be deleted. Cancel the project instead.' + 'Locations cannot be deleted. Cancel the project instead.' ) - -- GitLab From 1616ba72516ccfb670b1b3a4a08446154249ab2d Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Tue, 22 Mar 2016 10:19:39 -0400 Subject: [PATCH 07/48] [[#115944293]] - Street address field --- app/models/place.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/place.py b/app/models/place.py index 318f25b..be43e7d 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -15,8 +15,7 @@ class Place(Model, Tracked, db.Model): class Address(Model, Tracked, db.Model): """The Address class""" - street_name = db.Column(db.Unicode(255)) - street_number = db.Column(db.Unicode(255)) + street_address = db.Column(db.Unicode(255)) unit_number = db.Column(db.Unicode(255), index=True) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) -- GitLab From 1c4982f0f54ea2a3724ae4c1d7b9f8818db18732 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 12:21:05 -0400 Subject: [PATCH 08/48] Updated GitIgnore --- .gitignore | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f5d3943..fd3ced5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,37 @@ config.json *.db # PyCharm -.idea \ No newline at end of file +.idea + +# Vagrant +.vagrant +vagrant_local.yml + +# Mac system +.DS_Store +._.DS_Store + +# Logs +logs/ + +# Packages +*.egg +*.egg-info +build +develop-eggs +eggs +parts +sdist +var + +.installed.cfg +lib64 + +# Virtualenv +virtpy/ + +# Data +/data/ + +# Local Dev Folder +/local_dev \ No newline at end of file -- GitLab From 5bd14c9962fcec7a5c93176d61069add6fe9fe94 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 12:23:12 -0400 Subject: [PATCH 09/48] Updated Class Structures State() - Now an object, with functions for returning a State Object given search paramters Location() - Now an object. This class will be fully operational in the DB once support for GIS has been implemented. --- app/models/place.py | 144 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 13 deletions(-) diff --git a/app/models/place.py b/app/models/place.py index be43e7d..be7b1cc 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -4,34 +4,152 @@ from app.lib.database import db from app.models.base import Model, Tracked +class State(object): + """Each State and Territory in the USA""" + + name = db.Column(db.Unicode(255)) + code = db.Column(db.Unicode(255)) + + states = { + 'AL': 'Alabama', + 'AK': 'Alaska', + 'AS': 'American Samoa', + 'AZ': 'Arizona', + 'AR': 'Arkansas', + 'CA': 'California', + 'CO': 'Colorado', + 'CT': 'Connecticut', + 'DE': 'Delaware', + 'DC': 'District Of Columbia', + 'FM': 'Federated States Of Micronesia', + 'FL': 'Florida', + 'GA': 'Georgia', + 'GU': 'Guam', + 'HI': 'Hawaii', + 'ID': 'Idaho', + 'IL': 'Illinois', + 'IN': 'Indiana', + 'IA': 'Iowa', + 'KS': 'Kansas', + 'KY': 'Kentucky', + 'LA': 'Louisiana', + 'ME': 'Maine', + 'MH': 'Marshall Islands', + 'MD': 'Maryland', + 'MA': 'Massachusetts', + 'MI': 'Michigan', + 'MN': 'Minnesota', + 'MS': 'Mississippi', + 'MO': 'Missouri', + 'MT': 'Montana', + 'NE': 'Nebraska', + 'NV': 'Nevada', + 'NH': 'New Hampshire', + 'NJ': 'New Jersey', + 'NM': 'New Mexico', + 'NY': 'New York', + 'NC': 'North Carolina', + 'ND': 'North Dakota', + 'MP': 'Northern Mariana Islands', + 'OH': 'Ohio', + 'OK': 'Oklahoma', + 'OR': 'Oregon', + 'PW': 'Palau', + 'PA': 'Pennsylvania', + 'PR': 'Puerto Rico', + 'RI': 'Rhode Island', + 'SC': 'South Carolina', + 'SD': 'South Dakota', + 'TN': 'Tennessee', + 'TX': 'Texas', + 'UT': 'Utah', + 'VT': 'Vermont', + 'VI': 'Virgin Islands', + 'VA': 'Virginia', + 'WA': 'Washington', + 'WV': 'West Virginia', + 'WI': 'Wisconsin', + 'WY': 'Wyoming' + } + + # TODO + # Use Redis to cache objects and methods in this class + + codes = list(states.keys()) + names = list(map(lambda x: x[1], states.items())) + names_to_upper = list(map(lambda x: x[1].upper(), states.items())) + names_to_lower = list(map(lambda x: x[1].lower(), states.items())) + + def __init__(self, name, code): + self.name = name + self.code = code + + @classmethod + def get_states(cls): + """Returns a list of State Objects""" + + states = [] + + for code, name in cls.states.items(): + states.append(cls(name=name, code=code)) + + @classmethod + def get_state_by_code(cls, code): + """Returns a State object, given a State code + Args: + code - A State code + """ + + code = code.upper() + + if code in cls.states.keys(): + return cls(name=cls.states[code], code=code) + + return None + + @classmethod + def get_state_by_name(cls, state_name): + """ Returns a State object, given a name + Args: + name - The name of the State + """ + + if state_name.upper() not in cls.names_to_lower: + return None + + for code, name in cls.states.items(): + if state_name.upper() == name.upper(): + return cls(name=name, code=code) + + return None + + class Place(Model, Tracked, db.Model): """The Place class""" name = db.Column(db.Unicode(255)) address_id = db.Column(db.Integer, db.ForeignKey('address.id'), nullable=False) - location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) + # location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) class Address(Model, Tracked, db.Model): """The Address class""" - street_address = db.Column(db.Unicode(255)) + street_address = db.Column(db.Unicode(255), nullable=False) unit_number = db.Column(db.Unicode(255), index=True) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) - state_id = db.Column(db.Integer, db.ForeignKey('state.id'), nullable=False) + state_code = db.Column(db.Enum(*State.codes), nullable=False) postal_code = db.Column(db.Integer) + @property + def state(self): + """Returns a State object from Address.state_code""" + return State.get_state_by_code(self.state_code) -class Location(Model, Tracked, db.Model): - """The Location class""" - - name = db.Column(db.Unicode(255)) - coords = db.Column(Geometry('POINT')) +class Location(object): + """The Location class""" -class State(Model, Tracked, db.Model): - """Each State and Territory in the USA""" - - name = db.Column(db.Unicode(255), index=True) - code = db.Column(db.Integer, index=True) + name = db.Column(db.Unicode(255), nullable=False) + coords = db.Column(Geometry('POINT'), nullable=False) -- GitLab From 960604f0e9e2a418e95deb42fbae50c451e8667b Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 12:55:05 -0400 Subject: [PATCH 10/48] Removed Unnecessary Ignores --- .gitignore | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index fd3ced5..1665ba6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,27 +19,13 @@ config.json # Vagrant .vagrant -vagrant_local.yml # Mac system .DS_Store ._.DS_Store # Logs -logs/ - -# Packages -*.egg -*.egg-info -build -develop-eggs -eggs -parts -sdist -var - -.installed.cfg -lib64 +/logs/ # Virtualenv virtpy/ -- GitLab From c042fb5e7442ca1ac4353a1f14de0fa380584c52 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 12:55:47 -0400 Subject: [PATCH 11/48] Place.name Is a required field --- app/models/place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/place.py b/app/models/place.py index be7b1cc..dcc0e19 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -127,7 +127,7 @@ class State(object): class Place(Model, Tracked, db.Model): """The Place class""" - name = db.Column(db.Unicode(255)) + name = db.Column(db.Unicode(255), nullable=False) address_id = db.Column(db.Integer, db.ForeignKey('address.id'), nullable=False) # location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) -- GitLab From 55de0ef0ac621595f39eded3ab11dd6a375c3feb Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 13:17:06 -0400 Subject: [PATCH 12/48] Removed Location Controllers and Views --- app/controllers/place.py | 23 ++--------------------- app/views/place.py | 39 ++------------------------------------- 2 files changed, 4 insertions(+), 58 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index 00aca41..3713e17 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -1,9 +1,9 @@ from werkzeug.exceptions import BadRequest from app.controllers.base import RestController -from app.forms.place import CreateAddressForm, CreateLocationForm, CreatePlaceForm, UpdateAddressForm, \ +from app.forms.place import CreateAddressForm, CreatePlaceForm, UpdateAddressForm, \ UpdateLocationForm, UpdatePlaceForm -from app.models.place import Address, Location, Place +from app.models.place import Address, Place class PlaceController(RestController): @@ -43,22 +43,3 @@ class AddressController(RestController): }[filter_data.get('form')] except KeyError: raise BadRequest('Invalid form.') - - -class LocationController(RestController): - """The Location controller. """ - - Model = Location - - def get_form(self, filter_data): - """ - Args: - filter_data - A dictionary - """ - try: - return { - 'create': CreateLocationForm, - 'update': UpdateLocationForm - }[filter_data.get('form')] - except KeyError: - raise BadRequest('Invalid form.') diff --git a/app/views/place.py b/app/views/place.py index fe83f43..86d09e8 100644 --- a/app/views/place.py +++ b/app/views/place.py @@ -1,7 +1,7 @@ from flask import request from werkzeug.exceptions import MethodNotAllowed -from app.controllers.place import AddressController, LocationController, PlaceController +from app.controllers.place import AddressController, PlaceController from app.permissions.base import FormNeed from app.views.base import RestView @@ -73,39 +73,4 @@ class AddressView(RestView): raise MethodNotAllowed( 'Addresses cannot be deleted. Cancel the project instead.' - ) - - -class LocationView(RestView): - def get_controller(self): - """Return an instance of the Location controller.""" - - return LocationController() - - def post(self): - """Checks for a create form before posting.""" - - form = request.args.get('form') - with FormNeed('create', form): - return super(LocationView, self).post() - - def put(self, id_): - """ - Args: - id_ - The object ID - """ - - form = request.args.get('form') - with FormNeed('update', form): - return super(LocationView, self).put(id_) - - def delete(self, id_): - """Not implemented. - - Args: - id_ - The object ID - """ - - raise MethodNotAllowed( - 'Locations cannot be deleted. Cancel the project instead.' - ) + ) \ No newline at end of file -- GitLab From bf037a8690e4c1543ed0557b83b8120324113599 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 13:17:44 -0400 Subject: [PATCH 13/48] Removed Location Forms --- app/forms/place.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/app/forms/place.py b/app/forms/place.py index d54d26f..d39cefd 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -31,34 +31,6 @@ class UpdateAddressForm(Form): ) -class CreateLocationForm(Form): - """ - A form for posting new Locations. - """ - - sales_force_id = wtf.StringField( - validators=[wtf.validators.Length(max=255)]) - name = wtf.StringField( - validators=[ - wtf.validators.Required(), - wtf.validators.Length(max=255) - ] - ) - - -class UpdateLocationForm(Form): - """ - A form for manipulating existing Locations. - """ - - name = wtf.StringField( - validators=[ - wtf.validators.Required(), - wtf.validators.Length(max=255) - ] - ) - - class CreatePlaceForm(Form): """ A form for posting new Places. -- GitLab From 17b716cda4272c03f2c50636099e8d9e4d28e4d4 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 13:18:05 -0400 Subject: [PATCH 14/48] Added tests for Place and Address classes --- app/tests/test_place.py | 262 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 app/tests/test_place.py diff --git a/app/tests/test_place.py b/app/tests/test_place.py new file mode 100644 index 0000000..39839f0 --- /dev/null +++ b/app/tests/test_place.py @@ -0,0 +1,262 @@ +"""Unit tests for working with Places and Addresses.""" +from sqlalchemy import and_ +from app.lib.database import db +from app.tests.base import RestTestCase +from app.models.place import Place, Address + + +class TestPlace(RestTestCase): + """Tests the /place/ endpoints.""" + + url = '/place/' + Model = Place + + def test_index(self): + """Tests /place/ GET.""" + + model = self.env.place + self._test_index() + + def test_get(self): + """Tests /place/ GET.""" + + model = self.env.place + self._test_get(model.id) + + def test_post(self): + """Tests /place/ POST.""" + + data = { + 'name': 'test', + 'address_id': 'test', + } + response_data = self._test_post(data, {'form': 'create'}) + self.assertTrue( + db.session.query(Place).filter( + and_( + Place.id == response_data['id'], + Place.name == data['name'], + Place.address_id == data['address_id'], + ) + ).first() + ) + + def test_post_bad_form(self): + """Tests /place/ POST with a bad form. It should 400.""" + + response = self.post( + self.url, + data={'name': 'test', 'address_id': 'test'}, + query_string={'form': 'update'} + ) + self.assertEqual(response.status_code, 400) + + def test_post_missing_name(self): + """Tests /place/ POST with a missing name. It should 400.""" + + response = self.post( + self.url, + data={'address_id': 'test'}, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_put(self): + """Tests /place/ PUT.""" + + model = self.env.place + data = model.get_dictionary() + data.update({'name': 'foo'}) + response_data = self._test_put(model.id, data, {'form': 'update'}) + self.assertEqual(response_data['name'], 'foo') + self.assertTrue(db.session.query(Place).filter(and_(Place.id == model.id, Place.name == data['name'])).first()) + + def test_put_bad_form(self): + """Tests /place/ PUT with a bad form. It should 400.""" + + model = self.env.place + data = model.get_dictionary() + data.update({'name': 'foo'}) + response = self.put( + self.url + str(model.id), + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_delete(self): + """Tests /place/ DELETE.""" + + model = self.env.place + self._test_delete(model.id) + + +class TestAddress(RestTestCase): + """Tests the /address/ endpoints.""" + + url = '/address/' + Model = Address + + def test_index(self): + """Tests /address/ GET.""" + + model = self.env.address + self._test_index() + + def test_get(self): + """Tests /address/ GET.""" + + model = self.env.address + self._test_get(model.id) + + def test_post(self): + """Tests /address/ POST.""" + + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state_code': 'NY', + 'postal_code': '11210', + } + + response_data = self._test_post(data, {'form': 'create'}) + self.assertTrue( + db.session.query(Address).filter( + and_( + Address.id == response_data['id'], + Address.street_address == data['street_address'], + Address.unit_number == data['unit_number'], + Address.city == data['city'], + Address.state_code == data['state_code'], + Address.postal_code == data['postal_code'], + ) + ).first() + ) + + def test_post_bad_form(self): + """Tests /address/ POST with a bad form. It should 400.""" + + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state_code': 'NY', + 'postal_code': '11210', + } + + response = self.post( + self.url, + data=data, + query_string={'form': 'update'} + ) + self.assertEqual(response.status_code, 400) + + def test_post_missing_street_address(self): + """Tests /address/ POST with a missing street_address. It should 400.""" + + data = { + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state_code': 'NY', + 'postal_code': '11210', + } + + response = self.post( + self.url, + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_post_missing_city(self): + """Tests /address/ POST with missing city. It should 400.""" + + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'county': 'Kings', + 'state_code': 'NY', + 'postal_code': '11210', + } + + response = self.post( + self.url, + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_post_missing_state_code(self): + """Tests /address/ POST with missing state_code. It should 400.""" + + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'postal_code': '11210', + } + + response = self.post( + self.url, + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_post_invalid_state_code(self): + """Tests /address/ POST with invalid state_code. It should 400.""" + + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state_code': 'NN', + 'postal_code': '11210', + } + + response = self.post( + self.url, + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_put(self): + """Tests /address/ PUT.""" + + data = { + 'unit_number': 'Suite 1800', + } + + model = self.env.address + data = model.get_dictionary() + data.update({'unit_number': 'Suite 1800'}) + response_data = self._test_put(model.id, data, {'form': 'update'}) + self.assertEqual(response_data['unit_number'], 'Suite 1800') + self.assertTrue(db.session.query(Address).filter( + and_(Address.id == model.id, Address.unit_number == data['unit_number'])).first()) + + def test_put_bad_form(self): + """Tests /address/ PUT with a bad form. It should 400.""" + + model = self.env.address + data = model.get_dictionary() + data.update({'unit_number': 'Suite 1800'}) + response = self.put( + self.url + str(model.id), + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + + def test_delete(self): + """Tests /address/ DELETE.""" + + model = self.env.address + self._test_delete(model.id) -- GitLab From d5db514333a9302ad435391fbbebcc78533900df Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 17:32:37 -0400 Subject: [PATCH 15/48] Register the views for: Address, Place --- app/views/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/__init__.py b/app/views/__init__.py index ee90b57..633ad1c 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -1,5 +1,5 @@ """Flask-classy views for the flask application.""" -from app.views import application, auth, project +from app.views import application, auth, project, place def register(app): @@ -13,3 +13,6 @@ def register(app): project.ProjectView.register(app) auth.GoogleAuthView.register(app) + + place.PlaceView.register(app) + place.AddressView.register(app) -- GitLab From 3512ca77a9a2cfa7656c9762b2178e9b09903952 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 17:33:08 -0400 Subject: [PATCH 16/48] Added environment properties for: Place, Address --- app/tests/environment.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/tests/environment.py b/app/tests/environment.py index 77ca585..dfd52eb 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -10,6 +10,7 @@ from app.lib.red import redis from app.lib.database import db, commit from app.models.application import Application +from app.models.place import Address, Place, State from app.models.project import Project @@ -65,3 +66,23 @@ class Environment(object): 'refresh_token': self.app.config['GOOGLE_REFRESH_TOKEN'], 'grant_type': 'refresh_token' }).json()['id_token'] + + @property + def state(self): + return State.get_state_by_code('NY') + + @property + def address(self): + address = Address( + street_address='15 MetroTech Center', + unit_number='1900', + city='Brooklyn', + county='Kings', + state_code=self.state.code, + postal_code='11210' + ) + return self.add(address) + + @property + def place(self): + return self.add(Place(name='test', address_id=self.address.id)) -- GitLab From 8f7395fbd7282eca29b851145a41352e7abe8994 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 17:33:32 -0400 Subject: [PATCH 17/48] Updated Place & Address forms --- app/forms/place.py | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/app/forms/place.py b/app/forms/place.py index d39cefd..d3535b4 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -3,52 +3,50 @@ import wtforms as wtf from app.forms.base import Form -class CreateAddressForm(Form): +class AddressForm(Form): """ A form for posting new Addresses. """ - sales_force_id = wtf.StringField( - validators=[wtf.validators.Length(max=255)]) - name = wtf.StringField( + street_address = wtf.StringField( validators=[ wtf.validators.Required(), wtf.validators.Length(max=255) ] ) + unit_number = wtf.StringField( + validators=[ + wtf.validators.Length(max=255) + ] + ) -class UpdateAddressForm(Form): - """ - A form for manipulating existing Addresses. - """ - - name = wtf.StringField( + city = wtf.StringField( validators=[ wtf.validators.Required(), wtf.validators.Length(max=255) ] ) + county = wtf.StringField( + validators=[ + wtf.validators.Length(max=255) + ] + ) -class CreatePlaceForm(Form): - """ - A form for posting new Places. - """ - - sales_force_id = wtf.StringField( - validators=[wtf.validators.Length(max=255)]) - name = wtf.StringField( + state_code = wtf.StringField( validators=[ wtf.validators.Required(), wtf.validators.Length(max=255) ] ) + postal_code = wtf.IntegerField() + -class UpdatePlaceForm(Form): +class PlaceForm(Form): """ - A form for manipulating existing Places. + A form for posting new Places. """ name = wtf.StringField( @@ -56,4 +54,5 @@ class UpdatePlaceForm(Form): wtf.validators.Required(), wtf.validators.Length(max=255) ] - ) \ No newline at end of file + ) + address_id = wtf.IntegerField(validators=[wtf.validators.Required()]) -- GitLab From 968bf3e322d22bd86b313f8ba5b11dc94d1ca825 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 17:34:06 -0400 Subject: [PATCH 18/48] Added Docstring and route_base for Place & Address Views --- app/views/place.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/place.py b/app/views/place.py index 86d09e8..1fd3497 100644 --- a/app/views/place.py +++ b/app/views/place.py @@ -7,6 +7,10 @@ from app.views.base import RestView class PlaceView(RestView): + """The Place view.""" + + route_base = '/place/' + def get_controller(self): """Return an instance of the Place controller.""" @@ -42,6 +46,10 @@ class PlaceView(RestView): class AddressView(RestView): + """The Address view.""" + + route_base = '/address/' + def get_controller(self): """Return an instance of the Address controller.""" -- GitLab From bec339ca7f43924774390313922f6ed8d383221c Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 17:34:44 -0400 Subject: [PATCH 19/48] Using updated Place Forms --- app/controllers/place.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index 3713e17..08dff5b 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -1,8 +1,7 @@ from werkzeug.exceptions import BadRequest from app.controllers.base import RestController -from app.forms.place import CreateAddressForm, CreatePlaceForm, UpdateAddressForm, \ - UpdateLocationForm, UpdatePlaceForm +from app.forms.place import AddressForm, PlaceForm from app.models.place import Address, Place @@ -12,15 +11,15 @@ class PlaceController(RestController): Model = Place def get_form(self, filter_data): - """ + """Return the Place form Args: filter_data - A dictionary """ try: return { - 'create': CreatePlaceForm, - 'update': UpdatePlaceForm + 'create': PlaceForm, + 'update': PlaceForm }[filter_data.get('form')] except KeyError: raise BadRequest('Invalid form.') @@ -38,8 +37,8 @@ class AddressController(RestController): """ try: return { - 'create': CreateAddressForm, - 'update': UpdateAddressForm + 'create': AddressForm, + 'update': AddressForm }[filter_data.get('form')] except KeyError: raise BadRequest('Invalid form.') -- GitLab From 55426ec40ee4260099d1e09419366fc8e83f2cd3 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Wed, 23 Mar 2016 17:35:14 -0400 Subject: [PATCH 20/48] Updated tests for: Place, Address --- app/tests/test_place.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 39839f0..4676d9e 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -28,8 +28,9 @@ class TestPlace(RestTestCase): data = { 'name': 'test', - 'address_id': 'test', + 'address_id': self.env.address.id, } + response_data = self._test_post(data, {'form': 'create'}) self.assertTrue( db.session.query(Place).filter( @@ -46,7 +47,7 @@ class TestPlace(RestTestCase): response = self.post( self.url, - data={'name': 'test', 'address_id': 'test'}, + data={'name': 'test', 'address_id': self.env.address.id}, query_string={'form': 'update'} ) self.assertEqual(response.status_code, 400) @@ -56,7 +57,7 @@ class TestPlace(RestTestCase): response = self.post( self.url, - data={'address_id': 'test'}, + data={'address_id': self.env.address.id}, query_string={'form': 'create'} ) self.assertEqual(response.status_code, 400) @@ -84,12 +85,6 @@ class TestPlace(RestTestCase): ) self.assertEqual(response.status_code, 400) - def test_delete(self): - """Tests /place/ DELETE.""" - - model = self.env.place - self._test_delete(model.id) - class TestAddress(RestTestCase): """Tests the /address/ endpoints.""" @@ -254,9 +249,3 @@ class TestAddress(RestTestCase): query_string={'form': 'create'} ) self.assertEqual(response.status_code, 400) - - def test_delete(self): - """Tests /address/ DELETE.""" - - model = self.env.address - self._test_delete(model.id) -- GitLab From a62024080507a02f2831260f0842c5ad68056ca4 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 12:22:15 -0400 Subject: [PATCH 21/48] Common Utils location for commonly used items --- app/lib/utils.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 app/lib/utils.py diff --git a/app/lib/utils.py b/app/lib/utils.py new file mode 100644 index 0000000..96cde3d --- /dev/null +++ b/app/lib/utils.py @@ -0,0 +1,64 @@ +"""Provides common utilities used across the codebase.""" + +# States in the United States and their State Codes +state_codes = [ + 'AL', + 'AK', + 'AS', + 'AZ', + 'AR', + 'CA', + 'CO', + 'CT', + 'DE', + 'DC', + 'FM', + 'FL', + 'GA', + 'GU', + 'HI', + 'ID', + 'IL', + 'IN', + 'IA', + 'KS', + 'KY', + 'LA', + 'ME', + 'MH', + 'MD', + 'MA', + 'MI', + 'MN', + 'MS', + 'MO', + 'MT', + 'NE', + 'NV', + 'NH', + 'NJ', + 'NM', + 'NY', + 'NC', + 'ND', + 'MP', + 'OH', + 'OK', + 'OR', + 'PW', + 'PA', + 'PR', + 'RI', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VT', + 'VI', + 'VA', + 'WA', + 'WV', + 'WI', + 'WY' +] -- GitLab From dbbe5faddade653729325b93f855bedda94d919c Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 12:23:25 -0400 Subject: [PATCH 22/48] Validating State Codes --- app/forms/place.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/forms/place.py b/app/forms/place.py index d3535b4..fb65ede 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -1,7 +1,7 @@ """Forms for validating Addresses, Locations, Places.""" import wtforms as wtf from app.forms.base import Form - +from app.lib.utils import state_codes class AddressForm(Form): """ @@ -36,7 +36,7 @@ class AddressForm(Form): state_code = wtf.StringField( validators=[ - wtf.validators.Required(), + wtf.validators.AnyOf(state_codes), wtf.validators.Length(max=255) ] ) -- GitLab From c9b7a44d0f4c50dd75a4a132603b7f761aa74a2e Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 12:24:34 -0400 Subject: [PATCH 23/48] Removed State Object Class. Place.state_code column only accepts values that are in app.lib.utils.state_codes --- app/models/place.py | 131 ++------------------------------------------ 1 file changed, 5 insertions(+), 126 deletions(-) diff --git a/app/models/place.py b/app/models/place.py index dcc0e19..c73311b 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -1,129 +1,10 @@ from geoalchemy2 import Geometry from app.lib.database import db +from app.lib.utils import state_codes from app.models.base import Model, Tracked -class State(object): - """Each State and Territory in the USA""" - - name = db.Column(db.Unicode(255)) - code = db.Column(db.Unicode(255)) - - states = { - 'AL': 'Alabama', - 'AK': 'Alaska', - 'AS': 'American Samoa', - 'AZ': 'Arizona', - 'AR': 'Arkansas', - 'CA': 'California', - 'CO': 'Colorado', - 'CT': 'Connecticut', - 'DE': 'Delaware', - 'DC': 'District Of Columbia', - 'FM': 'Federated States Of Micronesia', - 'FL': 'Florida', - 'GA': 'Georgia', - 'GU': 'Guam', - 'HI': 'Hawaii', - 'ID': 'Idaho', - 'IL': 'Illinois', - 'IN': 'Indiana', - 'IA': 'Iowa', - 'KS': 'Kansas', - 'KY': 'Kentucky', - 'LA': 'Louisiana', - 'ME': 'Maine', - 'MH': 'Marshall Islands', - 'MD': 'Maryland', - 'MA': 'Massachusetts', - 'MI': 'Michigan', - 'MN': 'Minnesota', - 'MS': 'Mississippi', - 'MO': 'Missouri', - 'MT': 'Montana', - 'NE': 'Nebraska', - 'NV': 'Nevada', - 'NH': 'New Hampshire', - 'NJ': 'New Jersey', - 'NM': 'New Mexico', - 'NY': 'New York', - 'NC': 'North Carolina', - 'ND': 'North Dakota', - 'MP': 'Northern Mariana Islands', - 'OH': 'Ohio', - 'OK': 'Oklahoma', - 'OR': 'Oregon', - 'PW': 'Palau', - 'PA': 'Pennsylvania', - 'PR': 'Puerto Rico', - 'RI': 'Rhode Island', - 'SC': 'South Carolina', - 'SD': 'South Dakota', - 'TN': 'Tennessee', - 'TX': 'Texas', - 'UT': 'Utah', - 'VT': 'Vermont', - 'VI': 'Virgin Islands', - 'VA': 'Virginia', - 'WA': 'Washington', - 'WV': 'West Virginia', - 'WI': 'Wisconsin', - 'WY': 'Wyoming' - } - - # TODO - # Use Redis to cache objects and methods in this class - - codes = list(states.keys()) - names = list(map(lambda x: x[1], states.items())) - names_to_upper = list(map(lambda x: x[1].upper(), states.items())) - names_to_lower = list(map(lambda x: x[1].lower(), states.items())) - - def __init__(self, name, code): - self.name = name - self.code = code - - @classmethod - def get_states(cls): - """Returns a list of State Objects""" - - states = [] - - for code, name in cls.states.items(): - states.append(cls(name=name, code=code)) - - @classmethod - def get_state_by_code(cls, code): - """Returns a State object, given a State code - Args: - code - A State code - """ - - code = code.upper() - - if code in cls.states.keys(): - return cls(name=cls.states[code], code=code) - - return None - - @classmethod - def get_state_by_name(cls, state_name): - """ Returns a State object, given a name - Args: - name - The name of the State - """ - - if state_name.upper() not in cls.names_to_lower: - return None - - for code, name in cls.states.items(): - if state_name.upper() == name.upper(): - return cls(name=name, code=code) - - return None - - class Place(Model, Tracked, db.Model): """The Place class""" @@ -139,17 +20,15 @@ class Address(Model, Tracked, db.Model): unit_number = db.Column(db.Unicode(255), index=True) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) - state_code = db.Column(db.Enum(*State.codes), nullable=False) + state_code = db.Column(db.Enum(*state_codes), nullable=False) postal_code = db.Column(db.Integer) - @property - def state(self): - """Returns a State object from Address.state_code""" - return State.get_state_by_code(self.state_code) - class Location(object): """The Location class""" + # TODO + # Convert this to a regular Class - Model, Tracked, db.Model + # After PostGIS and a SQLite equivalent have been setup (SpatiaLite) name = db.Column(db.Unicode(255), nullable=False) coords = db.Column(Geometry('POINT'), nullable=False) -- GitLab From d471de33952f0db33274bfb328ceedc178e2d741 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 12:24:59 -0400 Subject: [PATCH 24/48] Removed reference to app.models.place.State --- app/tests/environment.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/tests/environment.py b/app/tests/environment.py index dfd52eb..4bfaff6 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -10,7 +10,7 @@ from app.lib.red import redis from app.lib.database import db, commit from app.models.application import Application -from app.models.place import Address, Place, State +from app.models.place import Address, Place from app.models.project import Project @@ -67,10 +67,6 @@ class Environment(object): 'grant_type': 'refresh_token' }).json()['id_token'] - @property - def state(self): - return State.get_state_by_code('NY') - @property def address(self): address = Address( @@ -78,7 +74,7 @@ class Environment(object): unit_number='1900', city='Brooklyn', county='Kings', - state_code=self.state.code, + state_code='NY', postal_code='11210' ) return self.add(address) -- GitLab From c7f4809f8ca94713332c57ad64455e78d17c0aae Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 12:25:50 -0400 Subject: [PATCH 25/48] Added test for an Address with incorrect State Code --- app/tests/test_place.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 4676d9e..a14ca66 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -222,6 +222,25 @@ class TestAddress(RestTestCase): ) self.assertEqual(response.status_code, 400) + def test_post_valid_state_code_invalid_case(self): + """Tests /address/ POST with Valid state_code, invalid case. It should 400.""" + + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state_code': 'ny', + 'postal_code': '11210', + } + + response = self.post( + self.url, + data=data, + query_string={'form': 'create'} + ) + self.assertEqual(response.status_code, 400) + def test_put(self): """Tests /address/ PUT.""" -- GitLab From bea1693c58c322f9ee3a35385fe41a735d9708d8 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:33:42 -0400 Subject: [PATCH 26/48] Shorten state list. --- app/lib/utils.py | 64 ++++-------------------------------------------- 1 file changed, 5 insertions(+), 59 deletions(-) diff --git a/app/lib/utils.py b/app/lib/utils.py index 96cde3d..c31d3f8 100644 --- a/app/lib/utils.py +++ b/app/lib/utils.py @@ -2,63 +2,9 @@ # States in the United States and their State Codes state_codes = [ - 'AL', - 'AK', - 'AS', - 'AZ', - 'AR', - 'CA', - 'CO', - 'CT', - 'DE', - 'DC', - 'FM', - 'FL', - 'GA', - 'GU', - 'HI', - 'ID', - 'IL', - 'IN', - 'IA', - 'KS', - 'KY', - 'LA', - 'ME', - 'MH', - 'MD', - 'MA', - 'MI', - 'MN', - 'MS', - 'MO', - 'MT', - 'NE', - 'NV', - 'NH', - 'NJ', - 'NM', - 'NY', - 'NC', - 'ND', - 'MP', - 'OH', - 'OK', - 'OR', - 'PW', - 'PA', - 'PR', - 'RI', - 'SC', - 'SD', - 'TN', - 'TX', - 'UT', - 'VT', - 'VI', - 'VA', - 'WA', - 'WV', - 'WI', - 'WY' + 'AL', 'AK', 'AS', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FM', 'FL', + 'GA', 'GU', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MH', + 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', + 'NY', 'NC', 'ND', 'MP', 'OH', 'OK', 'OR', 'PW', 'PA', 'PR', 'RI', 'SC', + 'SD', 'TN', 'TX', 'UT', 'VT', 'VI', 'VA', 'WA', 'WV', 'WI', 'WY' ] -- GitLab From 11893f550a56e868f9d4c4b7e9df70e8696dd07b Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:37:04 -0400 Subject: [PATCH 27/48] Improve style guide and PEP compliance. --- app/controllers/place.py | 11 +++++------ app/forms/place.py | 34 +++++++++------------------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index 08dff5b..bcaf1c4 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -6,14 +6,13 @@ from app.models.place import Address, Place class PlaceController(RestController): - """The Place controller. """ - + """The Place controller.""" Model = Place def get_form(self, filter_data): - """Return the Place form + """Return the Place form. Args: - filter_data - A dictionary + filter_data (dict) """ try: @@ -31,9 +30,9 @@ class AddressController(RestController): Model = Address def get_form(self, filter_data): - """ + """Return the Address form. Args: - filter_data - A dictionary + filter_data (dict) """ try: return { diff --git a/app/forms/place.py b/app/forms/place.py index fb65ede..d71e431 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -3,56 +3,40 @@ import wtforms as wtf from app.forms.base import Form from app.lib.utils import state_codes -class AddressForm(Form): - """ - A form for posting new Addresses. - """ +class AddressForm(Form): + """A form for posting new Addresses.""" street_address = wtf.StringField( validators=[ wtf.validators.Required(), wtf.validators.Length(max=255) - ] - ) - + ]) unit_number = wtf.StringField( validators=[ wtf.validators.Length(max=255) - ] - ) - + ]) city = wtf.StringField( validators=[ wtf.validators.Required(), wtf.validators.Length(max=255) - ] - ) - + ]) county = wtf.StringField( validators=[ wtf.validators.Length(max=255) - ] - ) - + ]) state_code = wtf.StringField( validators=[ wtf.validators.AnyOf(state_codes), wtf.validators.Length(max=255) - ] - ) - + ]) postal_code = wtf.IntegerField() class PlaceForm(Form): - """ - A form for posting new Places. - """ - + """A form for posting new Places.""" name = wtf.StringField( validators=[ wtf.validators.Required(), wtf.validators.Length(max=255) - ] - ) + ]) address_id = wtf.IntegerField(validators=[wtf.validators.Required()]) -- GitLab From 246983be677d9a50b9f487e5f6b9d7ae88a3736c Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:38:11 -0400 Subject: [PATCH 28/48] Shorten the get_form methods in the place and address controllers. --- app/controllers/place.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index bcaf1c4..001b57a 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -14,14 +14,7 @@ class PlaceController(RestController): Args: filter_data (dict) """ - - try: - return { - 'create': PlaceForm, - 'update': PlaceForm - }[filter_data.get('form')] - except KeyError: - raise BadRequest('Invalid form.') + return PlaceForm class AddressController(RestController): @@ -34,10 +27,4 @@ class AddressController(RestController): Args: filter_data (dict) """ - try: - return { - 'create': AddressForm, - 'update': AddressForm - }[filter_data.get('form')] - except KeyError: - raise BadRequest('Invalid form.') + return AddressForm -- GitLab From ee8793d219c5f77c5ac891441be369217f38d1bf Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:39:54 -0400 Subject: [PATCH 29/48] Remove unnecesary form need checks in the place views. --- app/views/place.py | 49 ++++------------------------------------------ 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/app/views/place.py b/app/views/place.py index 1fd3497..429236f 100644 --- a/app/views/place.py +++ b/app/views/place.py @@ -2,52 +2,29 @@ from flask import request from werkzeug.exceptions import MethodNotAllowed from app.controllers.place import AddressController, PlaceController -from app.permissions.base import FormNeed from app.views.base import RestView class PlaceView(RestView): """The Place view.""" - route_base = '/place/' def get_controller(self): """Return an instance of the Place controller.""" - return PlaceController() - def post(self): - """Checks for a create form before posting.""" - - form = request.args.get('form') - with FormNeed('create', form): - return super(PlaceView, self).post() - - def put(self, id_): - """ - Args: - id_ - The object ID - """ - - form = request.args.get('form') - with FormNeed('update', form): - return super(PlaceView, self).put(id_) - def delete(self, id_): """Not implemented. - - Args: - id_ - The object ID + Args: + id_ - The object ID """ raise MethodNotAllowed( - 'Places cannot be deleted. Cancel the project instead.' - ) + 'Places cannot be deleted. Cancel the project instead.') class AddressView(RestView): """The Address view.""" - route_base = '/address/' def get_controller(self): @@ -55,23 +32,6 @@ class AddressView(RestView): return AddressController() - def post(self): - """Checks for a create form before posting.""" - - form = request.args.get('form') - with FormNeed('create', form): - return super(AddressView, self).post() - - def put(self, id_): - """ - Args: - id_ - The object ID - """ - - form = request.args.get('form') - with FormNeed('update', form): - return super(AddressView, self).put(id_) - def delete(self, id_): """Not implemented. @@ -80,5 +40,4 @@ class AddressView(RestView): """ raise MethodNotAllowed( - 'Addresses cannot be deleted. Cancel the project instead.' - ) \ No newline at end of file + 'Addresses cannot be deleted. Cancel the project instead.') -- GitLab From 85ea29801179a7d298a0495a9395cc582f6dcc14 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:40:30 -0400 Subject: [PATCH 30/48] Remove redundant route_base from address and place views. --- app/views/place.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/views/place.py b/app/views/place.py index 429236f..c03bcd5 100644 --- a/app/views/place.py +++ b/app/views/place.py @@ -7,8 +7,6 @@ from app.views.base import RestView class PlaceView(RestView): """The Place view.""" - route_base = '/place/' - def get_controller(self): """Return an instance of the Place controller.""" return PlaceController() @@ -18,18 +16,14 @@ class PlaceView(RestView): Args: id_ - The object ID """ - raise MethodNotAllowed( 'Places cannot be deleted. Cancel the project instead.') class AddressView(RestView): """The Address view.""" - route_base = '/address/' - def get_controller(self): """Return an instance of the Address controller.""" - return AddressController() def delete(self, id_): @@ -38,6 +32,5 @@ class AddressView(RestView): Args: id_ - The object ID """ - raise MethodNotAllowed( 'Addresses cannot be deleted. Cancel the project instead.') -- GitLab From b6c3eb95b4c3c9094148d034469f46e0ae184778 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:55:44 -0400 Subject: [PATCH 31/48] Remove form from place and address tests. --- app/tests/test_place.py | 180 +++++++++------------------------------- 1 file changed, 38 insertions(+), 142 deletions(-) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index a14ca66..6fb9a6c 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -25,65 +25,36 @@ class TestPlace(RestTestCase): def test_post(self): """Tests /place/ POST.""" - - data = { - 'name': 'test', - 'address_id': self.env.address.id, - } - - response_data = self._test_post(data, {'form': 'create'}) + data = {'name': 'test', 'address_id': self.env.address.id} + response_data = self._test_post(data) self.assertTrue( - db.session.query(Place).filter( - and_( + db.session.query(Place)\ + .filter(and_( Place.id == response_data['id'], Place.name == data['name'], Place.address_id == data['address_id'], - ) - ).first() - ) - - def test_post_bad_form(self): - """Tests /place/ POST with a bad form. It should 400.""" - - response = self.post( - self.url, - data={'name': 'test', 'address_id': self.env.address.id}, - query_string={'form': 'update'} - ) - self.assertEqual(response.status_code, 400) + ))\ + .first()) def test_post_missing_name(self): """Tests /place/ POST with a missing name. It should 400.""" - - response = self.post( - self.url, - data={'address_id': self.env.address.id}, - query_string={'form': 'create'} - ) + response = self.post(self.url, data={'address_id': self.env.address.id}) self.assertEqual(response.status_code, 400) def test_put(self): """Tests /place/ PUT.""" - model = self.env.place data = model.get_dictionary() data.update({'name': 'foo'}) - response_data = self._test_put(model.id, data, {'form': 'update'}) + response_data = self._test_put(model.id, data) self.assertEqual(response_data['name'], 'foo') - self.assertTrue(db.session.query(Place).filter(and_(Place.id == model.id, Place.name == data['name'])).first()) - - def test_put_bad_form(self): - """Tests /place/ PUT with a bad form. It should 400.""" - - model = self.env.place - data = model.get_dictionary() - data.update({'name': 'foo'}) - response = self.put( - self.url + str(model.id), - data=data, - query_string={'form': 'create'} - ) - self.assertEqual(response.status_code, 400) + self.assertTrue( + db.session.query(Place)\ + .filter(and_( + Place.id == model.id, + Place.name == data['name'] + ))\ + .first()) class TestAddress(RestTestCase): @@ -94,177 +65,102 @@ class TestAddress(RestTestCase): def test_index(self): """Tests /address/ GET.""" - model = self.env.address self._test_index() def test_get(self): """Tests /address/ GET.""" - model = self.env.address self._test_get(model.id) def test_post(self): """Tests /address/ POST.""" - data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state_code': 'NY', - 'postal_code': '11210', - } - - response_data = self._test_post(data, {'form': 'create'}) + 'postal_code': '11210'} + response_data = self._test_post(data) self.assertTrue( - db.session.query(Address).filter( - and_( + db.session.query(Address)\ + .filter(and_( Address.id == response_data['id'], Address.street_address == data['street_address'], Address.unit_number == data['unit_number'], Address.city == data['city'], Address.state_code == data['state_code'], - Address.postal_code == data['postal_code'], - ) - ).first() - ) - - def test_post_bad_form(self): - """Tests /address/ POST with a bad form. It should 400.""" - - data = { - 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', - 'city': 'Brooklyn', - 'county': 'Kings', - 'state_code': 'NY', - 'postal_code': '11210', - } - - response = self.post( - self.url, - data=data, - query_string={'form': 'update'} - ) - self.assertEqual(response.status_code, 400) + Address.postal_code == data['postal_code']))\ + .first()) def test_post_missing_street_address(self): """Tests /address/ POST with a missing street_address. It should 400.""" - data = { 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state_code': 'NY', - 'postal_code': '11210', - } - - response = self.post( - self.url, - data=data, - query_string={'form': 'create'} - ) + 'postal_code': '11210'} + response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_post_missing_city(self): """Tests /address/ POST with missing city. It should 400.""" - data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'county': 'Kings', 'state_code': 'NY', - 'postal_code': '11210', - } - - response = self.post( - self.url, - data=data, - query_string={'form': 'create'} - ) + 'postal_code': '11210'} + response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_post_missing_state_code(self): """Tests /address/ POST with missing state_code. It should 400.""" - data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', - 'postal_code': '11210', - } - - response = self.post( - self.url, - data=data, - query_string={'form': 'create'} - ) + 'postal_code': '11210'} + response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_post_invalid_state_code(self): """Tests /address/ POST with invalid state_code. It should 400.""" - data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state_code': 'NN', - 'postal_code': '11210', - } - - response = self.post( - self.url, - data=data, - query_string={'form': 'create'} - ) + 'postal_code': '11210'} + response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_post_valid_state_code_invalid_case(self): """Tests /address/ POST with Valid state_code, invalid case. It should 400.""" - data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state_code': 'ny', - 'postal_code': '11210', - } - - response = self.post( - self.url, - data=data, - query_string={'form': 'create'} - ) + 'postal_code': '11210'} + response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_put(self): """Tests /address/ PUT.""" - - data = { - 'unit_number': 'Suite 1800', - } - model = self.env.address data = model.get_dictionary() data.update({'unit_number': 'Suite 1800'}) - response_data = self._test_put(model.id, data, {'form': 'update'}) + response_data = self._test_put(model.id, data) self.assertEqual(response_data['unit_number'], 'Suite 1800') - self.assertTrue(db.session.query(Address).filter( - and_(Address.id == model.id, Address.unit_number == data['unit_number'])).first()) - - def test_put_bad_form(self): - """Tests /address/ PUT with a bad form. It should 400.""" - - model = self.env.address - data = model.get_dictionary() - data.update({'unit_number': 'Suite 1800'}) - response = self.put( - self.url + str(model.id), - data=data, - query_string={'form': 'create'} - ) - self.assertEqual(response.status_code, 400) + self.assertTrue( + db.session.query(Address)\ + .filter(and_( + Address.id == model.id, + Address.unit_number == data['unit_number']))\ + .first()) -- GitLab From 43f3b79f264b3b4b951e951dac3a254f6d9612c3 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:58:27 -0400 Subject: [PATCH 32/48] Improve style-guide compliance. --- app/tests/test_place.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 6fb9a6c..90fb9c0 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -7,19 +7,16 @@ from app.models.place import Place, Address class TestPlace(RestTestCase): """Tests the /place/ endpoints.""" - url = '/place/' Model = Place def test_index(self): """Tests /place/ GET.""" - model = self.env.place self._test_index() def test_get(self): """Tests /place/ GET.""" - model = self.env.place self._test_get(model.id) @@ -59,7 +56,6 @@ class TestPlace(RestTestCase): class TestAddress(RestTestCase): """Tests the /address/ endpoints.""" - url = '/address/' Model = Address -- GitLab From d18e54b3cfc31d692f79b946693a33be5d18caec Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 12:59:25 -0400 Subject: [PATCH 33/48] Remove dead state() property from the test environment. --- app/tests/environment.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/tests/environment.py b/app/tests/environment.py index ce18d8e..21fdfe1 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -114,10 +114,6 @@ class Environment(object): project_id=self.project.id, contact_id=self.contact.id)) - @property - def state(self): - return State.get_state_by_code('NY') - @property def address(self): address = Address( -- GitLab From a016229dec62d5e47d7806f82de0a36e46e5fdea Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 13:00:42 -0400 Subject: [PATCH 34/48] Remove the unused location model. --- app/models/place.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/models/place.py b/app/models/place.py index c73311b..1af3c29 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -7,7 +7,6 @@ from app.models.base import Model, Tracked class Place(Model, Tracked, 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) # location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) @@ -15,20 +14,9 @@ class Place(Model, Tracked, db.Model): class Address(Model, Tracked, db.Model): """The Address class""" - street_address = db.Column(db.Unicode(255), nullable=False) unit_number = db.Column(db.Unicode(255), index=True) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) state_code = db.Column(db.Enum(*state_codes), nullable=False) postal_code = db.Column(db.Integer) - - -class Location(object): - """The Location class""" - - # TODO - # Convert this to a regular Class - Model, Tracked, db.Model - # After PostGIS and a SQLite equivalent have been setup (SpatiaLite) - name = db.Column(db.Unicode(255), nullable=False) - coords = db.Column(Geometry('POINT'), nullable=False) -- GitLab From 1796f322f71f845a3246bdcb8031515449ebb736 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 13:11:49 -0400 Subject: [PATCH 35/48] Add sales_force_id to the place model. --- app/controllers/place.py | 1 + app/forms/place.py | 2 ++ app/models/place.py | 8 ++++---- app/tests/environment.py | 5 ++++- app/tests/test_place.py | 9 +++++++-- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index 001b57a..cf4f202 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -8,6 +8,7 @@ from app.models.place import Address, Place class PlaceController(RestController): """The Place controller.""" Model = Place + constant_fields = ['sales_force_id'] def get_form(self, filter_data): """Return the Place form. diff --git a/app/forms/place.py b/app/forms/place.py index d71e431..6746745 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -34,6 +34,8 @@ class AddressForm(Form): class PlaceForm(Form): """A form for posting new Places.""" + sales_force_id = wtf.StringField( + validators=[wtf.validators.Length(max=255)]) name = wtf.StringField( validators=[ wtf.validators.Required(), diff --git a/app/models/place.py b/app/models/place.py index 1af3c29..aadc314 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -2,14 +2,14 @@ from geoalchemy2 import Geometry from app.lib.database import db from app.lib.utils import state_codes -from app.models.base import Model, Tracked +from app.models.base import Model, Tracked, SalesForce -class Place(Model, 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) - # location_id = db.Column(db.Integer, db.ForeignKey('location.id'), nullable=False) + address_id = db.Column( + db.Integer, db.ForeignKey('address.id'), nullable=False) class Address(Model, Tracked, db.Model): diff --git a/app/tests/environment.py b/app/tests/environment.py index 21fdfe1..c4cd11d 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -128,4 +128,7 @@ class Environment(object): @property def place(self): - return self.add(Place(name='test', address_id=self.address.id)) + return self.add(Place( + sales_force_id='test', + name='test', + address_id=self.address.id)) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 90fb9c0..08df1cd 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -22,12 +22,16 @@ class TestPlace(RestTestCase): def test_post(self): """Tests /place/ POST.""" - data = {'name': 'test', 'address_id': self.env.address.id} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'address_id': self.env.address.id} response_data = self._test_post(data) self.assertTrue( db.session.query(Place)\ .filter(and_( Place.id == response_data['id'], + Place.sales_force_id == data['sales_force_id'], Place.name == data['name'], Place.address_id == data['address_id'], ))\ @@ -35,7 +39,8 @@ class TestPlace(RestTestCase): def test_post_missing_name(self): """Tests /place/ POST with a missing name. It should 400.""" - response = self.post(self.url, data={'address_id': self.env.address.id}) + data = {'sales_force_id': 'test', 'address_id': self.env.address.id} + response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) def test_put(self): -- GitLab From ea52b6efc917dea85d123cadae49da2cf1ca77b7 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 13:12:29 -0400 Subject: [PATCH 36/48] Make address_id on the place controller a constant field. --- app/controllers/place.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/place.py b/app/controllers/place.py index cf4f202..12a6b6c 100644 --- a/app/controllers/place.py +++ b/app/controllers/place.py @@ -8,7 +8,7 @@ from app.models.place import Address, Place class PlaceController(RestController): """The Place controller.""" Model = Place - constant_fields = ['sales_force_id'] + constant_fields = ['sales_force_id', 'address_id'] def get_form(self, filter_data): """Return the Place form. @@ -20,7 +20,6 @@ class PlaceController(RestController): class AddressController(RestController): """The Address controller.""" - Model = Address def get_form(self, filter_data): -- GitLab From 2ce9b1472672e22a9e4168e9b2f499aa06baf444 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 13:13:56 -0400 Subject: [PATCH 37/48] Added TODO for utils.state_code (Needs to be ENUM) --- app/lib/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/lib/utils.py b/app/lib/utils.py index c31d3f8..2439d19 100644 --- a/app/lib/utils.py +++ b/app/lib/utils.py @@ -1,5 +1,9 @@ """Provides common utilities used across the codebase.""" +# 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 +# beta, this should be ported to an Enum. + # States in the United States and their State Codes state_codes = [ 'AL', 'AK', 'AS', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FM', 'FL', -- GitLab From 78d78276fac5bb634933fa234a7207f036685bd6 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 13:14:47 -0400 Subject: [PATCH 38/48] Shorten the address property in the test environment. --- app/tests/environment.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/tests/environment.py b/app/tests/environment.py index c4cd11d..e50b305 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -116,15 +116,13 @@ class Environment(object): @property def address(self): - address = Address( + return self.add(Address( street_address='15 MetroTech Center', unit_number='1900', city='Brooklyn', county='Kings', state_code='NY', - postal_code='11210' - ) - return self.add(address) + postal_code='11210')) @property def place(self): -- GitLab From a40786b8f2bd555746c0894c82e9024a0efd6042 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 13:26:17 -0400 Subject: [PATCH 39/48] Port state/country over to lib.geography. --- app/forms/place.py | 34 +++++++++------------------- app/lib/{utils.py => geography.py} | 16 ++++++++----- app/models/place.py | 7 +++--- app/tests/environment.py | 3 ++- app/tests/test_place.py | 36 +++++++++++++----------------- 5 files changed, 42 insertions(+), 54 deletions(-) rename app/lib/{utils.py => geography.py} (58%) diff --git a/app/forms/place.py b/app/forms/place.py index 6746745..2be0a88 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -1,34 +1,23 @@ """Forms for validating Addresses, Locations, Places.""" import wtforms as wtf from app.forms.base import Form -from app.lib.utils import state_codes +from app.lib import geography class AddressForm(Form): """A form for posting new Addresses.""" street_address = wtf.StringField( - validators=[ - wtf.validators.Required(), - wtf.validators.Length(max=255) - ]) + validators=[wtf.validators.Required(), wtf.validators.Length(max=255)]) unit_number = wtf.StringField( - validators=[ - wtf.validators.Length(max=255) - ]) + validators=[wtf.validators.Length(max=255)]) city = wtf.StringField( - validators=[ - wtf.validators.Required(), - wtf.validators.Length(max=255) - ]) + validators=[wtf.validators.Required(), wtf.validators.Length(max=255)]) county = wtf.StringField( - validators=[ - wtf.validators.Length(max=255) - ]) - state_code = wtf.StringField( - validators=[ - wtf.validators.AnyOf(state_codes), - wtf.validators.Length(max=255) - ]) + validators=[wtf.validators.Length(max=255)]) + state = wtf.StringField( + validators=[wtf.validators.AnyOf(geography.states)]) + country = wtf.StringField( + validators=[wtf.validators.AnyOf(geography.countries)]) postal_code = wtf.IntegerField() @@ -37,8 +26,5 @@ class PlaceForm(Form): sales_force_id = wtf.StringField( validators=[wtf.validators.Length(max=255)]) name = wtf.StringField( - validators=[ - wtf.validators.Required(), - wtf.validators.Length(max=255) - ]) + validators=[wtf.validators.Required(), wtf.validators.Length(max=255)]) address_id = wtf.IntegerField(validators=[wtf.validators.Required()]) diff --git a/app/lib/utils.py b/app/lib/geography.py similarity index 58% rename from app/lib/utils.py rename to app/lib/geography.py index 2439d19..57c94c8 100644 --- a/app/lib/utils.py +++ b/app/lib/geography.py @@ -1,14 +1,20 @@ -"""Provides common utilities used across the codebase.""" - +"""Utilities for working with geographical entities.""" +# Valid strings representing valid provinces and states. +# # 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 # beta, this should be ported to an Enum. - -# States in the United States and their State Codes -state_codes = [ +states = [ 'AL', 'AK', 'AS', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FM', 'FL', 'GA', 'GU', 'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MH', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'MP', 'OH', 'OK', 'OR', 'PW', 'PA', 'PR', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VI', 'VA', 'WA', 'WV', 'WI', 'WY' ] + +# Valid strings representing countries. +# +# 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 +# beta, this should be ported to an Enum. +countries = ['United States of America'] diff --git a/app/models/place.py b/app/models/place.py index aadc314..b12d124 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -1,7 +1,7 @@ from geoalchemy2 import Geometry from app.lib.database import db -from app.lib.utils import state_codes +from app.lib import geography from app.models.base import Model, Tracked, SalesForce @@ -15,8 +15,9 @@ class Place(Model, Tracked, SalesForce, db.Model): class Address(Model, Tracked, db.Model): """The Address class""" street_address = db.Column(db.Unicode(255), nullable=False) - unit_number = db.Column(db.Unicode(255), index=True) + unit_number = db.Column(db.Unicode(255)) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) - state_code = db.Column(db.Enum(*state_codes), nullable=False) + state = db.Column(db.Enum(*geography.states), nullable=False) + country = db.Column(db.Enum(*geography.countries), nullable=False) postal_code = db.Column(db.Integer) diff --git a/app/tests/environment.py b/app/tests/environment.py index e50b305..e63a173 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -121,7 +121,8 @@ class Environment(object): unit_number='1900', city='Brooklyn', county='Kings', - state_code='NY', + state='NY', + country='United States of America', postal_code='11210')) @property diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 08df1cd..99d6e75 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -81,7 +81,8 @@ class TestAddress(RestTestCase): 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', - 'state_code': 'NY', + 'state': 'NY', + 'country': 'United States of America', 'postal_code': '11210'} response_data = self._test_post(data) self.assertTrue( @@ -91,7 +92,8 @@ class TestAddress(RestTestCase): Address.street_address == data['street_address'], Address.unit_number == data['unit_number'], Address.city == data['city'], - Address.state_code == data['state_code'], + Address.state == data['state'], + Address.country == data['country'], Address.postal_code == data['postal_code']))\ .first()) @@ -101,7 +103,8 @@ class TestAddress(RestTestCase): 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', - 'state_code': 'NY', + 'state': 'NY', + 'country': 'United States of America', 'postal_code': '11210'} response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) @@ -112,42 +115,33 @@ class TestAddress(RestTestCase): 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'county': 'Kings', - 'state_code': 'NY', + 'state': 'NY', + 'country': 'United States of America', 'postal_code': '11210'} response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) - def test_post_missing_state_code(self): - """Tests /address/ POST with missing state_code. It should 400.""" + def test_post_missing_state(self): + """Tests /address/ POST with missing state. It should 400.""" data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', + 'country': 'United States of America', 'postal_code': '11210'} response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) - def test_post_invalid_state_code(self): - """Tests /address/ POST with invalid state_code. It should 400.""" + def test_post_invalid_state(self): + """Tests /address/ POST with invalid state. It should 400.""" data = { 'street_address': '15 MetroTech Center', 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', - 'state_code': 'NN', - 'postal_code': '11210'} - response = self.post(self.url, data=data) - self.assertEqual(response.status_code, 400) - - def test_post_valid_state_code_invalid_case(self): - """Tests /address/ POST with Valid state_code, invalid case. It should 400.""" - data = { - 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', - 'city': 'Brooklyn', - 'county': 'Kings', - 'state_code': 'ny', + 'state': 'NN', + 'country': 'United States of America', 'postal_code': '11210'} response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) -- GitLab From 1dd5b904b0c36fb41c870dcfa3c7c96570d94240 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 14:38:03 -0400 Subject: [PATCH 40/48] Add a place id to the project model. --- app/controllers/project.py | 2 +- app/forms/project.py | 1 + app/models/project.py | 2 ++ app/tests/environment.py | 37 +++++++++++++++++++------------------ app/tests/test_project.py | 23 +++++++++++++++++++++++ 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/app/controllers/project.py b/app/controllers/project.py index f79edea..24931b6 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -9,7 +9,7 @@ from app.forms.project import ProjectForm class ProjectController(RestController): """The project controller.""" Model = Project - constant_fields = ['sales_force_id', 'client_id'] + constant_fields = ['sales_force_id', 'client_id', 'place_id'] def get_form(self, filter_data): """Return the project form.""" diff --git a/app/forms/project.py b/app/forms/project.py index e56e9e0..d69d9bf 100644 --- a/app/forms/project.py +++ b/app/forms/project.py @@ -10,6 +10,7 @@ class ProjectForm(Form): sales_force_id = wtf.StringField( validators=[wtf.validators.Length(max=255)]) client_id = wtf.IntegerField(validators=[wtf.validators.Required()]) + place_id = wtf.IntegerField(validators=[wtf.validators.Required()]) name = wtf.StringField( validators=[ wtf.validators.Required(), diff --git a/app/models/project.py b/app/models/project.py index d572634..ec3bbfd 100644 --- a/app/models/project.py +++ b/app/models/project.py @@ -36,6 +36,8 @@ class Project(Model, Tracked, SalesForce, db.Model): client_id = db.Column( db.Integer, db.ForeignKey('client.id'), nullable=False) + place_id = db.Column( + db.Integer, db.ForeignKey('place.id'), nullable=False) name = db.Column(db.Unicode(255), nullable=False) state = db.Column(db.Enum(*states), default='pending', nullable=False) diff --git a/app/tests/environment.py b/app/tests/environment.py index e63a173..2bbd2e8 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -69,11 +69,30 @@ class Environment(object): def client(self): return self.add(Client(sales_force_id='test', name='test')) + @property + def address(self): + return self.add(Address( + street_address='15 MetroTech Center', + unit_number='1900', + city='Brooklyn', + county='Kings', + state='NY', + country='United States of America', + postal_code='11210')) + + @property + def place(self): + return self.add(Place( + sales_force_id='test', + name='test', + address_id=self.address.id)) + @property def project(self): return self.add(Project( sales_force_id='test', client_id=self.client.id, + place_id=self.place.id, name='test')) @property @@ -113,21 +132,3 @@ class Environment(object): return self.add(ProjectContact( project_id=self.project.id, contact_id=self.contact.id)) - - @property - def address(self): - return self.add(Address( - street_address='15 MetroTech Center', - unit_number='1900', - city='Brooklyn', - county='Kings', - state='NY', - country='United States of America', - postal_code='11210')) - - @property - def place(self): - return self.add(Place( - sales_force_id='test', - name='test', - address_id=self.address.id)) diff --git a/app/tests/test_project.py b/app/tests/test_project.py index fbc4b85..ac68ef2 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -30,6 +30,7 @@ class TestProject(RestTestCase): data = { 'sales_force_id': 'test', 'client_id': self.env.client.id, + 'place_id': self.env.place.id, 'name': 'test', 'state': 'pending'} response_data = self._test_post(data) @@ -54,6 +55,7 @@ class TestProject(RestTestCase): data={ 'sales_force_id': 'test', 'client_id': self.env.client.id, + 'place_id': self.env.place.id, 'state': 'pending'}) self.assertEqual(response.status_code, 400) @@ -63,6 +65,7 @@ class TestProject(RestTestCase): data={ 'sales_force_id': 'test', 'client_id': self.env.client.id, + 'place_id': self.env.place.id, 'name': 'test'}) self.assertEqual(response.status_code, 400) @@ -82,6 +85,18 @@ class TestProject(RestTestCase): data={ 'sales_force_id': 'test', 'client_id': 1, + 'place_id': self.env.place.id, + 'name': 'test', + 'state': 'pending'}) + self.assertEqual(response.status_code, 400) + + def test_post_bad_place_id(self): + """Tests /project/ POST with a bad place id.""" + response = self.post(self.url, + data={ + 'sales_force_id': 'test', + 'client_id': self.env.client.id, + 'place_id': 1, 'name': 'test', 'state': 'pending'}) self.assertEqual(response.status_code, 400) @@ -145,6 +160,14 @@ class TestProject(RestTestCase): response = self.put(self.url + str(model.id), data=data) self.assertEqual(response.status_code, 400) + def test_put_place_id(self): + """Tests /project/ PUT with a place id. It should 400.""" + model = self.env.project + data = model.get_dictionary() + data.update({'place_id': self.env.place.id}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + def test_delete(self): """Tests /project/ DELETE. It should 405.""" model = self.env.project -- GitLab From fb310cd310097e175d877ea097f4d63bcd4e52c8 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 15:05:20 -0400 Subject: [PATCH 41/48] Redundant. Client class inherits from Salesforce. --- app/models/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/client.py b/app/models/client.py index a473e61..afa2543 100644 --- a/app/models/client.py +++ b/app/models/client.py @@ -5,5 +5,4 @@ from app.models.base import Model, Tracked, SalesForce class Client(Model, Tracked, SalesForce, db.Model): """A client.""" - sales_force_id = db.Column(db.Unicode(255), index=True) name = db.Column(db.Unicode(255), nullable=False) -- GitLab From 21977988c01fd24dd8259ffe7d77f70fa23e2812 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 15:07:28 -0400 Subject: [PATCH 42/48] Added tests for Place.address_id & Address.country --- app/tests/test_place.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 99d6e75..f99698d 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -43,6 +43,18 @@ class TestPlace(RestTestCase): response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) + def test_post_missing_address_id(self): + """Tests /place/ POST with a missing address ID. It should 400.""" + data = {'sales_force_id': 'test', 'name': 'test'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_post_bad_address_id(self): + """Tests /place/ POST with a bad address ID. It should 400.""" + data = {'sales_force_id': 'test', 'name': 'test', 'address_id': '100000000'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + def test_put(self): """Tests /place/ PUT.""" model = self.env.place @@ -146,6 +158,31 @@ class TestAddress(RestTestCase): response = self.post(self.url, data=data) self.assertEqual(response.status_code, 400) + def test_post_missing_country(self): + """Tests /address/ POST with missing Country. It should 400.""" + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state': 'NY', + 'postal_code': '11210'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_post_invalid_country(self): + """Tests /address/ POST with missing Country. It should 400.""" + data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state': 'NY', + 'country': 'United States of America!', + 'postal_code': '11210'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + def test_put(self): """Tests /address/ PUT.""" model = self.env.address -- GitLab From f0bacbbe22a249ea4c9c46476cf688ae1dd0f954 Mon Sep 17 00:00:00 2001 From: Chimumbwa George Shikopa Date: Thu, 24 Mar 2016 15:40:22 -0400 Subject: [PATCH 43/48] Added Tests for Place, Address Classes Address: PUT (sales_force_id, address), DELETE Place: PUT, DELETE --- app/tests/test_place.py | 49 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index f99698d..01a0b7e 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -70,6 +70,28 @@ class TestPlace(RestTestCase): ))\ .first()) + def test_put_sales_force_id(self): + """Tests /place/ PUT with a sales force id. It should 400.""" + model = self.env.project + data = model.get_dictionary() + data.update({'sales_force_id': 'foo'}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_put_address_id(self): + """Tests /place/ PUT with a place id. It should 400.""" + model = self.env.place + data = model.get_dictionary() + data.update({'address_id': self.env.address.id}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_delete(self): + """Tests /place/ DELETE. It should 405.""" + model = self.env.place + response = self.delete(self.url + str(model.id)) + self.assertEqual(response.status_code, 405) + class TestAddress(RestTestCase): """Tests the /address/ endpoints.""" @@ -187,12 +209,33 @@ class TestAddress(RestTestCase): """Tests /address/ PUT.""" model = self.env.address data = model.get_dictionary() - data.update({'unit_number': 'Suite 1800'}) + data.update({'street_address': '1600 Pennsylvania Ave'}) + data.update({'unit_number': 'Oval Office'}) + data.update({'city': 'Washington, DC'}) + data.update({'county': 'Washington County, DC'}) + data.update({'state': 'DC'}) + data.update({'postal_code': '20006'}) response_data = self._test_put(model.id, data) - self.assertEqual(response_data['unit_number'], 'Suite 1800') + self.assertEqual(response_data['street_address'], '1600 Pennsylvania Ave') + self.assertEqual(response_data['unit_number'], 'Oval Office') + self.assertEqual(response_data['city'], 'Washington, DC') + self.assertEqual(response_data['county'], 'Washington County, DC') + self.assertEqual(response_data['state'], 'DC') + self.assertEqual(response_data['postal_code'], 20006) self.assertTrue( db.session.query(Address)\ .filter(and_( Address.id == model.id, - Address.unit_number == data['unit_number']))\ + Address.street_address == data['street_address'], + Address.unit_number == data['unit_number'], + Address.city == data['city'], + Address.county == data['county'], + Address.state == data['state'], + Address.postal_code == data['postal_code'])) .first()) + + def test_delete(self): + """Tests /address/ DELETE. It should 405.""" + model = self.env.address + response = self.delete(self.url + str(model.id)) + self.assertEqual(response.status_code, 405) -- GitLab From 285db59914a561b8ce635ffca0cb8743f725671c Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 15:43:46 -0400 Subject: [PATCH 44/48] Fix typo. --- app/tests/test_place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 01a0b7e..f737ad3 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -79,7 +79,7 @@ class TestPlace(RestTestCase): self.assertEqual(response.status_code, 400) def test_put_address_id(self): - """Tests /place/ PUT with a place id. It should 400.""" + """Tests /place/ PUT with an address id. It should 400.""" model = self.env.place data = model.get_dictionary() data.update({'address_id': self.env.address.id}) -- GitLab From 84b903bf8c9b35e176d42ef783327cf1b0619fbb Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 15:51:02 -0400 Subject: [PATCH 45/48] Add a zip code validator. --- app/forms/validators.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/forms/validators.py b/app/forms/validators.py index 1aed526..da9c2ec 100644 --- a/app/forms/validators.py +++ b/app/forms/validators.py @@ -22,3 +22,6 @@ class Map(object): # A phone number validator. phone = wtf.validators.Regexp(r'^[0-9]{10,}$') + +# A zip code validator. +zip_ = wtf.validators.Regexp(r'^\d{5}(-\d{4})?$') -- GitLab From 17d7c53afe01da0720593c45ad12ed8e1b951f14 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 15:51:18 -0400 Subject: [PATCH 46/48] Use a string field for zip code. --- app/forms/place.py | 3 ++- app/models/place.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/forms/place.py b/app/forms/place.py index 2be0a88..7aadf06 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -1,5 +1,6 @@ """Forms for validating Addresses, Locations, Places.""" import wtforms as wtf +from app.forms import validators from app.forms.base import Form from app.lib import geography @@ -18,7 +19,7 @@ class AddressForm(Form): validators=[wtf.validators.AnyOf(geography.states)]) country = wtf.StringField( validators=[wtf.validators.AnyOf(geography.countries)]) - postal_code = wtf.IntegerField() + postal_code = wtf.StringField(validators=[validators.zip_]) class PlaceForm(Form): diff --git a/app/models/place.py b/app/models/place.py index b12d124..9d83f2b 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -20,4 +20,4 @@ class Address(Model, Tracked, db.Model): county = db.Column(db.Unicode(255)) state = db.Column(db.Enum(*geography.states), nullable=False) country = db.Column(db.Enum(*geography.countries), nullable=False) - postal_code = db.Column(db.Integer) + postal_code = db.Column(db.Unicode(10)) -- GitLab From 0fa92cfca60489a5fbd79770eba3eee336b5a96d Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 15:51:37 -0400 Subject: [PATCH 47/48] Shorten the unit test for /address/ PUT. --- app/tests/test_place.py | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/app/tests/test_place.py b/app/tests/test_place.py index f737ad3..44128e7 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -209,30 +209,22 @@ class TestAddress(RestTestCase): """Tests /address/ PUT.""" model = self.env.address data = model.get_dictionary() - data.update({'street_address': '1600 Pennsylvania Ave'}) - data.update({'unit_number': 'Oval Office'}) - data.update({'city': 'Washington, DC'}) - data.update({'county': 'Washington County, DC'}) - data.update({'state': 'DC'}) - data.update({'postal_code': '20006'}) + new_data = { + 'street_address': '1600 Pennsylvania Ave', + 'unit_number': 'Oval Office', + 'city': 'Washington, DC', + 'county': 'Washington County, DC', + 'state': 'DC', + 'postal_code': '20006'} + data.update(new_data) response_data = self._test_put(model.id, data) - self.assertEqual(response_data['street_address'], '1600 Pennsylvania Ave') - self.assertEqual(response_data['unit_number'], 'Oval Office') - self.assertEqual(response_data['city'], 'Washington, DC') - self.assertEqual(response_data['county'], 'Washington County, DC') - self.assertEqual(response_data['state'], 'DC') - self.assertEqual(response_data['postal_code'], 20006) + for key, value in new_data.items(): + self.assertEqual(response_data[key], value) + + filters = [Address.id == model.id] + filters += [getattr(Address, k) == data[k] for k in new_data.keys()] self.assertTrue( - db.session.query(Address)\ - .filter(and_( - Address.id == model.id, - Address.street_address == data['street_address'], - Address.unit_number == data['unit_number'], - Address.city == data['city'], - Address.county == data['county'], - Address.state == data['state'], - Address.postal_code == data['postal_code'])) - .first()) + db.session.query(Address).filter(and_(*filters)).first()) def test_delete(self): """Tests /address/ DELETE. It should 405.""" -- GitLab From ee35adb90e9768b7a180ffdc282bc46cf0b6d0d5 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 16:44:27 -0400 Subject: [PATCH 48/48] Combine unit number and street address fields in the address model. --- app/forms/place.py | 2 -- app/models/place.py | 1 - app/tests/environment.py | 1 - app/tests/test_place.py | 9 --------- 4 files changed, 13 deletions(-) diff --git a/app/forms/place.py b/app/forms/place.py index 7aadf06..08c522b 100644 --- a/app/forms/place.py +++ b/app/forms/place.py @@ -9,8 +9,6 @@ class AddressForm(Form): """A form for posting new Addresses.""" street_address = wtf.StringField( validators=[wtf.validators.Required(), wtf.validators.Length(max=255)]) - unit_number = wtf.StringField( - validators=[wtf.validators.Length(max=255)]) city = wtf.StringField( validators=[wtf.validators.Required(), wtf.validators.Length(max=255)]) county = wtf.StringField( diff --git a/app/models/place.py b/app/models/place.py index 9d83f2b..5062eb7 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -15,7 +15,6 @@ class Place(Model, Tracked, SalesForce, db.Model): class Address(Model, Tracked, db.Model): """The Address class""" street_address = db.Column(db.Unicode(255), nullable=False) - unit_number = db.Column(db.Unicode(255)) city = db.Column(db.Unicode(255), nullable=False) county = db.Column(db.Unicode(255)) state = db.Column(db.Enum(*geography.states), nullable=False) diff --git a/app/tests/environment.py b/app/tests/environment.py index 2bbd2e8..b75aec7 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -73,7 +73,6 @@ class Environment(object): def address(self): return self.add(Address( street_address='15 MetroTech Center', - unit_number='1900', city='Brooklyn', county='Kings', state='NY', diff --git a/app/tests/test_place.py b/app/tests/test_place.py index 44128e7..5aba1bc 100644 --- a/app/tests/test_place.py +++ b/app/tests/test_place.py @@ -112,7 +112,6 @@ class TestAddress(RestTestCase): """Tests /address/ POST.""" data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NY', @@ -124,7 +123,6 @@ class TestAddress(RestTestCase): .filter(and_( Address.id == response_data['id'], Address.street_address == data['street_address'], - Address.unit_number == data['unit_number'], Address.city == data['city'], Address.state == data['state'], Address.country == data['country'], @@ -134,7 +132,6 @@ class TestAddress(RestTestCase): def test_post_missing_street_address(self): """Tests /address/ POST with a missing street_address. It should 400.""" data = { - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NY', @@ -147,7 +144,6 @@ class TestAddress(RestTestCase): """Tests /address/ POST with missing city. It should 400.""" data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'county': 'Kings', 'state': 'NY', 'country': 'United States of America', @@ -159,7 +155,6 @@ class TestAddress(RestTestCase): """Tests /address/ POST with missing state. It should 400.""" data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'country': 'United States of America', @@ -171,7 +166,6 @@ class TestAddress(RestTestCase): """Tests /address/ POST with invalid state. It should 400.""" data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NN', @@ -184,7 +178,6 @@ class TestAddress(RestTestCase): """Tests /address/ POST with missing Country. It should 400.""" data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NY', @@ -196,7 +189,6 @@ class TestAddress(RestTestCase): """Tests /address/ POST with missing Country. It should 400.""" data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NY', @@ -211,7 +203,6 @@ class TestAddress(RestTestCase): data = model.get_dictionary() new_data = { 'street_address': '1600 Pennsylvania Ave', - 'unit_number': 'Oval Office', 'city': 'Washington, DC', 'county': 'Washington County, DC', 'state': 'DC', -- GitLab