diff --git a/.gitignore b/.gitignore index 1e4aa24..90a96e2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ build/ develop-eggs/ dist/ eggs/ +.eggs/ parts/ sdist/ var/ @@ -47,6 +48,7 @@ coverage.xml *.log *.pot oah +*.sqlite3 # Sphinx documentation docs/_build/ diff --git a/.travis.yml b/.travis.yml index 2c16152..a3bc198 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,10 @@ language: python python: - 2.7.14 install: -- pip install -r requirements/test.txt +- pip install tox - pip install coveralls script: -- export DJANGO_SETTINGS_MODULE=settings_for_testing -- coverage run manage.py test +- tox - python2.7 setup.py bdist_wheel after_success: - coveralls diff --git a/README.md b/README.md index 4be585d..ce7e7a3 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,6 @@ These instructions are for installation on a Mac with OS X Yosemite (version 10. **Optional** * [Homebrew](http://brew.sh) -* [MySQL](http://www.mysql.com) -* [MySQL Python](http://mysql-python.sourceforge.net/) #### Steps for firing up Django - It's useful to create a [virtualenv](https://virtualenv.pypa.io/en/latest/) virtual environment to keep Python dependencies sandboxed: @@ -40,7 +38,7 @@ cd ~/workspace git clone https://github.com/cfpb/owning-a-home-api.git cd owning-a-home-api/ setvirtualenvproject -pip install -r requirements/test.txt +pip install -e '.[testing]' ``` - Initialize your database, load some basic data and launch a development server: @@ -69,14 +67,25 @@ This repo contains limited data, but you can explore mortgage interest rates in ## Deeper dive -You can find more about using the API endpoints and the optional use of a MySQL database in our [API documentation pages](https://cfpb.github.io/owning-a-home-api/). +You can find [additional documentation for the `ratechecker` app](ratechecker). -See also [additional documentation for the `ratechecker` app](ratechecker). -## Testing -You can run Python unit tests and see code coverage by running: +## Running Tests + +If you have [Tox](https://tox.readthedocs.io/en/latest/) installed (recommended), +you can run the specs for this project with the `tox` command. + +If not, this command will run the specs on the python version your local +environment has installed: `./manage.py test`. + +If you run the tests via Tox, it will automatically display spec coverage information. +To get test coverage information outside of Tox, install [Coverage.py](https://coverage.readthedocs.io/en/coverage-4.5.1a/) +and run these commands: + ``` -./pytest.sh +coverage erase +coverage run manage.py test +coverage report ``` ## Contributions diff --git a/countylimits/data_collection/county_data_monitor.py b/countylimits/data_collection/county_data_monitor.py index 2889461..86a564d 100644 --- a/countylimits/data_collection/county_data_monitor.py +++ b/countylimits/data_collection/county_data_monitor.py @@ -14,7 +14,7 @@ def get_current_log(): changelog_response = requests.get(CENSUS_CHANGELOG) changelog_response.raise_for_status() - soup = bs(changelog_response.text, 'lxml') + soup = bs(changelog_response.text, 'html.parser') return soup.find("div", {"id": CHANGELOG_ID}).text diff --git a/countylimits/migrations/0001_initial.py b/countylimits/migrations/0001_initial.py index 387bcc0..0e48273 100644 --- a/countylimits/migrations/0001_initial.py +++ b/countylimits/migrations/0001_initial.py @@ -1,74 +1,51 @@ # -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'State' - db.create_table(u'countylimits_state', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('state_fips', self.gf('django.db.models.fields.CharField')(max_length=2)), - ('state_abbr', self.gf('localflavor.us.models.USStateField')(max_length=2)), - )) - db.send_create_signal(u'countylimits', ['State']) - - # Adding model 'County' - db.create_table(u'countylimits_county', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('county_fips', self.gf('django.db.models.fields.CharField')(max_length=3)), - ('county_name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('state', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['countylimits.State'])), - )) - db.send_create_signal(u'countylimits', ['County']) - - # Adding model 'CountyLimit' - db.create_table(u'countylimits_countylimit', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('fha_limit', self.gf('django.db.models.fields.DecimalField')(max_digits=12, decimal_places=2)), - ('gse_limit', self.gf('django.db.models.fields.DecimalField')(max_digits=12, decimal_places=2)), - ('va_limit', self.gf('django.db.models.fields.DecimalField')(max_digits=12, decimal_places=2)), - ('county', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['countylimits.County'], unique=True)), - )) - db.send_create_signal(u'countylimits', ['CountyLimit']) - - - def backwards(self, orm): - # Deleting model 'State' - db.delete_table(u'countylimits_state') - - # Deleting model 'County' - db.delete_table(u'countylimits_county') - - # Deleting model 'CountyLimit' - db.delete_table(u'countylimits_countylimit') - - - models = { - u'countylimits.county': { - 'Meta': {'object_name': 'County'}, - 'county_fips': ('django.db.models.fields.CharField', [], {'max_length': '3'}), - 'county_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['countylimits.State']"}) - }, - u'countylimits.countylimit': { - 'Meta': {'object_name': 'CountyLimit'}, - 'county': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['countylimits.County']", 'unique': 'True'}), - 'fha_limit': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'gse_limit': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'va_limit': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}) - }, - u'countylimits.state': { - 'Meta': {'object_name': 'State'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'state_abbr': ('localflavor.us.models.USStateField', [], {'max_length': '2'}), - 'state_fips': ('django.db.models.fields.CharField', [], {'max_length': '2'}) - } - } - - complete_apps = ['countylimits'] \ No newline at end of file +from __future__ import unicode_literals + +from django.db import migrations, models +import localflavor.us.models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='County', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('county_fips', models.CharField(help_text=b"A three-digit FIPS code for the state's county", max_length=3)), + ('county_name', models.CharField(help_text=b'The county name', max_length=100)), + ], + options={ + 'ordering': ['county_fips'], + }, + ), + migrations.CreateModel( + name='CountyLimit', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('fha_limit', models.DecimalField(help_text=b'Federal Housing Administration loan lending limit for the county', max_digits=12, decimal_places=2)), + ('gse_limit', models.DecimalField(help_text=b'Loan limit for mortgages acquired by the Government-Sponsored Enterprises', max_digits=12, decimal_places=2)), + ('va_limit', models.DecimalField(help_text=b'The Department of Veterans Affairs loan guaranty program limit', max_digits=12, decimal_places=2)), + ('county', models.OneToOneField(to='countylimits.County')), + ], + ), + migrations.CreateModel( + name='State', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('state_fips', models.CharField(help_text=b'A two-digit FIPS code for the state', max_length=2)), + ('state_abbr', localflavor.us.models.USStateField(help_text=b'A two-letter state abbreviation', max_length=2)), + ], + options={ + 'ordering': ['state_fips'], + }, + ), + migrations.AddField( + model_name='county', + name='state', + field=models.ForeignKey(to='countylimits.State'), + ), + ] diff --git a/docs/mysql.md b/docs/mysql.md deleted file mode 100644 index c0f719e..0000000 --- a/docs/mysql.md +++ /dev/null @@ -1,54 +0,0 @@ -# Using MySQL - -For testing, the default Django sqlite database will be set up for you automatically. If you want to load a MySQL dataset, you can install MySQL as follows and run tests by specifying a DATABASE_URL like so: `DATABASE_URL=mysql://user:password@localhost/db ./pytest.sh` - -Installing MySQL: -```shell -pip install requirements/mysql.txt -brew install mysql -``` -Start the MySQL Server, this command may need to be run again (if stopped) when trying to bring up the web server later: -```shell -mysql.server start -``` -Set Password for root: -```shell -mysql_secure_installation -``` -Connect to MySQL with root and password: - -```shell -mysql -uroot -p -``` - -Or, if you're using [cfgov-refresh's](https://github.com/cfpb/cfgov-refresh) no-password local development setup, you can forgo the password step: - -```shell -mysql -uroot -``` - -Then create an owning-a-home database: -```shell -create database oah; -``` -If you would like to connect with a different user other than root, you can create a user, and replace `oah_user` with your desired username and `password` with your desired password: -```shell -create user 'oah_user'@'localhost' identified by 'password'; -grant all privileges on oah.* to 'oah_user'@'localhost'; -flush privileges; -exit -``` -You can now connect to MySQL with your newly created username and password and have access to `oah`: -```shell -mysql -u oah_user -p -# enter your password -show databases; -use oah; -exit -``` - -If you have access to mortgage data, you could load it like so: - -``` -mysql -uroot -p oah < [PATH TO YOUR .sql DUMP] -``` diff --git a/mkdocs.yml b/mkdocs.yml index c3484ee..37f1f93 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,7 +3,6 @@ pages: - Introduction: index.md - Usage: - API endpoints: api.md - - Using MySQL: mysql.md theme: mkDOCter extra: diff --git a/oahapi/urls.py b/oahapi/urls.py index ae8b224..10841f9 100755 --- a/oahapi/urls.py +++ b/oahapi/urls.py @@ -2,5 +2,5 @@ urlpatterns = [ url(r'^oah-api/rates/', include('ratechecker.urls')), - url(r'^oah-api/county/$', include('countylimits.urls')), + url(r'^oah-api/county/', include('countylimits.urls')), ] diff --git a/pytest.sh b/pytest.sh deleted file mode 100755 index 153c112..0000000 --- a/pytest.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -e - -# coverage erase -coverage run manage.py test "$@" -coverage report -m diff --git a/ratechecker/migrations/0001_initial.py b/ratechecker/migrations/0001_initial.py index 4c95396..767f73c 100644 --- a/ratechecker/migrations/0001_initial.py +++ b/ratechecker/migrations/0001_initial.py @@ -1,161 +1,113 @@ # -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models +from __future__ import unicode_literals +from django.db import migrations, models +import localflavor.us.models -class Migration(SchemaMigration): - def forwards(self, orm): - # Adding model 'Product' - db.create_table(u'ratechecker_product', ( - ('plan_id', self.gf('django.db.models.fields.IntegerField')(primary_key=True)), - ('institution', self.gf('django.db.models.fields.CharField')(max_length=16)), - ('loan_purpose', self.gf('django.db.models.fields.CharField')(max_length=12)), - ('pmt_type', self.gf('django.db.models.fields.CharField')(default='FIXED', max_length=12)), - ('loan_type', self.gf('django.db.models.fields.CharField')(max_length=12)), - ('loan_term', self.gf('django.db.models.fields.IntegerField')()), - ('int_adj_term', self.gf('django.db.models.fields.IntegerField')(null=True)), - ('adj_period', self.gf('django.db.models.fields.PositiveSmallIntegerField')(null=True)), - ('io', self.gf('django.db.models.fields.BooleanField')()), - ('arm_index', self.gf('django.db.models.fields.CharField')(max_length=96, null=True)), - ('int_adj_cap', self.gf('django.db.models.fields.IntegerField')(null=True)), - ('annual_cap', self.gf('django.db.models.fields.IntegerField')(null=True)), - ('loan_cap', self.gf('django.db.models.fields.IntegerField')(null=True)), - ('arm_margin', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=4)), - ('ai_value', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=4)), - ('min_ltv', self.gf('django.db.models.fields.FloatField')()), - ('max_ltv', self.gf('django.db.models.fields.FloatField')()), - ('min_fico', self.gf('django.db.models.fields.IntegerField')()), - ('max_fico', self.gf('django.db.models.fields.IntegerField')()), - ('min_loan_amt', self.gf('django.db.models.fields.DecimalField')(max_digits=12, decimal_places=2)), - ('max_loan_amt', self.gf('django.db.models.fields.DecimalField')(max_digits=12, decimal_places=2)), - ('single_family', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('condo', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('coop', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('data_timestamp', self.gf('django.db.models.fields.DateTimeField')()), - )) - db.send_create_signal(u'ratechecker', ['Product']) +class Migration(migrations.Migration): - # Adding model 'Adjustment' - db.create_table(u'ratechecker_adjustment', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('rule_id', self.gf('django.db.models.fields.IntegerField')()), - ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ratechecker.Product'])), - ('affect_rate_type', self.gf('django.db.models.fields.CharField')(max_length=1)), - ('adj_value', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=3)), - ('min_loan_amt', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=12, decimal_places=2)), - ('max_loan_amt', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=12, decimal_places=2)), - ('prop_type', self.gf('django.db.models.fields.CharField')(max_length=10, null=True)), - ('min_fico', self.gf('django.db.models.fields.IntegerField')(null=True)), - ('max_fico', self.gf('django.db.models.fields.IntegerField')(null=True)), - ('min_ltv', self.gf('django.db.models.fields.FloatField')(null=True)), - ('max_ltv', self.gf('django.db.models.fields.FloatField')(null=True)), - ('state', self.gf('localflavor.us.models.USStateField')(max_length=2, null=True)), - ('data_timestamp', self.gf('django.db.models.fields.DateTimeField')()), - )) - db.send_create_signal(u'ratechecker', ['Adjustment']) + dependencies = [ + ] - # Adding model 'Region' - db.create_table(u'ratechecker_region', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('region_id', self.gf('django.db.models.fields.IntegerField')(db_index=True)), - ('state_id', self.gf('localflavor.us.models.USStateField')(max_length=2)), - ('data_timestamp', self.gf('django.db.models.fields.DateTimeField')()), - )) - db.send_create_signal(u'ratechecker', ['Region']) - - # Adding model 'Rate' - db.create_table(u'ratechecker_rate', ( - ('rate_id', self.gf('django.db.models.fields.IntegerField')(primary_key=True)), - ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ratechecker.Product'])), - ('region_id', self.gf('django.db.models.fields.IntegerField')()), - ('lock', self.gf('django.db.models.fields.PositiveSmallIntegerField')()), - ('base_rate', self.gf('django.db.models.fields.DecimalField')(max_digits=6, decimal_places=3)), - ('total_points', self.gf('django.db.models.fields.DecimalField')(max_digits=6, decimal_places=3)), - ('data_timestamp', self.gf('django.db.models.fields.DateTimeField')()), - )) - db.send_create_signal(u'ratechecker', ['Rate']) - - - def backwards(self, orm): - # Deleting model 'Product' - db.delete_table(u'ratechecker_product') - - # Deleting model 'Adjustment' - db.delete_table(u'ratechecker_adjustment') - - # Deleting model 'Region' - db.delete_table(u'ratechecker_region') - - # Deleting model 'Rate' - db.delete_table(u'ratechecker_rate') - - - models = { - u'ratechecker.adjustment': { - 'Meta': {'object_name': 'Adjustment'}, - 'adj_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'affect_rate_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'max_fico': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'max_loan_amt': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2'}), - 'max_ltv': ('django.db.models.fields.FloatField', [], {'null': 'True'}), - 'min_fico': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'min_loan_amt': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2'}), - 'min_ltv': ('django.db.models.fields.FloatField', [], {'null': 'True'}), - 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'prop_type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), - 'rule_id': ('django.db.models.fields.IntegerField', [], {}), - 'state': ('localflavor.us.models.USStateField', [], {'max_length': '2', 'null': 'True'}) - }, - u'ratechecker.product': { - 'Meta': {'object_name': 'Product'}, - 'adj_period': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), - 'ai_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '4'}), - 'annual_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'arm_index': ('django.db.models.fields.CharField', [], {'max_length': '96', 'null': 'True'}), - 'arm_margin': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '4'}), - 'condo': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'coop': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'institution': ('django.db.models.fields.CharField', [], {'max_length': '16'}), - 'int_adj_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'int_adj_term': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'io': ('django.db.models.fields.BooleanField', [], {}), - 'loan_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'loan_purpose': ('django.db.models.fields.CharField', [], {'max_length': '12'}), - 'loan_term': ('django.db.models.fields.IntegerField', [], {}), - 'loan_type': ('django.db.models.fields.CharField', [], {'max_length': '12'}), - 'max_fico': ('django.db.models.fields.IntegerField', [], {}), - 'max_loan_amt': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'max_ltv': ('django.db.models.fields.FloatField', [], {}), - 'min_fico': ('django.db.models.fields.IntegerField', [], {}), - 'min_loan_amt': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'min_ltv': ('django.db.models.fields.FloatField', [], {}), - 'plan_id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}), - 'pmt_type': ('django.db.models.fields.CharField', [], {'default': "'FIXED'", 'max_length': '12'}), - 'single_family': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) - }, - u'ratechecker.rate': { - 'Meta': {'object_name': 'Rate'}, - 'base_rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'lock': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), - 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'rate_id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}), - 'region_id': ('django.db.models.fields.IntegerField', [], {}), - 'total_points': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}) - }, - u'ratechecker.region': { - 'Meta': {'object_name': 'Region'}, - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'region_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'state_id': ('localflavor.us.models.USStateField', [], {'max_length': '2'}) - } - } - - complete_apps = ['ratechecker'] \ No newline at end of file + operations = [ + migrations.CreateModel( + name='Adjustment', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('rule_id', models.IntegerField()), + ('affect_rate_type', models.CharField(max_length=1, choices=[(b'P', b'Points'), (b'R', b'Rate')])), + ('adj_value', models.DecimalField(null=True, max_digits=6, decimal_places=3)), + ('min_loan_amt', models.DecimalField(null=True, max_digits=12, decimal_places=2)), + ('max_loan_amt', models.DecimalField(null=True, max_digits=12, decimal_places=2)), + ('prop_type', models.CharField(max_length=18, null=True, choices=[(b'CONDO', b'Condo'), (b'COOP', b'Co-op'), (b'CASHOUT-REFI', b'Cash-out refinance')])), + ('min_fico', models.IntegerField(null=True)), + ('max_fico', models.IntegerField(null=True)), + ('min_ltv', models.DecimalField(null=True, max_digits=6, decimal_places=3)), + ('max_ltv', models.DecimalField(null=True, max_digits=6, decimal_places=3)), + ('state', localflavor.us.models.USStateField(max_length=2, null=True)), + ('data_timestamp', models.DateTimeField()), + ], + ), + migrations.CreateModel( + name='Fee', + fields=[ + ('fee_id', models.AutoField(serialize=False, primary_key=True)), + ('product_id', models.IntegerField()), + ('state_id', localflavor.us.models.USStateField(max_length=2)), + ('lender', models.CharField(max_length=16)), + ('single_family', models.BooleanField(default=True)), + ('condo', models.BooleanField(default=False)), + ('coop', models.BooleanField(default=False)), + ('origination_dollar', models.DecimalField(max_digits=8, decimal_places=2)), + ('origination_percent', models.DecimalField(max_digits=6, decimal_places=3)), + ('third_party', models.DecimalField(max_digits=8, decimal_places=2)), + ('data_timestamp', models.DateTimeField()), + ], + ), + migrations.CreateModel( + name='Product', + fields=[ + ('plan_id', models.IntegerField(serialize=False, primary_key=True)), + ('institution', models.CharField(max_length=16)), + ('loan_purpose', models.CharField(max_length=12, choices=[(b'REFI', b'Refinance'), (b'PURCH', b'Purchase')])), + ('pmt_type', models.CharField(default=b'FIXED', max_length=12, choices=[(b'FIXED', b'Fixed Rate Mortgage'), (b'ARM', b'Adjustable Rate Mortgage')])), + ('loan_type', models.CharField(max_length=12, choices=[(b'JUMBO', b'Jumbo Mortgage'), (b'CONF', b'Conforming Loan'), (b'AGENCY', b'Agency Loan'), (b'FHA', b'Federal Housing Administration Loan'), (b'VA', b'Veterans Affairs Loan'), (b'VA-HB', b'VA-HB Loan'), (b'FHA-HB', b'FHA-HB Loan')])), + ('loan_term', models.IntegerField()), + ('int_adj_term', models.IntegerField(help_text=b'1st part of the ARM definition. E.g. 5 in 5/1 ARM', null=True)), + ('adj_period', models.PositiveSmallIntegerField(null=True)), + ('io', models.BooleanField()), + ('arm_index', models.CharField(max_length=96, null=True)), + ('int_adj_cap', models.IntegerField(help_text=b'Max percentage points the rate can adjust at first adjustment.', null=True)), + ('annual_cap', models.IntegerField(help_text=b'Max percentage points adjustable at each subsequent adjustment', null=True)), + ('loan_cap', models.IntegerField(help_text=b'Total lifetime maximum change that the ARM rate can have.', null=True)), + ('arm_margin', models.DecimalField(null=True, max_digits=6, decimal_places=4)), + ('ai_value', models.DecimalField(null=True, max_digits=6, decimal_places=4)), + ('min_ltv', models.DecimalField(null=True, max_digits=6, decimal_places=3)), + ('max_ltv', models.DecimalField(null=True, max_digits=6, decimal_places=3)), + ('min_fico', models.IntegerField()), + ('max_fico', models.IntegerField()), + ('min_loan_amt', models.DecimalField(max_digits=12, decimal_places=2)), + ('max_loan_amt', models.DecimalField(max_digits=12, decimal_places=2)), + ('single_family', models.BooleanField(default=True)), + ('condo', models.BooleanField(default=False)), + ('coop', models.BooleanField(default=False)), + ('data_timestamp', models.DateTimeField()), + ], + ), + migrations.CreateModel( + name='Rate', + fields=[ + ('rate_id', models.IntegerField(serialize=False, primary_key=True)), + ('region_id', models.IntegerField()), + ('lock', models.PositiveSmallIntegerField()), + ('base_rate', models.DecimalField(max_digits=6, decimal_places=3)), + ('total_points', models.DecimalField(max_digits=6, decimal_places=3)), + ('data_timestamp', models.DateTimeField()), + ('product', models.ForeignKey(to='ratechecker.Product')), + ], + ), + migrations.CreateModel( + name='Region', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('region_id', models.IntegerField(db_index=True)), + ('state_id', localflavor.us.models.USStateField(max_length=2)), + ('data_timestamp', models.DateTimeField()), + ], + ), + migrations.AddField( + model_name='fee', + name='plan', + field=models.ForeignKey(to='ratechecker.Product'), + ), + migrations.AddField( + model_name='adjustment', + name='product', + field=models.ForeignKey(to='ratechecker.Product'), + ), + migrations.AlterUniqueTogether( + name='fee', + unique_together=set([('product_id', 'state_id', 'lender', 'single_family', 'condo', 'coop')]), + ), + ] diff --git a/ratechecker/migrations/0002_auto__chg_field_adjustment_min_ltv__chg_field_adjustment_max_ltv__chg_.py b/ratechecker/migrations/0002_auto__chg_field_adjustment_min_ltv__chg_field_adjustment_max_ltv__chg_.py deleted file mode 100644 index 02b970b..0000000 --- a/ratechecker/migrations/0002_auto__chg_field_adjustment_min_ltv__chg_field_adjustment_max_ltv__chg_.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Changing field 'Adjustment.min_ltv' - db.alter_column(u'ratechecker_adjustment', 'min_ltv', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=3)) - - # Changing field 'Adjustment.max_ltv' - db.alter_column(u'ratechecker_adjustment', 'max_ltv', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=3)) - - # Changing field 'Product.max_ltv' - db.alter_column(u'ratechecker_product', 'max_ltv', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=3)) - - # Changing field 'Product.min_ltv' - db.alter_column(u'ratechecker_product', 'min_ltv', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=3)) - - def backwards(self, orm): - - # Changing field 'Adjustment.min_ltv' - db.alter_column(u'ratechecker_adjustment', 'min_ltv', self.gf('django.db.models.fields.FloatField')(null=True)) - - # Changing field 'Adjustment.max_ltv' - db.alter_column(u'ratechecker_adjustment', 'max_ltv', self.gf('django.db.models.fields.FloatField')(null=True)) - - # Changing field 'Product.max_ltv' - db.alter_column(u'ratechecker_product', 'max_ltv', self.gf('django.db.models.fields.FloatField')(default=None)) - - # Changing field 'Product.min_ltv' - db.alter_column(u'ratechecker_product', 'min_ltv', self.gf('django.db.models.fields.FloatField')(default=None)) - - models = { - u'ratechecker.adjustment': { - 'Meta': {'object_name': 'Adjustment'}, - 'adj_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'affect_rate_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'max_fico': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'max_loan_amt': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2'}), - 'max_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'min_fico': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'min_loan_amt': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2'}), - 'min_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'prop_type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), - 'rule_id': ('django.db.models.fields.IntegerField', [], {}), - 'state': ('localflavor.us.models.USStateField', [], {'max_length': '2', 'null': 'True'}) - }, - u'ratechecker.product': { - 'Meta': {'object_name': 'Product'}, - 'adj_period': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), - 'ai_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '4'}), - 'annual_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'arm_index': ('django.db.models.fields.CharField', [], {'max_length': '96', 'null': 'True'}), - 'arm_margin': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '4'}), - 'condo': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'coop': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'institution': ('django.db.models.fields.CharField', [], {'max_length': '16'}), - 'int_adj_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'int_adj_term': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'io': ('django.db.models.fields.BooleanField', [], {}), - 'loan_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'loan_purpose': ('django.db.models.fields.CharField', [], {'max_length': '12'}), - 'loan_term': ('django.db.models.fields.IntegerField', [], {}), - 'loan_type': ('django.db.models.fields.CharField', [], {'max_length': '12'}), - 'max_fico': ('django.db.models.fields.IntegerField', [], {}), - 'max_loan_amt': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'max_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'min_fico': ('django.db.models.fields.IntegerField', [], {}), - 'min_loan_amt': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'min_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'plan_id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}), - 'pmt_type': ('django.db.models.fields.CharField', [], {'default': "'FIXED'", 'max_length': '12'}), - 'single_family': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) - }, - u'ratechecker.rate': { - 'Meta': {'object_name': 'Rate'}, - 'base_rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'lock': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), - 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'rate_id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}), - 'region_id': ('django.db.models.fields.IntegerField', [], {}), - 'total_points': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}) - }, - u'ratechecker.region': { - 'Meta': {'object_name': 'Region'}, - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'region_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'state_id': ('localflavor.us.models.USStateField', [], {'max_length': '2'}) - } - } - - complete_apps = ['ratechecker'] \ No newline at end of file diff --git a/ratechecker/migrations/0003_auto__add_fee__add_unique_fee_product_id_state_id_lender_single_family.py b/ratechecker/migrations/0003_auto__add_fee__add_unique_fee_product_id_state_id_lender_single_family.py deleted file mode 100644 index 6da3e76..0000000 --- a/ratechecker/migrations/0003_auto__add_fee__add_unique_fee_product_id_state_id_lender_single_family.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Fee' - db.create_table(u'ratechecker_fee', ( - ('fee_id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('plan', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ratechecker.Product'])), - ('product_id', self.gf('django.db.models.fields.IntegerField')()), - ('state_id', self.gf('localflavor.us.models.USStateField')(max_length=2)), - ('lender', self.gf('django.db.models.fields.CharField')(max_length=16)), - ('single_family', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('condo', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('coop', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('origination_dollar', self.gf('django.db.models.fields.DecimalField')(max_digits=8, decimal_places=2)), - ('origination_percent', self.gf('django.db.models.fields.DecimalField')(max_digits=6, decimal_places=3)), - ('third_party', self.gf('django.db.models.fields.DecimalField')(max_digits=8, decimal_places=2)), - ('data_timestamp', self.gf('django.db.models.fields.DateTimeField')()), - )) - db.send_create_signal(u'ratechecker', ['Fee']) - - # Adding unique constraint on 'Fee', fields ['product_id', 'state_id', 'lender', 'single_family', 'condo', 'coop'] - db.create_unique(u'ratechecker_fee', ['product_id', 'state_id', 'lender', 'single_family', 'condo', 'coop']) - - - def backwards(self, orm): - # Removing unique constraint on 'Fee', fields ['product_id', 'state_id', 'lender', 'single_family', 'condo', 'coop'] - db.delete_unique(u'ratechecker_fee', ['product_id', 'state_id', 'lender', 'single_family', 'condo', 'coop']) - - # Deleting model 'Fee' - db.delete_table(u'ratechecker_fee') - - - models = { - u'ratechecker.adjustment': { - 'Meta': {'object_name': 'Adjustment'}, - 'adj_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'affect_rate_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'max_fico': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'max_loan_amt': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2'}), - 'max_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'min_fico': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'min_loan_amt': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2'}), - 'min_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'prop_type': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), - 'rule_id': ('django.db.models.fields.IntegerField', [], {}), - 'state': ('localflavor.us.models.USStateField', [], {'max_length': '2', 'null': 'True'}) - }, - u'ratechecker.fee': { - 'Meta': {'unique_together': "(('product_id', 'state_id', 'lender', 'single_family', 'condo', 'coop'),)", 'object_name': 'Fee'}, - 'condo': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'coop': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'fee_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'lender': ('django.db.models.fields.CharField', [], {'max_length': '16'}), - 'origination_dollar': ('django.db.models.fields.DecimalField', [], {'max_digits': '8', 'decimal_places': '2'}), - 'origination_percent': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}), - 'plan': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'product_id': ('django.db.models.fields.IntegerField', [], {}), - 'single_family': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'state_id': ('localflavor.us.models.USStateField', [], {'max_length': '2'}), - 'third_party': ('django.db.models.fields.DecimalField', [], {'max_digits': '8', 'decimal_places': '2'}) - }, - u'ratechecker.product': { - 'Meta': {'object_name': 'Product'}, - 'adj_period': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), - 'ai_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '4'}), - 'annual_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'arm_index': ('django.db.models.fields.CharField', [], {'max_length': '96', 'null': 'True'}), - 'arm_margin': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '4'}), - 'condo': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'coop': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'institution': ('django.db.models.fields.CharField', [], {'max_length': '16'}), - 'int_adj_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'int_adj_term': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'io': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'loan_cap': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), - 'loan_purpose': ('django.db.models.fields.CharField', [], {'max_length': '12'}), - 'loan_term': ('django.db.models.fields.IntegerField', [], {}), - 'loan_type': ('django.db.models.fields.CharField', [], {'max_length': '12'}), - 'max_fico': ('django.db.models.fields.IntegerField', [], {}), - 'max_loan_amt': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'max_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'min_fico': ('django.db.models.fields.IntegerField', [], {}), - 'min_loan_amt': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '2'}), - 'min_ltv': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '3'}), - 'plan_id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}), - 'pmt_type': ('django.db.models.fields.CharField', [], {'default': "'FIXED'", 'max_length': '12'}), - 'single_family': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) - }, - u'ratechecker.rate': { - 'Meta': {'object_name': 'Rate'}, - 'base_rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}), - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - 'lock': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), - 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ratechecker.Product']"}), - 'rate_id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}), - 'region_id': ('django.db.models.fields.IntegerField', [], {}), - 'total_points': ('django.db.models.fields.DecimalField', [], {'max_digits': '6', 'decimal_places': '3'}) - }, - u'ratechecker.region': { - 'Meta': {'object_name': 'Region'}, - 'data_timestamp': ('django.db.models.fields.DateTimeField', [], {}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'region_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'state_id': ('localflavor.us.models.USStateField', [], {'max_length': '2'}) - } - } - - complete_apps = ['ratechecker'] diff --git a/ratechecker/views.py b/ratechecker/views.py index eb607ab..0f91717 100644 --- a/ratechecker/views.py +++ b/ratechecker/views.py @@ -165,7 +165,7 @@ def rate_checker(request): # transform them to upper cases fixed_data = dict(map( lambda (k, v): (k, v.strip().upper()), - request.QUERY_PARAMS.iteritems())) + request.query_params.iteritems())) fixed_data = set_lock_max_min(fixed_data) serializer = ParamsSerializer(data=fixed_data) @@ -190,7 +190,7 @@ def rate_checker_fees(request): # transform them to upper cases fixed_data = dict(map( lambda (k, v): (k, v.strip().upper()), - request.QUERY_PARAMS.iteritems())) + request.query_params.iteritems())) serializer = ParamsSerializer(data=fixed_data) if serializer.is_valid(): diff --git a/requirements/base.txt b/requirements/base.txt deleted file mode 100644 index 6900f7f..0000000 --- a/requirements/base.txt +++ /dev/null @@ -1,9 +0,0 @@ -beautifulsoup4==4.5.3 -Django==1.8.15 -django-cors-headers -dj-database-url==0.4.2 -django-localflavor -djangorestframework==3.1.3 -lxml==3.7.2 -requests==2.12.4 -unicodecsv==0.14.1 diff --git a/requirements/mysql.txt b/requirements/mysql.txt deleted file mode 100644 index ea5a41a..0000000 --- a/requirements/mysql.txt +++ /dev/null @@ -1,3 +0,0 @@ --r base.txt - -MySQL-python diff --git a/requirements/test.txt b/requirements/test.txt deleted file mode 100644 index c48fe27..0000000 --- a/requirements/test.txt +++ /dev/null @@ -1,5 +0,0 @@ --r base.txt - -coverage==4.2 -mock==2.0.0 -model_mommy==1.2.6 diff --git a/requirements/docs.txt b/requirements_docs.txt similarity index 100% rename from requirements/docs.txt rename to requirements_docs.txt diff --git a/settings_for_testing.py b/settings_for_testing.py index 2482a5d..53c2336 100644 --- a/settings_for_testing.py +++ b/settings_for_testing.py @@ -39,7 +39,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'oah', + 'NAME': 'oah.sqlite3', } } @@ -53,3 +53,9 @@ USE_L10N = True USE_TZ = True STATIC_URL = '/static/' +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + } +] diff --git a/setup.py b/setup.py index 3c5ccd1..b2af385 100755 --- a/setup.py +++ b/setup.py @@ -12,6 +12,24 @@ def read_file(filename): return '' +install_requires = [ + 'beautifulsoup4==4.5.3', + 'Django>=1.8,<1.12', + 'django-cors-headers', + 'dj-database-url==0.4.2', + 'django-localflavor', + 'djangorestframework==3.6.4', # Latest version that supports both Django 1.8 and 1.11 + 'requests>2.18,<2.20', + 'unicodecsv==0.14.1', +] + +testing_extras = [ + 'coverage==4.2', + 'mock==2.0.0', + 'model_mommy==1.2.6', +] + + setup( name='owning-a-home-api', version_format='{tag}.dev{commitcount}+{gitsha}', @@ -41,4 +59,8 @@ def read_file(filename): setup_requires=['setuptools-git-version==1.0.3'], long_description=read_file('README.md'), zip_safe=False, + install_requires=install_requires, + extras_require={ + 'testing': testing_extras, + } ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f46782f --- /dev/null +++ b/tox.ini @@ -0,0 +1,14 @@ +[tox] +skipsdist=True +envlist=dj{18,111} + +[testenv] +install_command=pip install -e ".[testing]" -U {opts} {packages} +commands= + coverage erase + coverage run manage.py test {posargs} + coverage report --skip-covered + +deps= + dj18: Django>=1.8,<1.9 + dj111: Django>=1.11,<1.12