Skip to content

Commit

Permalink
Merge pull request #219 from fecgov/release/sprint-13
Browse files Browse the repository at this point in the history
Release/sprint 13
  • Loading branch information
mjtravers authored Sep 14, 2022
2 parents fc18265 + 788a0d5 commit 728ba87
Show file tree
Hide file tree
Showing 27 changed files with 504 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
- run:
name: Create/run migrations
command: |
python manage.py migrate
python manage.py migrate --no-input --traceback --verbosity 3
working_directory: ~/project/django-backend/

# Only use SonarCloud security checking for now.
Expand Down
6 changes: 3 additions & 3 deletions .safety.dependency.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
44717 2022-03-01 # numpy - dependencies not caught up yet
44716 2022-03-01 # numpy - dependencies not caught up yet
43975 2022-03-01 # urllib3 - botocore dependency needs to catch up
48040 2022-09-01 # django
48041 2022-09-01 # django
48542 2022-09-01 # pyjwt
48040 2022-10-01 # django
48041 2022-10-01 # django
48542 2022-10-01 # pyjwt
9 changes: 4 additions & 5 deletions bin/run-api.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
cd django-backend

# Run migrations
./manage.py migrate --noinput > migrate.out

# Run application
python wait_for_db.py && gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9 -t 200
# Run migrations and application
./manage.py migrate --no-input --traceback --verbosity 3 > migrate.out &&
python wait_for_db.py &&
gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9 -t 200
Empty file.
36 changes: 36 additions & 0 deletions django-backend/fecfiler/authentication/test_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import unittest
from unittest.mock import Mock


from django.test import RequestFactory

from fecfiler.authentication.token import (login_dot_gov_logout,
generate_username)


class TestToken(unittest.TestCase):

def setUp(self):
self.factory = RequestFactory()

def test_login_dot_gov_logout_happy_path(self):
test_id_token_hint = 'test_id_token_hint'
test_state = 'test_state'

mock_request = Mock()
mock_request.session = Mock()
mock_request.session.get.return_value = test_id_token_hint
mock_request.get_signed_cookie.return_value = test_state

retval = login_dot_gov_logout(mock_request)
self.maxDiff = None
self.assertEqual(retval, ('https://idp.int.identitysandbox.gov'
'/openid_connect/logout?'
'id_token_hint=test_id_token_hint'
'&post_logout_redirect_uri=None'
'&state=test_state'))

def test_generate_username(self):
test_uuid = 'test_uuid'
retval = generate_username(test_uuid)
self.assertEqual(test_uuid, retval)
20 changes: 19 additions & 1 deletion django-backend/fecfiler/authentication/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@
from fecfiler.settings import SECRET_KEY
import jwt
from rest_framework_jwt.compat import get_username_field, get_username
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.settings import api_settings, settings
import logging
from urllib.parse import urlencode

logger = logging.getLogger(__name__)


def login_dot_gov_logout(request):
id_token_hint = request.session.get("oidc_id_token")
post_logout_redirect_uri = settings.LOGOUT_REDIRECT_URL
state = request.get_signed_cookie('oidc_state')

params = {
'id_token_hint': id_token_hint,
'post_logout_redirect_uri': post_logout_redirect_uri,
'state': state,
}
query = urlencode(params)
op_logout_url = settings.OIDC_OP_LOGOUT_ENDPOINT
redirect_url = '{url}?{query}'.format(url=op_logout_url, query=query)

return redirect_url


def generate_username(uuid):
return uuid

Expand Down
6 changes: 4 additions & 2 deletions django-backend/fecfiler/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.views.generic import View
from django.http import HttpResponse, HttpResponseRedirect
from django.http import HttpResponseRedirect

from fecfiler.settings import (
LOGIN_REDIRECT_CLIENT_URL,
Expand Down Expand Up @@ -65,9 +65,11 @@ def get(self, request, *args, **kwargs):

class LoginDotGovSuccessLogoutSpaRedirect(View):
def get(self, request, *args, **kwargs):
response = HttpResponse(status=204) # no content
response = HttpResponseRedirect(LOGIN_REDIRECT_CLIENT_URL)
response.delete_cookie(FFAPI_COMMITTEE_ID_COOKIE_NAME,
domain=FFAPI_COOKIE_DOMAIN)
response.delete_cookie(FFAPI_EMAIL_COOKIE_NAME,
domain=FFAPI_COOKIE_DOMAIN)
response.delete_cookie('csrftoken',
domain=FFAPI_COOKIE_DOMAIN)
return response
3 changes: 2 additions & 1 deletion django-backend/fecfiler/contacts/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def setUp(self):
"state": "St",
"zip": "123456789",
"country": "Country",
"telephone": "+1 1234567890",
}

