Skip to content

Commit

Permalink
Merge pull request #1186 from fecgov/release/sprint-50
Browse files Browse the repository at this point in the history
Release/sprint 50
  • Loading branch information
toddlees authored Nov 13, 2024
2 parents 0a34f62 + b7e9924 commit d626460
Show file tree
Hide file tree
Showing 62 changed files with 2,725 additions and 671 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ finance information. The project code is distributed across these repositories:

- [fecfile-web-app](https://github.com/fecgov/fecfile-web-app): this is the browser-based front-end developed in Angular
- [fecfile-web-api](https://github.com/fecgov/fecfile-web-api): RESTful endpoint supporting the front-end
- [fecfile-api-proxy](https://github.com/fecgov/fecfile-api-proxy): Reverse proxy for API for IP blocking and rate limiting
- [fecfile-validate](https://github.com/fecgov/fecfile-validate): data validation rules and engine

---
Expand Down Expand Up @@ -68,6 +69,13 @@ export FEC_FILING_API_KEY="EFO_get_this_from_team_member"

# Deployment (FEC team only)

### Repositories

- API: https://github.com/fecgov/fecfile-web-api
- API proxy: https://github.com/fecgov/fecfile-api-proxy
- Web APP: https://github.com/fecgov/fecfile-web-app
- Validator: https://github.com/fecgov/fecfile-validate

_Special Note:_ If the fecfile-validate repo was updated, the commit of the update needs to be updated in the requirements.txt file otherwise the CircleCI cache will not roll out the change.

### Create a feature branch
Expand All @@ -86,6 +94,8 @@ Without the git-flow extensions:

### Create a release branch

See [Repositories](#repositories) for full list

- Using git-flow extensions:

```
Expand Down Expand Up @@ -128,9 +138,11 @@ git push --set-upstream origin hotfix/my-fix

### Deploying a release to test/production

See [Repositories](#repositories) for full list

- Reviewer approves PR and merges into `main` (At this point the code is automatically deployed)
- Check CircleCI for passing pipeline tests
- If tests pass, continue
- If all tests pass, continue
- (If commits were made to release/sprint-#) Developer creates a PR in GitHub to merge release/sprint-# branch into the `develop` branch
- Reviewer approves PR and merges into `develop`
- Delete release/sprint-# branch
Expand Down
3 changes: 2 additions & 1 deletion bin/run-api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ cd django-backend

# Run migrations and application
./manage.py migrate --no-input --traceback --verbosity 3 &&
exec gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9
python manage.py create_committee_views &&
exec gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9
Empty file.
51 changes: 51 additions & 0 deletions django-backend/fecfiler/cash_on_hand/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Generated by Django 4.2.7 on 2024-01-16 20:15

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):
initial = True

dependencies = [
("committee_accounts", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="CashOnHandYearly",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
(
"cash_on_hand",
models.DecimalField(
blank=True, decimal_places=2, max_digits=11, null=True
),
),
("year", models.TextField(blank=True, null=True)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
(
"committee_account",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="committee_accounts.committeeaccount",
),
),
],
options={
"db_table": "cash_on_hand_yearly",
},
),
]
Empty file.
32 changes: 32 additions & 0 deletions django-backend/fecfiler/cash_on_hand/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.db import models, transaction as db_transaction
from fecfiler.reports.models import Report
from fecfiler.committee_accounts.models import CommitteeOwnedModel
import uuid


class CashOnHandYearly(CommitteeOwnedModel):

id = models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
)

year = models.TextField(null=False, blank=False, unique=True)
cash_on_hand = models.DecimalField(
null=False, blank=False, max_digits=11, decimal_places=2
)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

def save(self, *args, **kwargs):
with db_transaction.atomic():
super(CashOnHandYearly, self).save(*args, **kwargs)
Report.objects.filter(
committee_account=self.committee_account,
).update(calculation_status=None)

class Meta:
db_table = "cash_on_hand_yearly"
16 changes: 16 additions & 0 deletions django-backend/fecfiler/cash_on_hand/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .models import CashOnHandYearly
from fecfiler.committee_accounts.serializers import CommitteeOwnedSerializer
import logging

logger = logging.getLogger(__name__)


class CashOnHandYearlySerializer(CommitteeOwnedSerializer):
class Meta:
model = CashOnHandYearly
fields = [f.name for f in CashOnHandYearly._meta.get_fields()]
read_only_fields = [
"id",
"created",
"updated",
]
Empty file.
48 changes: 48 additions & 0 deletions django-backend/fecfiler/cash_on_hand/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.test import TestCase
from ..models import CashOnHandYearly
from fecfiler.reports.tests.utils import create_form3x
from fecfiler.committee_accounts.models import CommitteeAccount
from fecfiler.committee_accounts.utils import create_committee_view
from fecfiler.web_services.summary.tasks import CalculationState
from fecfiler.reports.models import Report


class CashOnHandYearlyTestCase(TestCase):

def setUp(self):
self.committee = CommitteeAccount.objects.create(committee_id="C00000000")
create_committee_view(self.committee.id)

def test_calcualtion_status_flag(self):
f3x1 = create_form3x(
self.committee,
"2005-01-01",
"2005-01-30",
)
f3x2 = create_form3x(
self.committee,
"2005-02-01",
"2005-02-28",
)
Report.objects.update(calculation_status=CalculationState.SUCCEEDED)

CashOnHandYearly.objects.create(
committee_account=self.committee,
year="2016",
cash_on_hand=100,
)

self.assertIsNone(Report.objects.get(pk=f3x1.id).calculation_status)
self.assertIsNone(Report.objects.get(pk=f3x2.id).calculation_status)

def test_permissions(self):
CashOnHandYearly.objects.create(
committee_account=self.committee,
year="2016",
cash_on_hand=100,
)
other_committee = CommitteeAccount.objects.create(committee_id="C00000001")
other_committees_coh = CashOnHandYearly.objects.filter(
committee_account=other_committee
).first()
self.assertIsNone(other_committees_coh)
65 changes: 65 additions & 0 deletions django-backend/fecfiler/cash_on_hand/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from django.test import RequestFactory, TestCase
from ..views import CashOnHandYearlyViewSet
from ..models import CashOnHandYearly
from fecfiler.committee_accounts.models import CommitteeAccount
from .utils import create_cash_on_hand_yearly
from rest_framework.test import force_authenticate

from fecfiler.user.models import User


class CashOnHandYearlyViewSetTest(TestCase):
fixtures = ["C01234567_user_and_committee"]

def setUp(self):
self.user = User.objects.get(id="12345678-aaaa-bbbb-cccc-111122223333")

self.committee = CommitteeAccount.objects.get(committee_id="C01234567")
self.factory = RequestFactory()

def test_no_override(self):
request = self.factory.get("/api/v1/cash_on_hand/year/2024/")
request.user = self.user
request.session = {
"committee_uuid": str(self.committee.id),
"committee_id": str(self.committee.committee_id),
}
other_committee = CommitteeAccount.objects.create(committee_id="C00000001")
create_cash_on_hand_yearly(other_committee, "2024", 1)
response = CashOnHandYearlyViewSet.as_view({"get": "cash_on_hand_for_year"})(
request, year="2024"
)
self.assertEqual(response.status_code, 404)

def test_get_override(self):
request = self.factory.get("/api/v1/cash_on_hand/year/2024/")
request.user = self.user
request.session = {
"committee_uuid": str(self.committee.id),
"committee_id": str(self.committee.committee_id),
}
create_cash_on_hand_yearly(self.committee, "2024", 1)
response = CashOnHandYearlyViewSet.as_view({"get": "cash_on_hand_for_year"})(
request, year="2024"
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["cash_on_hand"], "1.00")

def test_set_override(self):
request = self.factory.post(
"/api/v1/cash_on_hand/year/2024/", {"cash_on_hand": 2}
)
request.user = self.user
request.session = {
"committee_uuid": str(self.committee.id),
"committee_id": str(self.committee.committee_id),
}
force_authenticate(request, self.user)
response = CashOnHandYearlyViewSet.as_view({"post": "cash_on_hand_for_year"})(
request, year="2024"
)
self.assertEqual(response.status_code, 200)
cash_on_hand = CashOnHandYearly.objects.get(
committee_account=self.committee, year="2024"
)
self.assertEqual(cash_on_hand.cash_on_hand, 2)
7 changes: 7 additions & 0 deletions django-backend/fecfiler/cash_on_hand/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from ..models import CashOnHandYearly


def create_cash_on_hand_yearly(committee_account, year, cash_on_hand):
return CashOnHandYearly.objects.create(
committee_account=committee_account, year=year, cash_on_hand=cash_on_hand
)
12 changes: 12 additions & 0 deletions django-backend/fecfiler/cash_on_hand/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CashOnHandYearlyViewSet

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r"cash_on_hand", CashOnHandYearlyViewSet, basename="cash_on_hand")

# The API URLs are now determined automatically by the router.
urlpatterns = [
path("", include(router.urls)),
]
54 changes: 54 additions & 0 deletions django-backend/fecfiler/cash_on_hand/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from rest_framework.response import Response
from rest_framework.decorators import action
from fecfiler.committee_accounts.views import CommitteeOwnedViewMixin
from .serializers import CashOnHandYearlySerializer
from .models import CashOnHandYearly
import logging

logger = logging.getLogger(__name__)


class CashOnHandYearlyViewSet(CommitteeOwnedViewMixin):
"""
Note that this ViewSet inherits from CommitteeOwnedViewMixin
The queryset will be further limited by the user's committee
in CommitteeOwnedViewMixin's implementation of get_queryset()
"""

queryset = CashOnHandYearly.objects
serializer_class = CashOnHandYearlySerializer
pagination_class = None

@action(detail=False, methods=["get", "post"], url_path=r"year/(?P<year>\d+)")
def cash_on_hand_for_year(self, request, year):
if request.method == "GET":
return self.get_cash_on_hand_for_year(request, year)
elif request.method == "POST":
return self.set_cash_on_hand_for_year(request, year)

def get_cash_on_hand_for_year(self, request, year):
cash_on_hand = self.get_queryset().filter(year=year).first()
if cash_on_hand is None:
return Response(status=404)
serializer = self.get_serializer(cash_on_hand)
return Response(serializer.data)

def set_cash_on_hand_for_year(self, request, year):
new_cash_on_hand = request.data.get("cash_on_hand")
committee_uuid = self.get_committee_uuid()
if new_cash_on_hand is None:
return Response("cash_on_hand is required", status=400)
cash_on_hand_record = self.get_queryset().filter(year=year).first()
if cash_on_hand_record is None:
cash_on_hand_record = CashOnHandYearly.objects.create(
committee_account_id=committee_uuid,
year=year,
cash_on_hand=new_cash_on_hand,
)
else:
cash_on_hand_record.cash_on_hand = new_cash_on_hand
cash_on_hand_record.save()

serializer = self.get_serializer(cash_on_hand_record)
return Response(serializer.data)
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
from fecfiler.committee_accounts.models import CommitteeAccount
from fecfiler.committee_accounts.utils import create_committee_view

import structlog


logger = structlog.get_logger(__name__)


class Command(BaseCommand):
help = "Create or update committee views"

def handle(self, *args, **options):
for committee in CommitteeAccount.objects.all():
logger.info(f"Running create_committee_view on {committee.id}")
create_committee_view(committee.id)

self.stdout.write(
self.style.SUCCESS("Successfully created/updated committee views")
)
logger.info("Successfully created/updated committee views")
5 changes: 4 additions & 1 deletion django-backend/fecfiler/committee_accounts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ def create_committee_view(committee_uuid):
.query.sql_with_params()
)
definition = cursor.mogrify(sql, params).decode("utf-8")
cursor.execute(f"CREATE OR REPLACE VIEW {view_name} as {definition}")
cursor.execute(
f"DROP VIEW IF EXISTS {view_name};"
f"CREATE VIEW {view_name} as {definition}"
)


def get_committee_account_data(committee_id):
Expand Down
4 changes: 3 additions & 1 deletion django-backend/fecfiler/committee_accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ def create_account(self, request):
raise Exception("no committee_id provided")
account = create_committee_account(committee_id, request.user)

return Response(CommitteeAccountSerializer(account).data)
return Response(
self.add_committee_account_data(CommitteeAccountSerializer(account).data)
)

@action(detail=False, methods=["get"], url_path="get-available-committee")
def get_available_committee(self, request):
Expand Down
1 change: 0 additions & 1 deletion django-backend/fecfiler/reports/form_3x/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class Form3X(models.Model):
date_of_election = models.DateField(null=True, blank=True)
state_of_election = models.TextField(null=True, blank=True)
qualified_committee = models.BooleanField(default=False, null=True, blank=True)
cash_on_hand_date = models.DateField(null=True, blank=True)
L6b_cash_on_hand_beginning_period = models.DecimalField(
null=True, blank=True, max_digits=11, decimal_places=2
)
Expand Down
Loading

0 comments on commit d626460

Please sign in to comment.