hackoregon / elections-2018-backend

MIT License
1 stars 1 forks source link

[WIP] fix routing to comply with AWS infrastructure #5

Closed MikeTheCanuck closed 6 years ago

MikeTheCanuck commented 6 years ago

This will address #3.

MikeTheCanuck commented 6 years ago

Incomplete so far - next we need to connect the /local-elections/ base route to the endpoints in the /api/ folder.

MikeTheCanuck commented 6 years ago

Well, I'm at the limits of my current understanding of Django. Anyone out there know the right way to get responses on each of the sub-routes (endpoints) that are specific and exclusive (not duplicative)?

What works now

Given the current state of the urls.py files in this PR, I can browse the root of the project (http://0.0.0.0:8000/local-elections/) and it shows both a beautiful Swaggerized page and a listing of all the proposed routes.

What's not working as expected

Then I added in additional entries to the urlpatterns array, one for each of the endpoints, such as this: url(r'^local-elections/donor/', include('api.urls')),

However, when I browse to any of those sub-routes (e.g. /local-elections/donor/), I get back a 404 Not Found response with the following output:

Using the URLconf defined in elections_api.urls, Django tried these URL patterns, in this order:

^ ^ ^transactions/$ [name='transactions-list']
^ ^ ^transactions\.(?P<format>[a-z0-9]+)/?$ [name='transactions-list']
^ ^ ^transactions/(?P<pk>[^/.]+)/$ [name='transactions-detail']
^ ^ ^transactions/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='transactions-detail']
^ ^ ^transactiondetail/$ [name='transactiondetails-list']
^ ^ ^transactiondetail\.(?P<format>[a-z0-9]+)/?$ [name='transactiondetails-list']
^ ^ ^transactiondetail/(?P<pk>[^/.]+)/$ [name='transactiondetails-detail']
^ ^ ^transactiondetail/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='transactiondetails-detail']
^ ^ ^statementoforg/$ [name='statementoforg-list']
^ ^ ^statementoforg\.(?P<format>[a-z0-9]+)/?$ [name='statementoforg-list']
^ ^ ^statementoforg/(?P<pk>[^/.]+)/$ [name='statementoforg-detail']
^ ^ ^statementoforg/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='statementoforg-detail']
^ ^ ^payee/$ [name='payee-list']
^ ^ ^payee\.(?P<format>[a-z0-9]+)/?$ [name='payee-list']
^ ^ ^payee/(?P<pk>[^/.]+)/$ [name='payee-detail']
^ ^ ^payee/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='payee-detail']
^ ^ ^electionactivity/$ [name='electionactivity-list']
^ ^ ^electionactivity\.(?P<format>[a-z0-9]+)/?$ [name='electionactivity-list']
^ ^ ^electionactivity/(?P<pk>[^/.]+)/$ [name='electionactivity-detail']
^ ^ ^electionactivity/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='electionactivity-detail']
^ ^ ^donor/$ [name='donor-list']
^ ^ ^donor\.(?P<format>[a-z0-9]+)/?$ [name='donor-list']
^ ^ ^donor/(?P<pk>[^/.]+)/$ [name='donor-detail']
^ ^ ^donor/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='donor-detail']
^ ^ ^committeeslist/$ [name='committeeslist-list']
^ ^ ^committeeslist\.(?P<format>[a-z0-9]+)/?$ [name='committeeslist-list']
^ ^ ^committeeslist/(?P<pk>[^/.]+)/$ [name='committeeslist-detail']
^ ^ ^committeeslist/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='committeeslist-detail']
^ ^ ^committeehistory/$ [name='committeehistory-list']
^ ^ ^committeehistory\.(?P<format>[a-z0-9]+)/?$ [name='committeehistory-list']
^ ^ ^committeehistory/(?P<pk>[^/.]+)/$ [name='committeehistory-detail']
^ ^ ^committeehistory/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='committeehistory-detail']
^ ^ ^ballots/$ [name='ballots-list']
^ ^ ^ballots\.(?P<format>[a-z0-9]+)/?$ [name='ballots-list']
^ ^ ^ballots/(?P<pk>[^/.]+)/$ [name='ballots-detail']
^ ^ ^ballots/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='ballots-detail']
^ ^ ^$ [name='api-root']
^ ^ ^\.(?P<format>[a-z0-9]+)/?$ [name='api-root']
^local-elections/$
^local-elections/ballots/
The current path, local-elections/donor/, didn't match any of these.

Adding to /elections_api/urls.py