self.invalid_contact = {
Expand All @@ -26,6 +27,7 @@ def setUp(self):
"first_name": "First",
"street_1": "Street",
"city": "City",
"country": "USA",
}

self.mock_request = Request(HttpRequest())
Expand All @@ -46,7 +48,6 @@ def test_serializer_validate(self):
self.assertFalse(invalid_serializer.is_valid())
self.assertIsNotNone(invalid_serializer.errors["state"])
self.assertIsNotNone(invalid_serializer.errors["zip"])
self.assertIsNotNone(invalid_serializer.errors["country"])

def test_read_only_fields(self):
update = self.valid_contact.copy()
Expand Down
51 changes: 51 additions & 0 deletions django-backend/fecfiler/contacts/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from django.test import TestCase, RequestFactory
from .views import ContactViewSet
from ..authentication.models import Account
from unittest import mock


def mocked_requests_get(*args, **kwargs):
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code

def json(self):
return self.json_data

return MockResponse(
{
"results": [
{"name": "BIDEN FOR PRESIDENT", "id": "C00703975", "is_active": "true"},
{"name": "BIDEN VICTORY FUND", "id": "C00744946", "is_active": "true"},
]
},
200,
)


class ContactViewSetTest(TestCase):
fixtures = ["test_contacts", "test_committee_accounts", "test_accounts"]

def setUp(self):
self.user = Account.objects.get(cmtee_id="C12345678")
self.factory = RequestFactory()

@mock.patch("requests.get", side_effect=mocked_requests_get)
def test_committee_lookup_happy_path(self, mock_get):
self.assertEqual(True, True)
request = self.factory.get("/api/v1/contacts/committee_lookup")
request.user = self.user

response = ContactViewSet.as_view({"get": "committee_lookup"})(request)

expected_json = {
"fec_api_committees": [
{"name": "BIDEN FOR PRESIDENT", "id": "C00703975", "is_active": "true"},
{"name": "BIDEN VICTORY FUND", "id": "C00744946", "is_active": "true"},
],
"fecfile_committees": [],
}

self.assertEqual(response.status_code, 200)
self.assertJSONEqual(str(response.content, encoding="utf8"), expected_json)
52 changes: 52 additions & 0 deletions django-backend/fecfiler/contacts/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from django.http import HttpResponseBadRequest, JsonResponse
from django.db.models import Q, F
from rest_framework.decorators import action
from fecfiler.settings import FEC_API_KEY, FEC_API_COMMITTEE_LOOKUP_ENDPOINT
from urllib.parse import urlencode
from fecfiler.committee_accounts.views import CommitteeOwnedViewSet
from .models import Contact
from .serializers import ContactSerializer
import requests
import logging

logger = logging.getLogger(__name__)
Expand All @@ -20,3 +26,49 @@ class ContactViewSet(CommitteeOwnedViewSet):
in CommitteeOwnedViewSet's implementation of get_queryset()
"""
queryset = Contact.objects.all().order_by("-id")

@action(detail=False)
def committee_lookup(self, request):
q = request.GET.get("q", "")
if q is None:
return HttpResponseBadRequest()

max_fec_results = 10
max_fec_results_param = request.GET.get("max_fec_results", "")
if max_fec_results_param is not None and max_fec_results_param.isnumeric():
max_fec_results = int(max_fec_results_param)

max_fecfile_results = 10
max_fecfile_results_param = request.GET.get("max_fecfile_results", "")
if (
max_fecfile_results_param is not None
and max_fecfile_results_param.isnumeric()
):
max_fecfile_results = int(max_fecfile_results_param)

query_params = urlencode(
{
"q": q,
"api_key": FEC_API_KEY,
}
)
url = "{url}?{query_params}".format(
url=FEC_API_COMMITTEE_LOOKUP_ENDPOINT, query_params=query_params
)
json_results = requests.get(url).json()

fec_api_committees = json_results.get("results", [])[:max_fec_results]
fecfile_committees = list(
self.get_queryset()
.filter(Q(committee_id__icontains=q) | Q(name__icontains=q))
.order_by("-committee_id")
.annotate(result_id=F("committee_id"))
.values("name", "result_id")
.annotate(id=F("result_id"))[:max_fecfile_results]
)
return_value = {
"fec_api_committees": fec_api_committees,
"fecfile_committees": fecfile_committees,
}

return JsonResponse(return_value)
11 changes: 11 additions & 0 deletions django-backend/fecfiler/f3x_summaries/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Django App for F3X Reports
Provides F3X Summary record
:py:class:`fecfiler.f3x_summaries.models.F3XSummary`
Also provides abstract model and viewset for
apps that need report-related records
:py:class:`fecfiler.committee_accounts.models.ReportMixin`
:py:class:`fecfiler.committee_accounts.views.ReportViewMixin`
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"comment": "SA15 transaction to count",
"model": "scha_transactions.schatransaction",
"fields": {
"id": 9999,
"committee_account_id": 1000,
"report_id":9999,
"form_type": "SA15",
"contribution_amount":1234.56,
"memo_code": false,
"created": "2022-02-09T00:00:00.000Z",
"updated": "2022-02-09T00:00:00.000Z",
"transaction_type_identifier":"OFFSET_TO_OPEX"
}
},
{
"comment": "SA15 transaction to count",
"model": "scha_transactions.schatransaction",
"fields": {
"id": 10000,
"committee_account_id": 1000,
"report_id":9999,
"form_type": "SA15",
"contribution_amount":891.23,
"memo_code": false,
"created": "2022-02-09T00:00:00.000Z",
"updated": "2022-02-09T00:00:00.000Z",
"transaction_type_identifier":"OFFSET_TO_OPEX"
}
},
{
"comment": "SA15 transaction to NOT count",
"model": "scha_transactions.schatransaction",
"fields": {
"id": 10001,
"committee_account_id": 1000,
"report_id":9999,
"form_type": "SA15",
"contribution_amount":10000.23,
"memo_code": true,
"created": "2022-02-09T00:00:00.000Z",
"updated": "2022-02-09T00:00:00.000Z",
"transaction_type_identifier":"OFFSET_TO_OPEX"
}
},
{
"comment": "SA11AI transaction to NOT count with SA15",
"model": "scha_transactions.schatransaction",
"fields": {
"id": 10002,
"committee_account_id": 1000,
"report_id":9999,
"form_type": "SA11AI",
"contribution_amount":10000.23,
"memo_code": false,
"created": "2022-02-09T00:00:00.000Z",
"updated": "2022-02-09T00:00:00.000Z",
"transaction_type_identifier":"INDV_REC"
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2022-09-01 20:59

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('f3x_summaries', '0016_f3xsummary_webprint_submission'),
]

operations = [
migrations.AddField(
model_name='f3xsummary',
name='calculation_status',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
15 changes: 15 additions & 0 deletions django-backend/fecfiler/f3x_summaries/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django.db import models
from fecfiler.soft_delete.models import SoftDeleteModel
from fecfiler.committee_accounts.models import CommitteeOwnedModel
import logging


logger = logging.getLogger(__name__)


class ReportCodeLabel(models.Model):
Expand Down Expand Up @@ -370,6 +374,9 @@ class F3XSummary(SoftDeleteModel, CommitteeOwnedModel):
null=True,
blank=True,
)

calculation_status = models.CharField(max_length=255, null=True, blank=True)

created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

Expand All @@ -388,5 +395,13 @@ class ReportMixin(models.Model):
"f3x_summaries.F3XSummary", on_delete=models.CASCADE, null=True, blank=True
)

def save(self, *args, **kwargs):
if self.report:
self.report.calculation_status = None
self.report.save()
logger.info(f"F3X Summary: {self.report.id} marked for recalcuation")

super(ReportMixin, self).save(*args, **kwargs)

class Meta:
abstract = True
Loading

0 comments on commit 728ba87

Please sign in to comment.