When I add all of the following to the urlpatterns array:

    url(r'^local-elections/ballots/', include('api.urls')),
    url(r'^local-elections/transactions/', include('api.urls')),
    url(r'^local-elections/transactiondetail/', include('api.urls')),
    url(r'^local-elections/statementoforg/', include('api.urls')),
    url(r'^local-elections/payee/', include('api.urls')),
    url(r'^local-elections/electionactivity/', include('api.urls')),
    url(r'^local-elections/donor/', include('api.urls')),
    url(r'^local-elections/committeeslist/', include('api.urls')),
    url(r'^local-elections/committeehistory/', include('api.urls')),

...each of the sub-routes (endpoints) responds e.g. now I get a response from http://0.0.0.0:8000/local-elections/ballots/.

However, the response lists all of the objects for the entire Django app, not a listing specific to the sub-route I just requested:

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "transactions": "http://0.0.0.0:8000/local-elections/ballots/transactions/",
    "transactiondetail": "http://0.0.0.0:8000/local-elections/ballots/transactiondetail/",
    "statementoforg": "http://0.0.0.0:8000/local-elections/ballots/statementoforg/",
    "payee": "http://0.0.0.0:8000/local-elections/ballots/payee/",
    "electionactivity": "http://0.0.0.0:8000/local-elections/ballots/electionactivity/",
    "donor": "http://0.0.0.0:8000/local-elections/ballots/donor/",
    "committeeslist": "http://0.0.0.0:8000/local-elections/ballots/committeeslist/",
    "committeehistory": "http://0.0.0.0:8000/local-elections/ballots/committeehistory/",
    "ballots": "http://0.0.0.0:8000/local-elections/ballots/ballots/"
}

(The problem is, I see the exact same duplicative response to http://0.0.0.0:8000/local-elections/donor/)

Compare this to http://service.civicpdx.org/transportation-systems/biketown/ which only lists:

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "BiketownTrips": "http://service.civicpdx.org/transportation-systems/biketown/BiketownTrips/"
}

vs the response to http://service.civicpdx.org/transportation-systems/trimet-gis/:

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "TmBoundary": "http://service.civicpdx.org/transportation-systems/trimet-gis/TmBoundary/",
    "TmParkride": "http://service.civicpdx.org/transportation-systems/trimet-gis/TmParkride/"
}
grantholly commented 6 years ago

I've got the whole api root moved over to local-elections. Here's how it looks now.

{
  "transactions": "http://127.0.0.1:8000/local-elections/transactions/",
  "transactiondetail": "http://127.0.0.1:8000/local-elections/transactiondetail/",
  "statementoforg": "http://127.0.0.1:8000/local-elections/statementoforg/",
  "payee": "http://127.0.0.1:8000/local-elections/payee/",
  "electionactivity": "http://127.0.0.1:8000/local-elections/electionactivity/",
  "donor": "http://127.0.0.1:8000/local-elections/donor/",
  "committeeslist": "http://127.0.0.1:8000/local-elections/committeeslist/",
  "committeehistory": "http://127.0.0.1:8000/local-elections/committeehistory/",
  "ballots": "http://127.0.0.1:8000/local-elections/ballots/"
}

The Travis build is currently failing due to what looks like an SQL permissions error. At the end of running tests, Django wants to "flush", really this means wipe out, the test database. However it cannot.

E               django.core.management.base.CommandError: Database local-elections-finance couldn't be flushed. Possible reasons:
E                 * The database isn't running or isn't configured correctly.
E                 * At least one of the expected database tables doesn't exist.
E                 * The SQL was invalid.
E               Hint: Look at the output of 'django-admin sqlflush'. That's the SQL this command wasn't able to run.

And here's the preceding error output pointing to SQL permissions.

self = <django.db.backends.utils.CursorWrapper object at 0x7f45cf6bb2e8>
sql = 'TRUNCATE "auth_group", "auth_group_permissions", "auth_user", "auth_user_groups", "auth_permission", "django_session", "django_content_type", "django_admin_log", "auth_user_user_permissions";'
params = None
ignored_wrapper_args = (False, {'connection': <django.db.backends.postgresql.base.DatabaseWrapper object at 0x7f45d24e3080>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7f45cf6bb2e8>})
    def _execute(self, sql, params, *ignored_wrapper_args):
        self.db.validate_no_broken_transaction()
        with self.db.wrap_database_errors:
            if params is None:
>               return self.cursor.execute(sql)
E               psycopg2.ProgrammingError: permission denied for relation auth_group