From 48d04e5be2e8184a0c2d8f9ca19194ff3c5586f7 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Fri, 3 May 2024 12:13:33 -0400 Subject: [PATCH 01/30] Makes the committee registration email check case-insensitive --- django-backend/fecfiler/committee_accounts/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django-backend/fecfiler/committee_accounts/views.py b/django-backend/fecfiler/committee_accounts/views.py index 9492501a9e..1ec73e402e 100644 --- a/django-backend/fecfiler/committee_accounts/views.py +++ b/django-backend/fecfiler/committee_accounts/views.py @@ -219,7 +219,7 @@ def register_committee(committee_id, user): f1_email = f1_line.split(FS_STR)[11] failure_reason = None - if f1_email != email: + if not f1_email or f1_email.lower() != email.lower(): failure_reason = f"Email {email} does not match committee email" existing_account = CommitteeAccount.objects.filter( From ca1fd4a5a288771e7c5dbefa8e398c6352c52826 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Fri, 17 May 2024 09:52:34 -0400 Subject: [PATCH 02/30] WIP --- django-backend/fecfiler/transactions/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django-backend/fecfiler/transactions/views.py b/django-backend/fecfiler/transactions/views.py index 738e6b1514..6dcc3d6379 100644 --- a/django-backend/fecfiler/transactions/views.py +++ b/django-backend/fecfiler/transactions/views.py @@ -69,6 +69,8 @@ def get_queryset(self): committee_uuid = self.get_committee_uuid() model = get_read_model(committee_uuid) queryset = model.objects + + logger.debug(self.request.data) report_id = ( ( self.request.query_params.get("report_id") From a8c7b62d7458306dcf23818c27cb81a32248d8ea Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 20 May 2024 12:55:38 -0400 Subject: [PATCH 03/30] Adds unit test coverage --- .../fecfiler/committee_accounts/test_views.py | 12 ++++++++++++ django-backend/fecfiler/committee_accounts/views.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/django-backend/fecfiler/committee_accounts/test_views.py b/django-backend/fecfiler/committee_accounts/test_views.py index 19131c6d84..dbe02ae428 100644 --- a/django-backend/fecfiler/committee_accounts/test_views.py +++ b/django-backend/fecfiler/committee_accounts/test_views.py @@ -41,6 +41,18 @@ def test_register_committee_mismatch_email(self): user=self.other_user, ) + def test_register_committee_case_insensitive(self): + self.test_user.email = self.test_user.email.upper() + account = register_committee("C12345678", self.test_user) + self.assertEquals(account.committee_id, "C12345678") + self.assertRaisesMessage( + Exception, + self.register_error_message, + register_committee, + committee_id="C12345678", + user=self.test_user, + ) + class CommitteeMemberViewSetTest(TestCase): fixtures = ["C01234567_user_and_committee", "unaffiliated_users"] diff --git a/django-backend/fecfiler/committee_accounts/views.py b/django-backend/fecfiler/committee_accounts/views.py index ece4fd3e62..d413123c11 100644 --- a/django-backend/fecfiler/committee_accounts/views.py +++ b/django-backend/fecfiler/committee_accounts/views.py @@ -222,7 +222,7 @@ def register_committee(committee_id, user): failure_reason = None if not f1_email: - failure_reason = f"No email provided in F1" + failure_reason = "No email provided in F1" else: f1_email_lowercase = f1_email.lower() f1_emails = [] From 650bd91ce9059bf9553b504df79ef9b651feba2b Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Mon, 20 May 2024 12:59:58 -0400 Subject: [PATCH 04/30] Add try/catch around web print submit --- django-backend/fecfiler/web_services/tasks.py | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/django-backend/fecfiler/web_services/tasks.py b/django-backend/fecfiler/web_services/tasks.py index bf8010e6cd..d67c44c15d 100644 --- a/django-backend/fecfiler/web_services/tasks.py +++ b/django-backend/fecfiler/web_services/tasks.py @@ -154,23 +154,27 @@ def submit_to_webprint( email = WEBPRINT_EMAIL """Submit to WebPrint""" - submitter = WebPrintSubmitter(api) - logger.info(f"Uploading {file_name} to FEC WebPrint") - submission_response_string = submitter.submit(email, dot_fec_bytes) - submission.save_fec_response(submission_response_string) - - """Poll FEC for status of submission""" - # TODO: add timeout? - while submission.fec_status not in FECStatus.get_terminal_statuses_strings(): - logger.info(f"Polling status for {submission.fec_submission_id}.") - logger.info( - f"Status: {submission.fec_status}, Message: {submission.fec_message}" - ) - time.sleep(2) - status_response_string = submitter.poll_status( - submission.batch_id, submission.fec_submission_id - ) - submission.save_fec_response(status_response_string) + try: + submitter = WebPrintSubmitter(api) + logger.info(f"Uploading {file_name} to FEC WebPrint") + submission_response_string = submitter.submit(email, dot_fec_bytes) + submission.save_fec_response(submission_response_string) + + """Poll FEC for status of submission""" + # TODO: add timeout? + while submission.fec_status not in FECStatus.get_terminal_statuses_strings(): + logger.info(f"Polling status for {submission.fec_submission_id}.") + logger.info( + f"Status: {submission.fec_status}, Message: {submission.fec_message}" + ) + time.sleep(2) + status_response_string = submitter.poll_status( + submission.batch_id, submission.fec_submission_id + ) + submission.save_fec_response(status_response_string) + except Exception: + submission.save_error("Failed submitting to WebPrint") + return new_state = ( FECSubmissionState.SUCCEEDED From b2dfb826d4f02a8ed4222c9d1af9615a401552f3 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 21 May 2024 11:04:49 -0400 Subject: [PATCH 05/30] 817 added migration to remove old accounts --- .../0006_remove_old_login_accounts.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py diff --git a/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py b/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py new file mode 100644 index 0000000000..1e078336fd --- /dev/null +++ b/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py @@ -0,0 +1,30 @@ +from django.db import migrations +from django.db.models import Q + + +def remove_old_login_accounts(apps, schema_editor): + User = apps.get_model("user", "User") # noqa + Membership = apps.get_model("committee_accounts", "Membership") # noqa + + user_ids_to_delete = User.objects.filter( + ~Q(username__contains="%-%-%-%-%") + ).values_list("id", flat=True) + Membership.objects.filter(user_id__in=user_ids_to_delete).delete() + User.objects.filter(id__in=user_ids_to_delete).delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "user", + "0005_rename_security_consent_date_user_security_consent_exp_date", + ) + ] + + operations = [ + migrations.RunPython( + remove_old_login_accounts, + migrations.RunPython.noop, + ), + ] From f0e6723c34168fd40c54071e0268157c706718bb Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 21 May 2024 15:03:31 -0400 Subject: [PATCH 06/30] 817 local testing --- .../006_remove_old_login_accounts.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py diff --git a/django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py b/django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py new file mode 100644 index 0000000000..6fc82e8a57 --- /dev/null +++ b/django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py @@ -0,0 +1,27 @@ +from django.db import migrations + + +def remove_old_login_accounts(apps, schema_editor): + User = apps.get_model("user", "User") # noqa + + users_to_delete = User.objects.filter(username__contains="@") + for user in users_to_delete: + user.membership_set.all().delete() + users_to_delete.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "user", + "0005_rename_security_consent_date_user_security_consent_exp_date", + ) + ] + + operations = [ + migrations.RunPython( + remove_old_login_accounts, + migrations.RunPython.noop, + ), + ] From a61bc8a1c9880b05125b6ca9c7d03d5349a1b497 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 21 May 2024 15:20:58 -0400 Subject: [PATCH 07/30] 817 testing fix --- .../0006_remove_old_login_accounts.py | 11 +++----- .../006_remove_old_login_accounts.py | 27 ------------------- 2 files changed, 4 insertions(+), 34 deletions(-) delete mode 100644 django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py diff --git a/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py b/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py index 1e078336fd..6fc82e8a57 100644 --- a/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py +++ b/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py @@ -1,16 +1,13 @@ from django.db import migrations -from django.db.models import Q def remove_old_login_accounts(apps, schema_editor): User = apps.get_model("user", "User") # noqa - Membership = apps.get_model("committee_accounts", "Membership") # noqa - user_ids_to_delete = User.objects.filter( - ~Q(username__contains="%-%-%-%-%") - ).values_list("id", flat=True) - Membership.objects.filter(user_id__in=user_ids_to_delete).delete() - User.objects.filter(id__in=user_ids_to_delete).delete() + users_to_delete = User.objects.filter(username__contains="@") + for user in users_to_delete: + user.membership_set.all().delete() + users_to_delete.delete() class Migration(migrations.Migration): diff --git a/django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py b/django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py deleted file mode 100644 index 6fc82e8a57..0000000000 --- a/django-backend/fecfiler/user/migrations/006_remove_old_login_accounts.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.db import migrations - - -def remove_old_login_accounts(apps, schema_editor): - User = apps.get_model("user", "User") # noqa - - users_to_delete = User.objects.filter(username__contains="@") - for user in users_to_delete: - user.membership_set.all().delete() - users_to_delete.delete() - - -class Migration(migrations.Migration): - - dependencies = [ - ( - "user", - "0005_rename_security_consent_date_user_security_consent_exp_date", - ) - ] - - operations = [ - migrations.RunPython( - remove_old_login_accounts, - migrations.RunPython.noop, - ), - ] From ddaa3da8efb0c0a58bad530fdb8190f95b6775d8 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 21 May 2024 15:57:17 -0400 Subject: [PATCH 08/30] 817 added usernames from environments --- .../user/migrations/0006_remove_old_login_accounts.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py b/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py index 6fc82e8a57..f281f6bbd1 100644 --- a/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py +++ b/django-backend/fecfiler/user/migrations/0006_remove_old_login_accounts.py @@ -1,10 +1,13 @@ from django.db import migrations +from django.db.models import Q def remove_old_login_accounts(apps, schema_editor): User = apps.get_model("user", "User") # noqa - users_to_delete = User.objects.filter(username__contains="@") + users_to_delete = User.objects.filter( + Q(username__contains="@") | Q(username="adminnxg") | Q(username="tt") + ) for user in users_to_delete: user.membership_set.all().delete() users_to_delete.delete() From 98a226e086edc58bc57ff76b56c6a10fef72b74b Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Wed, 22 May 2024 08:05:46 -0400 Subject: [PATCH 09/30] Reattribution works again --- django-backend/fecfiler/transactions/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django-backend/fecfiler/transactions/views.py b/django-backend/fecfiler/transactions/views.py index 6dcc3d6379..e70c1aae1e 100644 --- a/django-backend/fecfiler/transactions/views.py +++ b/django-backend/fecfiler/transactions/views.py @@ -275,9 +275,9 @@ def save_transaction(self, transaction_data, request): for child_transaction_data in children: if type(child_transaction_data) is str: - child_transaction = self.get_queryset().get(id=child_transaction_data) - child_transaction.parent_transaction_id = transaction_instance.id - child_transaction.save() + Transaction.objects.filter(id=child_transaction_data).update( + parent_transaction_id=transaction_instance.id + ) else: child_transaction_data["parent_transaction_id"] = transaction_instance.id child_transaction_data.pop("parent_transaction", None) From 29f03edfcd216cbf5f7af054a562fc2736987a6f Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Wed, 22 May 2024 14:36:23 -0400 Subject: [PATCH 10/30] Removes debug call --- django-backend/fecfiler/transactions/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django-backend/fecfiler/transactions/views.py b/django-backend/fecfiler/transactions/views.py index e70c1aae1e..55bf09dba7 100644 --- a/django-backend/fecfiler/transactions/views.py +++ b/django-backend/fecfiler/transactions/views.py @@ -70,7 +70,6 @@ def get_queryset(self): model = get_read_model(committee_uuid) queryset = model.objects - logger.debug(self.request.data) report_id = ( ( self.request.query_params.get("report_id") From 791d02c441d62f45b0d71cfed644b861429df734 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Wed, 22 May 2024 15:07:13 -0400 Subject: [PATCH 11/30] 632 fixes --- bin/run-api.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/run-api.sh b/bin/run-api.sh index ea2de790cb..e3ba28d6e2 100755 --- a/bin/run-api.sh +++ b/bin/run-api.sh @@ -1,6 +1,6 @@ cd django-backend # Run migrations and application -./manage.py migrate --no-input --traceback --verbosity 3 > migrate.out && +./manage.py migrate --no-input --traceback --verbosity 3 && python manage.py create_committee_views && - gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9 + exec gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9 From ed777995dac07d670d50e5a1c1e1a1b428f80366 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Fri, 24 May 2024 15:19:59 -0400 Subject: [PATCH 12/30] Update single digit line numbers to have leading zeroes for correct sorting --- django-backend/fecfiler/transactions/schedule_c/managers.py | 2 +- django-backend/fecfiler/transactions/schedule_d/managers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django-backend/fecfiler/transactions/schedule_c/managers.py b/django-backend/fecfiler/transactions/schedule_c/managers.py index ee1c2b8b8a..3e647f57a4 100644 --- a/django-backend/fecfiler/transactions/schedule_c/managers.py +++ b/django-backend/fecfiler/transactions/schedule_c/managers.py @@ -1 +1 @@ -line_labels = {"SC/10": "10", "SC/9": "9"} +line_labels = {"SC/10": "10", "SC/9": "09"} diff --git a/django-backend/fecfiler/transactions/schedule_d/managers.py b/django-backend/fecfiler/transactions/schedule_d/managers.py index cda0c8e06c..9494831d73 100644 --- a/django-backend/fecfiler/transactions/schedule_d/managers.py +++ b/django-backend/fecfiler/transactions/schedule_d/managers.py @@ -1 +1 @@ -line_labels = {"SD9": "9", "SD10": "10"} +line_labels = {"SD9": "09", "SD10": "10"} From 026db9cffc9df03745b63b0657bcc4f086e1d4dd Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 28 May 2024 13:20:36 -0400 Subject: [PATCH 13/30] 734 update web-services/api manifests with envvars --- manifests/manifest-dev-api.yml | 3 ++- manifests/manifest-dev-web-services.yml | 2 ++ manifests/manifest-prod-api.yml | 7 ++++--- manifests/manifest-prod-web-services.yml | 2 ++ manifests/manifest-stage-api.yml | 7 ++++--- manifests/manifest-stage-web-services.yml | 2 ++ 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/manifests/manifest-dev-api.yml b/manifests/manifest-dev-api.yml index c2eae141d8..1cc686c7de 100644 --- a/manifests/manifest-dev-api.yml +++ b/manifests/manifest-dev-api.yml @@ -20,10 +20,11 @@ applications: CORS_ALLOWED_ORIGINS: https://fecfile-web-app-dev.app.cloud.gov CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-dev.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov - FRONTEND_URL: fecfile-web-app-dev.app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-dev.app.cloud.gov LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-dev.app.cloud.gov/api/v1/auth/login-redirect LOGOUT_REDIRECT_URL: https://fecfile-web-api-dev.app.cloud.gov/api/v1/auth/logout-redirect + FEC_API: https://api.open.fec.gov/v1/ SESSION_COOKIE_AGE: 64800 + BP_PIP_VERSION: latest LOG_FORMAT: KEY_VALUE MOCK_OPENFEC: REDIS diff --git a/manifests/manifest-dev-web-services.yml b/manifests/manifest-dev-web-services.yml index 5fc17a503b..f80b77f5a8 100644 --- a/manifests/manifest-dev-web-services.yml +++ b/manifests/manifest-dev-web-services.yml @@ -17,4 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production + FEC_AGENCY_ID: FEC + FEC_FILING_API: https://efoservices.stage.efo.fec.gov LOG_FORMAT: KEY_VALUE diff --git a/manifests/manifest-prod-api.yml b/manifests/manifest-prod-api.yml index c31cd1e98a..eee8a04fa6 100644 --- a/manifests/manifest-prod-api.yml +++ b/manifests/manifest-prod-api.yml @@ -15,15 +15,16 @@ applications: - fecfile-api-redis - fecfile-api-creds-prod env: - CORS_ALLOWED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov - CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production + CORS_ALLOWED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov + CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-prod.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov - FRONTEND_URL: fecfile-web-app-prod.app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-prod.app.cloud.gov LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-prod.app.cloud.gov/api/v1/auth/login-redirect LOGOUT_REDIRECT_URL: https://fecfile-web-api-prod.app.cloud.gov/api/v1/auth/logout-redirect + FEC_API: https://api.open.fec.gov/v1/ SESSION_COOKIE_AGE: 64800 + BP_PIP_VERSION: latest LOG_FORMAT: KEY_VALUE MOCK_OPENFEC: REDIS diff --git a/manifests/manifest-prod-web-services.yml b/manifests/manifest-prod-web-services.yml index de5009b96d..e4ef517d77 100644 --- a/manifests/manifest-prod-web-services.yml +++ b/manifests/manifest-prod-web-services.yml @@ -17,4 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production + FEC_AGENCY_ID: FEC + FEC_FILING_API: https://efoservices.stage.efo.fec.gov LOG_FORMAT: KEY_VALUE diff --git a/manifests/manifest-stage-api.yml b/manifests/manifest-stage-api.yml index e5a20cdc6d..7e23e555b1 100644 --- a/manifests/manifest-stage-api.yml +++ b/manifests/manifest-stage-api.yml @@ -15,15 +15,16 @@ applications: - fecfile-api-redis - fecfile-api-creds-stage env: - CORS_ALLOWED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov - CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production + CORS_ALLOWED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov + CSRF_TRUSTED_ORIGINS: https://fecfile-web-app-stage.app.cloud.gov FFAPI_COOKIE_DOMAIN: app.cloud.gov - FRONTEND_URL: fecfile-web-app-stage.app.cloud.gov LOGIN_REDIRECT_CLIENT_URL: https://fecfile-web-app-stage.app.cloud.gov LOGIN_REDIRECT_SERVER_URL: https://fecfile-web-api-stage.app.cloud.gov/api/v1/auth/login-redirect LOGOUT_REDIRECT_URL: https://fecfile-web-api-stage.app.cloud.gov/api/v1/auth/logout-redirect + FEC_API: https://api.open.fec.gov/v1/ SESSION_COOKIE_AGE: 64800 + BP_PIP_VERSION: latest LOG_FORMAT: KEY_VALUE MOCK_OPENFEC: REDIS diff --git a/manifests/manifest-stage-web-services.yml b/manifests/manifest-stage-web-services.yml index f0c9efe7fa..81a196744c 100644 --- a/manifests/manifest-stage-web-services.yml +++ b/manifests/manifest-stage-web-services.yml @@ -17,4 +17,6 @@ applications: env: DISABLE_COLLECTSTATIC: 1 DJANGO_SETTINGS_MODULE: fecfiler.settings.production + FEC_AGENCY_ID: FEC + FEC_FILING_API: https://efoservices.stage.efo.fec.gov LOG_FORMAT: KEY_VALUE From 5b9cd8717071ed1b46c8ae03fb51347d17b51bbe Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Tue, 28 May 2024 16:41:55 -0400 Subject: [PATCH 14/30] Update transaction Manager to have report code label in order to sort via Report Type --- .../fecfiler/transactions/managers.py | 47 +++++++++++++++++-- .../fecfiler/transactions/serializers.py | 26 +++++----- django-backend/fecfiler/transactions/views.py | 2 + 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/django-backend/fecfiler/transactions/managers.py b/django-backend/fecfiler/transactions/managers.py index f55b1dbe8a..bd8002f8f1 100644 --- a/django-backend/fecfiler/transactions/managers.py +++ b/django-backend/fecfiler/transactions/managers.py @@ -33,11 +33,41 @@ from decimal import Decimal from enum import Enum from .schedule_b.managers import refunds as schedule_b_refunds +from ..reports.models import Report """Manager to deterimine fields that are used the same way across transactions, but are called different names""" +report_code_label_mapping = Case( + When(report_code="Q1", then=Value("APRIL 15 QUARTERLY REPORT (Q1)")), + When(report_code="Q2", then=Value("JULY 15 QUARTERLY REPORT (Q2)")), + When(report_code="Q3", then=Value("OCTOBER 15 QUARTERLY REPORT (Q3)")), + When(report_code="YE", then=Value("JANUARY 31 YEAR-END (YE)")), + When(report_code="TER", then=Value("TERMINATION REPORT (TER)")), + When(report_code="MY", then=Value("JULY 31 MID-YEAR REPORT (MY)")), + When(report_code="12G", then=Value("12-DAY PRE-GENERAL (12G)")), + When(report_code="12P", then=Value("12-DAY PRE-PRIMARY (12P)")), + When(report_code="12R", then=Value("12-DAY PRE-RUNOFF (12R)")), + When(report_code="12S", then=Value("12-DAY PRE-SPECIAL (12S)")), + When(report_code="12C", then=Value("12-DAY PRE-CONVENTION (12C)")), + When(report_code="30G", then=Value("30-DAY POST-GENERAL (30G)")), + When(report_code="30R", then=Value("30-DAY POST-RUNOFF (30R)")), + When(report_code="30S", then=Value("30-DAY POST-SPECIAL (30S)")), + When(report_code="M2", then=Value("FEBRUARY 20 MONTHLY REPORT (M2)")), + When(report_code="M3", then=Value("MARCH 20 MONTHLY REPORT (M3)")), + When(report_code="M4", then=Value("APRIL 20 MONTHLY REPORT (M4)")), + When(report_code="M5", then=Value("MAY 20 MONTHLY REPORT (M5)")), + When(report_code="M6", then=Value("JUNE 20 MONTHLY REPORT (M6)")), + When(report_code="M7", then=Value("JULY 20 MONTHLY REPORT (M7)")), + When(report_code="M8", then=Value("AUGUST 20 MONTHLY REPORT (M8)")), + When(report_code="M9", then=Value("SEPTEMBER 20 MONTHLY REPORT (M9)")), + When(report_code="M10", then=Value("OCTOBER 20 MONTHLY REPORT (M10)")), + When(report_code="M11", then=Value("NOVEMBER 20 MONTHLY REPORT (M11)")), + When(report_code="M12", then=Value("DECEMBER 20 MONTHLY REPORT (M12)")), +) + + class TransactionManager(SoftDeleteManager): entity_aggregate_window = { "partition_by": [ @@ -303,6 +333,11 @@ def PAYMENT_AMOUNT_CLAUSE(self): # noqa: N802 ) def transaction_view(self): + REPORT_CODE_LABEL_CLAUSE = Subquery( # noqa: N806 + Report.objects.filter(transactions=OuterRef("pk")) + .annotate(report_code_label=report_code_label_mapping) + .values("report_code_label")[:1] + ) return ( super() .get_queryset() @@ -324,12 +359,19 @@ def transaction_view(self): transaction_ptr_id=F("id"), back_reference_tran_id_number=self.BACK_REFERENCE_CLAUSE, back_reference_sched_name=self.BACK_REFERENCE_NAME_CLAUSE, + report_code_label=REPORT_CODE_LABEL_CLAUSE, ) ) class TransactionViewManager(Manager): def get_queryset(self): + REPORT_CODE_LABEL_CLAUSE = Subquery( # noqa: N806 + Report.objects.filter(transactions=OuterRef("pk")) + .annotate(report_code_label=report_code_label_mapping) + .values("report_code_label")[:1] + ) + return ( super() .get_queryset() @@ -376,6 +418,7 @@ def get_queryset(self): "_calendar_ytd_per_election_office", ), line_label=self.LINE_LABEL_CLAUSE(), + report_code_label=REPORT_CODE_LABEL_CLAUSE, ) .alias(order_key=self.ORDER_KEY_CLAUSE()) .order_by("order_key") @@ -404,9 +447,7 @@ def ORDER_KEY_CLAUSE(self): # noqa: N802 output_field=TextField(), ), ), - default=Concat( - "schedule", "_form_type", "created", output_field=TextField() - ), + default=Concat("schedule", "_form_type", "created", output_field=TextField()), output_field=TextField(), ) diff --git a/django-backend/fecfiler/transactions/serializers.py b/django-backend/fecfiler/transactions/serializers.py index b366c64028..493ff717ee 100644 --- a/django-backend/fecfiler/transactions/serializers.py +++ b/django-backend/fecfiler/transactions/serializers.py @@ -33,9 +33,7 @@ logger = structlog.get_logger(__name__) -MISSING_SCHEMA_NAME_ERROR = ValidationError( - {"schema_name": ["No schema_name provided"]} -) +MISSING_SCHEMA_NAME_ERROR = ValidationError({"schema_name": ["No schema_name provided"]}) SCHEDULE_SERIALIZERS = dict( A=ScheduleASerializer, @@ -76,9 +74,7 @@ class TransactionSerializer( back_reference_tran_id_number = CharField( required=False, allow_null=True, read_only=True ) - back_reference_sched_name = CharField( - required=False, allow_null=True, read_only=True - ) + back_reference_sched_name = CharField(required=False, allow_null=True, read_only=True) form_type = CharField(required=False, allow_null=True) itemized = BooleanField(read_only=True) name = CharField(read_only=True) @@ -98,6 +94,7 @@ class TransactionSerializer( max_digits=11, decimal_places=2, read_only=True ) # debt payments line_label = CharField(read_only=True) + report_code_label = CharField(read_only=True) class Meta: model = Transaction @@ -144,6 +141,7 @@ def get_fields(): "payment_amount", "balance_at_close", "line_label", + "report_code_label", ] fields = get_fields() @@ -261,13 +259,15 @@ def to_representation(self, instance): representation["report_ids"] = [] for report in instance.reports.all(): representation["report_ids"].append(report.id) - representation["reports"].append({ - "id": report.id, - "coverage_from_date": report.coverage_from_date, - "coverage_through_date": report.coverage_through_date, - "report_code": report.report_code, - "report_type": report.report_type - }) + representation["reports"].append( + { + "id": report.id, + "coverage_from_date": report.coverage_from_date, + "coverage_through_date": report.coverage_through_date, + "report_code": report.report_code, + "report_type": report.report_type, + } + ) return representation diff --git a/django-backend/fecfiler/transactions/views.py b/django-backend/fecfiler/transactions/views.py index 738e6b1514..704f8fe112 100644 --- a/django-backend/fecfiler/transactions/views.py +++ b/django-backend/fecfiler/transactions/views.py @@ -52,6 +52,8 @@ class TransactionViewSet(CommitteeOwnedViewMixin, ModelViewSet): "aggregate", "balance", "back_reference_tran_id_number", + "form_type", + "report_code_label", ] ordering = ["-created"] queryset = Transaction.objects From a1498395bf9425a38c857c8bada130c33134ed2c Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 10:13:31 -0400 Subject: [PATCH 15/30] Get reports to recalculate returns the primary report if it has no coverage_from_date --- django-backend/fecfiler/web_services/summary/tasks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/django-backend/fecfiler/web_services/summary/tasks.py b/django-backend/fecfiler/web_services/summary/tasks.py index 662908d5a9..5db80e1130 100644 --- a/django-backend/fecfiler/web_services/summary/tasks.py +++ b/django-backend/fecfiler/web_services/summary/tasks.py @@ -21,6 +21,10 @@ def __str__(self): def get_reports_to_calculate(primary_report): + coverage_from_date = primary_report.coverage_from_date + if coverage_from_date is None: + return primary_report + report_year = primary_report.coverage_from_date.year reports_to_recalculate = Report.objects.filter( ~Q(calculation_status=CalculationState.SUCCEEDED), From c9ba62d177fcaa4e2a659de7cf6ccad087455311 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 10:35:02 -0400 Subject: [PATCH 16/30] Don't attempt to calculate the summary for forms that don't have summary pages --- django-backend/fecfiler/reports/models.py | 4 ++++ django-backend/fecfiler/web_services/summary/tasks.py | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/django-backend/fecfiler/reports/models.py b/django-backend/fecfiler/reports/models.py index 04772e659e..ad2835a6f2 100644 --- a/django-backend/fecfiler/reports/models.py +++ b/django-backend/fecfiler/reports/models.py @@ -160,6 +160,10 @@ def delete(self): "form_1m": "F1M", } +FORMS_TO_CALCULATE = [ + "F3X", +] + def update_recalculation(report: Report): if report: diff --git a/django-backend/fecfiler/web_services/summary/tasks.py b/django-backend/fecfiler/web_services/summary/tasks.py index 5db80e1130..fb2df5960b 100644 --- a/django-backend/fecfiler/web_services/summary/tasks.py +++ b/django-backend/fecfiler/web_services/summary/tasks.py @@ -1,6 +1,6 @@ from enum import Enum from celery import shared_task -from fecfiler.reports.models import Report +from fecfiler.reports.models import Report, FORMS_TO_CALCULATE from django.db.models import Q from .summary import SummaryService import uuid @@ -21,8 +21,7 @@ def __str__(self): def get_reports_to_calculate(primary_report): - coverage_from_date = primary_report.coverage_from_date - if coverage_from_date is None: + if not hasattr(primary_report, "coverage_from_date"): return primary_report report_year = primary_report.coverage_from_date.year @@ -44,6 +43,9 @@ def calculate_summary(report_id): except Exception: return None + if primary_report.get_form_name() not in FORMS_TO_CALCULATE: + return primary_report.id + reports_to_recalculate = get_reports_to_calculate(primary_report) calculation_token = uuid.uuid4() reports_to_recalculate.update( From c39d75ca54f8588e3df33dcd26e4540a791666dc Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 11:06:42 -0400 Subject: [PATCH 17/30] Expands unit testing to cover creating a dot_fec for a Form 24 --- .../fecfiler/reports/tests/test_models.py | 2 +- django-backend/fecfiler/reports/tests/utils.py | 8 ++++---- django-backend/fecfiler/web_services/test_views.py | 14 +++++++++++++- django-backend/fecfiler/web_services/views.py | 4 ++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/django-backend/fecfiler/reports/tests/test_models.py b/django-backend/fecfiler/reports/tests/test_models.py index 3fa233bdc4..d3bdacd77c 100644 --- a/django-backend/fecfiler/reports/tests/test_models.py +++ b/django-backend/fecfiler/reports/tests/test_models.py @@ -41,7 +41,7 @@ def test_amending_f24(self): self.assertEquals(f24_report.form_type, "F24A") def test_delete(self): - f24_report = create_form24(self.committee, "2024-01-01", "2024-02-01", {}) + f24_report = create_form24(self.committee, {}) f24_report_id = f24_report.id f24_id = f24_report.form_24.id f3x_report = create_form3x(self.committee, "2024-01-01", "2024-02-01", {}) diff --git a/django-backend/fecfiler/reports/tests/utils.py b/django-backend/fecfiler/reports/tests/utils.py index 3fa420e9c7..74573eafaa 100644 --- a/django-backend/fecfiler/reports/tests/utils.py +++ b/django-backend/fecfiler/reports/tests/utils.py @@ -6,16 +6,16 @@ from fecfiler.reports.form_99.models import Form99 -def create_form3x(committee, coverage_from, coverage_through, data): +def create_form3x(committee, coverage_from, coverage_through, data={}): return create_test_report(Form3X, committee, coverage_from, coverage_through, data) -def create_form24(committee, coverage_from, coverage_through, data): - return create_test_report(Form24, committee, coverage_from, coverage_through, data) +def create_form24(committee, data={}): + return create_test_report(Form24, committee, None, None, data) def create_test_report( - form, committee, coverage_from=None, coverage_through=None, data=None + form, committee, coverage_from=None, coverage_through=None, data={} ): form_object = create_form(form, data) report = Report.objects.create( diff --git a/django-backend/fecfiler/web_services/test_views.py b/django-backend/fecfiler/web_services/test_views.py index 2207ee7682..661bcbddc0 100644 --- a/django-backend/fecfiler/web_services/test_views.py +++ b/django-backend/fecfiler/web_services/test_views.py @@ -5,7 +5,7 @@ from fecfiler.user.models import User from fecfiler.committee_accounts.models import CommitteeAccount from fecfiler.committee_accounts.views import create_committee_view -from fecfiler.reports.tests.utils import create_form3x +from fecfiler.reports.tests.utils import create_form3x, create_form24 from fecfiler.web_services.summary.tasks import CalculationState from unittest.mock import patch @@ -55,6 +55,18 @@ def test_create_dot_fec(self): # assert that summary was caclulated self.assertEqual(report.form_3x.L8_cash_on_hand_at_close_period, 1) + def test_create_dot_fec_form_24(self): + report = create_form24(self.committee) + request = self.factory.post( + "/api/v1/web-services/dot-fec/", {"report_id": report.id} + ) + force_authenticate(request, self.user) + request.session = {"committee_uuid": self.committee.id} + + response = WebServicesViewSet.as_view({"post": "create_dot_fec"})(request) + self.assertEqual(response.status_code, 200) + report.refresh_from_db() + def test_submit_to_webprint(self): report = create_form3x( self.committee, "2024-01-01", "2024-02-01", {"L6a_cash_on_hand_jan_1_ytd": 1} diff --git a/django-backend/fecfiler/web_services/views.py b/django-backend/fecfiler/web_services/views.py index f68856a42c..ed06226483 100644 --- a/django-backend/fecfiler/web_services/views.py +++ b/django-backend/fecfiler/web_services/views.py @@ -15,7 +15,7 @@ from .renderers import DotFECRenderer from .web_service_storage import get_file from .models import DotFEC, UploadSubmission, WebPrintSubmission -from fecfiler.reports.models import Report +from fecfiler.reports.models import Report, FORMS_TO_CALCULATE from celery.result import AsyncResult import structlog @@ -176,6 +176,6 @@ def get_calculation_task(self, request, report_id): report = Report.objects.filter( id=report_id, committee_account_id=committee_uuid ).first() - if report.calculation_status != CalculationState.SUCCEEDED.value: + if report.get_form_name() in FORMS_TO_CALCULATE and report.calculation_status != CalculationState.SUCCEEDED.value: return calculate_summary.s(report_id) return None From 00ddf9205ab42741efc0c9512dfb6bc270bdf1c4 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 11:10:45 -0400 Subject: [PATCH 18/30] Adds unit tests for creating dot fec's for Forms 99 and 1m --- .../fecfiler/reports/tests/utils.py | 8 +++++- .../fecfiler/web_services/test_views.py | 26 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/django-backend/fecfiler/reports/tests/utils.py b/django-backend/fecfiler/reports/tests/utils.py index 74573eafaa..f08a9e1f53 100644 --- a/django-backend/fecfiler/reports/tests/utils.py +++ b/django-backend/fecfiler/reports/tests/utils.py @@ -11,7 +11,13 @@ def create_form3x(committee, coverage_from, coverage_through, data={}): def create_form24(committee, data={}): - return create_test_report(Form24, committee, None, None, data) + return create_test_report(Form24, committee, data=data) + +def create_form99(committee, data={}): + return create_test_report(Form99, committee, data=data) + +def create_form1m(committee, data={}): + return create_test_report(Form1M, committee, data=data) def create_test_report( diff --git a/django-backend/fecfiler/web_services/test_views.py b/django-backend/fecfiler/web_services/test_views.py index 661bcbddc0..b499b5658f 100644 --- a/django-backend/fecfiler/web_services/test_views.py +++ b/django-backend/fecfiler/web_services/test_views.py @@ -5,7 +5,7 @@ from fecfiler.user.models import User from fecfiler.committee_accounts.models import CommitteeAccount from fecfiler.committee_accounts.views import create_committee_view -from fecfiler.reports.tests.utils import create_form3x, create_form24 +from fecfiler.reports.tests.utils import create_form3x, create_form24, create_form99, create_form1m from fecfiler.web_services.summary.tasks import CalculationState from unittest.mock import patch @@ -67,6 +67,30 @@ def test_create_dot_fec_form_24(self): self.assertEqual(response.status_code, 200) report.refresh_from_db() + def test_create_dot_fec_form_99(self): + report = create_form99(self.committee) + request = self.factory.post( + "/api/v1/web-services/dot-fec/", {"report_id": report.id} + ) + force_authenticate(request, self.user) + request.session = {"committee_uuid": self.committee.id} + + response = WebServicesViewSet.as_view({"post": "create_dot_fec"})(request) + self.assertEqual(response.status_code, 200) + report.refresh_from_db() + + def test_create_dot_fec_form_1m(self): + report = create_form1m(self.committee) + request = self.factory.post( + "/api/v1/web-services/dot-fec/", {"report_id": report.id} + ) + force_authenticate(request, self.user) + request.session = {"committee_uuid": self.committee.id} + + response = WebServicesViewSet.as_view({"post": "create_dot_fec"})(request) + self.assertEqual(response.status_code, 200) + report.refresh_from_db() + def test_submit_to_webprint(self): report = create_form3x( self.committee, "2024-01-01", "2024-02-01", {"L6a_cash_on_hand_jan_1_ytd": 1} From 71c5461e0bcc94bfa841880536ba7301ff8fb3f0 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 11:20:25 -0400 Subject: [PATCH 19/30] Linting pass --- django-backend/fecfiler/web_services/test_views.py | 4 +++- django-backend/fecfiler/web_services/views.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/django-backend/fecfiler/web_services/test_views.py b/django-backend/fecfiler/web_services/test_views.py index b499b5658f..ef24281d27 100644 --- a/django-backend/fecfiler/web_services/test_views.py +++ b/django-backend/fecfiler/web_services/test_views.py @@ -5,7 +5,9 @@ from fecfiler.user.models import User from fecfiler.committee_accounts.models import CommitteeAccount from fecfiler.committee_accounts.views import create_committee_view -from fecfiler.reports.tests.utils import create_form3x, create_form24, create_form99, create_form1m +from fecfiler.reports.tests.utils import ( + create_form3x, create_form24, create_form99, create_form1m +) from fecfiler.web_services.summary.tasks import CalculationState from unittest.mock import patch diff --git a/django-backend/fecfiler/web_services/views.py b/django-backend/fecfiler/web_services/views.py index ed06226483..bed612de9b 100644 --- a/django-backend/fecfiler/web_services/views.py +++ b/django-backend/fecfiler/web_services/views.py @@ -176,6 +176,9 @@ def get_calculation_task(self, request, report_id): report = Report.objects.filter( id=report_id, committee_account_id=committee_uuid ).first() - if report.get_form_name() in FORMS_TO_CALCULATE and report.calculation_status != CalculationState.SUCCEEDED.value: + if ( + report.get_form_name() in FORMS_TO_CALCULATE + and report.calculation_status != CalculationState.SUCCEEDED.value + ): return calculate_summary.s(report_id) return None From ddfec4d1465e9942ad0d0f63041b19cbc1c612ca Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 11:26:54 -0400 Subject: [PATCH 20/30] More linting --- django-backend/fecfiler/web_services/test_views.py | 2 +- django-backend/fecfiler/web_services/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django-backend/fecfiler/web_services/test_views.py b/django-backend/fecfiler/web_services/test_views.py index ef24281d27..54765d6cb7 100644 --- a/django-backend/fecfiler/web_services/test_views.py +++ b/django-backend/fecfiler/web_services/test_views.py @@ -6,7 +6,7 @@ from fecfiler.committee_accounts.models import CommitteeAccount from fecfiler.committee_accounts.views import create_committee_view from fecfiler.reports.tests.utils import ( - create_form3x, create_form24, create_form99, create_form1m + create_form3x, create_form24, create_form99, create_form1m ) from fecfiler.web_services.summary.tasks import CalculationState diff --git a/django-backend/fecfiler/web_services/views.py b/django-backend/fecfiler/web_services/views.py index bed612de9b..38d0777a3c 100644 --- a/django-backend/fecfiler/web_services/views.py +++ b/django-backend/fecfiler/web_services/views.py @@ -177,7 +177,7 @@ def get_calculation_task(self, request, report_id): id=report_id, committee_account_id=committee_uuid ).first() if ( - report.get_form_name() in FORMS_TO_CALCULATE + report.get_form_name() in FORMS_TO_CALCULATE and report.calculation_status != CalculationState.SUCCEEDED.value ): return calculate_summary.s(report_id) From 10dcecf5031b9588a71375c4dd22a4470e3f8be3 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Mon, 3 Jun 2024 11:31:35 -0400 Subject: [PATCH 21/30] Adds whitespace where necessary --- django-backend/fecfiler/reports/tests/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django-backend/fecfiler/reports/tests/utils.py b/django-backend/fecfiler/reports/tests/utils.py index f08a9e1f53..26dfd6ec47 100644 --- a/django-backend/fecfiler/reports/tests/utils.py +++ b/django-backend/fecfiler/reports/tests/utils.py @@ -13,9 +13,11 @@ def create_form3x(committee, coverage_from, coverage_through, data={}): def create_form24(committee, data={}): return create_test_report(Form24, committee, data=data) + def create_form99(committee, data={}): return create_test_report(Form99, committee, data=data) + def create_form1m(committee, data={}): return create_test_report(Form1M, committee, data=data) From 71c41e42637b16cd9e8fc0c9aae86b8fe851400b Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Tue, 4 Jun 2024 12:03:17 -0400 Subject: [PATCH 22/30] Rename method for clarity --- django-backend/fecfiler/web_services/summary/tasks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/django-backend/fecfiler/web_services/summary/tasks.py b/django-backend/fecfiler/web_services/summary/tasks.py index fb2df5960b..77e9e420fb 100644 --- a/django-backend/fecfiler/web_services/summary/tasks.py +++ b/django-backend/fecfiler/web_services/summary/tasks.py @@ -20,7 +20,7 @@ def __str__(self): return str(self.value) -def get_reports_to_calculate(primary_report): +def get_reports_to_calculate_by_coverage_date(primary_report): if not hasattr(primary_report, "coverage_from_date"): return primary_report @@ -46,7 +46,9 @@ def calculate_summary(report_id): if primary_report.get_form_name() not in FORMS_TO_CALCULATE: return primary_report.id - reports_to_recalculate = get_reports_to_calculate(primary_report) + reports_to_recalculate = get_reports_to_calculate_by_coverage_date( + primary_report + ) calculation_token = uuid.uuid4() reports_to_recalculate.update( calculation_token=calculation_token, From 767df9216c62dd782987f08ec15bb81324bbfc2e Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 4 Jun 2024 12:51:04 -0400 Subject: [PATCH 23/30] 1978 independent expenditure candidate state not saving --- .../0007_schedulee_so_candidate_state.py | 17 +++++++ .../transactions/schedule_e/models.py | 1 + .../fecfiler/transactions/schedule_e/utils.py | 51 +++++++++++-------- 3 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 django-backend/fecfiler/transactions/migrations/0007_schedulee_so_candidate_state.py diff --git a/django-backend/fecfiler/transactions/migrations/0007_schedulee_so_candidate_state.py b/django-backend/fecfiler/transactions/migrations/0007_schedulee_so_candidate_state.py new file mode 100644 index 0000000000..3901f5c8f3 --- /dev/null +++ b/django-backend/fecfiler/transactions/migrations/0007_schedulee_so_candidate_state.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.11 on 2024-06-04 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("transactions", "0006_independent_expenditure_memos_no_aggregation_group"), + ] + + operations = [ + migrations.AddField( + model_name="schedulee", + name="so_candidate_state", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/django-backend/fecfiler/transactions/schedule_e/models.py b/django-backend/fecfiler/transactions/schedule_e/models.py index 36615842ed..f24bdd6a5e 100644 --- a/django-backend/fecfiler/transactions/schedule_e/models.py +++ b/django-backend/fecfiler/transactions/schedule_e/models.py @@ -28,6 +28,7 @@ class ScheduleE(models.Model): completing_suffix = models.TextField(null=True, blank=True) date_signed = models.DateField(null=True, blank=True) memo_text_description = models.TextField(null=True, blank=True) + so_candidate_state = models.TextField(null=True, blank=True) def get_transaction(self): return self.transaction_set.first() diff --git a/django-backend/fecfiler/transactions/schedule_e/utils.py b/django-backend/fecfiler/transactions/schedule_e/utils.py index 1d8043715b..f00dad2d99 100644 --- a/django-backend/fecfiler/transactions/schedule_e/utils.py +++ b/django-backend/fecfiler/transactions/schedule_e/utils.py @@ -1,30 +1,39 @@ def add_schedule_e_contact_fields(instance, representation=None): data = {} if instance.contact_1: - data['payee_organization_name'] = instance.contact_1.name - data['payee_last_name'] = instance.contact_1.last_name - data['payee_first_name'] = instance.contact_1.first_name - data['payee_middle_name'] = instance.contact_1.middle_name - data['payee_prefix'] = instance.contact_1.prefix - data['payee_suffix'] = instance.contact_1.suffix - data['payee_street_1'] = instance.contact_1.street_1 - data['payee_street_2'] = instance.contact_1.street_2 - data['payee_city'] = instance.contact_1.city - data['payee_state'] = instance.contact_1.state - data['payee_zip'] = instance.contact_1.zip + data["payee_organization_name"] = instance.contact_1.name + data["payee_last_name"] = instance.contact_1.last_name + data["payee_first_name"] = instance.contact_1.first_name + data["payee_middle_name"] = instance.contact_1.middle_name + data["payee_prefix"] = instance.contact_1.prefix + data["payee_suffix"] = instance.contact_1.suffix + data["payee_street_1"] = instance.contact_1.street_1 + data["payee_street_2"] = instance.contact_1.street_2 + data["payee_city"] = instance.contact_1.city + data["payee_state"] = instance.contact_1.state + data["payee_zip"] = instance.contact_1.zip if instance.contact_2: - data['so_candidate_id_number'] = instance.contact_2.candidate_id - data['so_candidate_last_name'] = instance.contact_2.last_name - data['so_candidate_first_name'] = instance.contact_2.first_name - data['so_candidate_middle_name'] = instance.contact_2.middle_name - data['so_candidate_prefix'] = instance.contact_2.prefix - data['so_candidate_suffix'] = instance.contact_2.suffix - data['so_candidate_office'] = instance.contact_2.candidate_office - data['so_candidate_district'] = instance.contact_2.candidate_district - data['so_candidate_state'] = instance.contact_2.candidate_state + data["so_candidate_id_number"] = instance.contact_2.candidate_id + data["so_candidate_last_name"] = instance.contact_2.last_name + data["so_candidate_first_name"] = instance.contact_2.first_name + data["so_candidate_middle_name"] = instance.contact_2.middle_name + data["so_candidate_prefix"] = instance.contact_2.prefix + data["so_candidate_suffix"] = instance.contact_2.suffix + data["so_candidate_office"] = instance.contact_2.candidate_office + data["so_candidate_district"] = instance.contact_2.candidate_district + # We do not update contact candidate state if presidential primary, + # instead, we update/pull from the state from the transaction object + data["so_candidate_state"] = ( + instance.schedule_e.so_candidate_state + if instance.schedule_e + and instance.schedule_e.election_code + and instance.schedule_e.election_code.startswith("P") + and instance.contact_2.candidate_office == "P" + else instance.contact_2.candidate_state + ) if representation: representation.update(data) else: - for (k, v) in data.items(): + for k, v in data.items(): setattr(instance, k, v) From 920fb2edaacdbd93766eac8446716bb4171818e5 Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Tue, 4 Jun 2024 13:34:32 -0400 Subject: [PATCH 24/30] 1978 added presidential primary test --- .../schedule_e/tests/test_utils.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/django-backend/fecfiler/transactions/schedule_e/tests/test_utils.py b/django-backend/fecfiler/transactions/schedule_e/tests/test_utils.py index 6283d5f40c..84d17a58bb 100644 --- a/django-backend/fecfiler/transactions/schedule_e/tests/test_utils.py +++ b/django-backend/fecfiler/transactions/schedule_e/tests/test_utils.py @@ -1,22 +1,39 @@ from django.test import TestCase from fecfiler.transactions.schedule_e.utils import add_schedule_e_contact_fields from fecfiler.transactions.tests.test_utils import get_test_transaction +from fecfiler.transactions.schedule_e.models import ScheduleE class ScheduleEUtilsTestCase(TestCase): def test_contacts_to_representation(self): - instance = get_test_transaction('INDEPENDENT_EXPENDITURE') + instance = get_test_transaction("INDEPENDENT_EXPENDITURE") - representation = dict( - transaction_type_identifier='INDEPENDENT_EXPENDITURE' + representation = dict(transaction_type_identifier="INDEPENDENT_EXPENDITURE") + + add_schedule_e_contact_fields(instance, representation) + + self.assertEquals( + representation["transaction_type_identifier"], "INDEPENDENT_EXPENDITURE" ) + self.assertEquals(representation["payee_last_name"], "1 last name") + self.assertEquals(representation["so_candidate_last_name"], "2 last name") + self.assertEquals(representation["so_candidate_state"], "2 candidate state") + + def test_contacts_to_representation_primary_presidential(self): + + instance = get_test_transaction("INDEPENDENT_EXPENDITURE") + instance.schedule_e = ScheduleE(election_code="P2024", so_candidate_state="CA") + instance.contact_2.candidate_office = "P" + + representation = dict(transaction_type_identifier="INDEPENDENT_EXPENDITURE") add_schedule_e_contact_fields(instance, representation) self.assertEquals( - representation['transaction_type_identifier'], 'INDEPENDENT_EXPENDITURE' + representation["transaction_type_identifier"], "INDEPENDENT_EXPENDITURE" ) - self.assertEquals(representation['payee_last_name'], '1 last name') - self.assertEquals(representation['so_candidate_last_name'], '2 last name') + self.assertEquals(representation["payee_last_name"], "1 last name") + self.assertEquals(representation["so_candidate_last_name"], "2 last name") + self.assertEquals(representation["so_candidate_state"], "CA") From b87f93dd9bbc7a3d21b871c20efaa69e235bf2cd Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Tue, 4 Jun 2024 14:56:25 -0400 Subject: [PATCH 25/30] Update README.md Added reference to Silk installation directions. --- locust-testing/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/locust-testing/README.md b/locust-testing/README.md index 51f9628c14..9d342c1888 100644 --- a/locust-testing/README.md +++ b/locust-testing/README.md @@ -101,4 +101,11 @@ the duration of the testing session. There are (as of writing) four tasks: - Celery Test - Load Contacts - Load Reports -- Load Transactions \ No newline at end of file +- Load Transactions + + +# Silk Profiling + +In addition to load testing, Silk query profiling can be installed to inspect queries and response times. + +Installation instructions for local development can be found [here](https://github.com/jazzband/django-silk?tab=readme-ov-file#installation). From 79a89ed1119b0bd4af71d5c0c273624492277604 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Thu, 6 Jun 2024 15:30:04 -0400 Subject: [PATCH 26/30] Only save the candidate state value to the schedule e table if it is a presidential candidate in a primary election. --- .../fecfiler/transactions/schedule_e/serializers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/django-backend/fecfiler/transactions/schedule_e/serializers.py b/django-backend/fecfiler/transactions/schedule_e/serializers.py index d680fb375f..049cabd697 100644 --- a/django-backend/fecfiler/transactions/schedule_e/serializers.py +++ b/django-backend/fecfiler/transactions/schedule_e/serializers.py @@ -56,6 +56,18 @@ def create(self, validated_data): model_data = get_model_data(validated_data, ScheduleE) return ScheduleE.objects.create(**model_data) + def to_internal_value(self, data): + # We only save the candidate state in the schedule e table for + # presidential candidates that are in a primary election. + # Otherwise, the candidate state is located in the contact_2 record. + validated_data = super().to_internal_value(data) + if not ( + validated_data.get('election_code').startswith('P') + and validated_data.get('so_candidate_office') == 'P' + ): + validated_data['so_candidate_state'] = None + return validated_data + class Meta: fields = [ f.name From 5761b76473a6cbe12d05dbae757b24dccc574cb1 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Thu, 6 Jun 2024 16:31:16 -0400 Subject: [PATCH 27/30] Update report code label to be entirely on backend --- .../fecfiler/reports/form_3x/views.py | 7 +++- .../fecfiler/reports/report_code_label.py | 38 +++++++++++++++++++ django-backend/fecfiler/reports/views.py | 31 +-------------- .../fecfiler/transactions/managers.py | 38 +------------------ 4 files changed, 48 insertions(+), 66 deletions(-) create mode 100644 django-backend/fecfiler/reports/report_code_label.py diff --git a/django-backend/fecfiler/reports/form_3x/views.py b/django-backend/fecfiler/reports/form_3x/views.py index 01dbe87713..e0e3127fe4 100644 --- a/django-backend/fecfiler/reports/form_3x/views.py +++ b/django-backend/fecfiler/reports/form_3x/views.py @@ -7,6 +7,7 @@ from .serializers import Form3XSerializer import structlog from rest_framework.response import Response +from fecfiler.reports.report_code_label import report_code_label_mapping logger = structlog.get_logger(__name__) @@ -36,9 +37,13 @@ def coverage_dates(self, request): ) return JsonResponse(data, safe=False) + @action(detail=False) + def report_code_map(self, request): + return JsonResponse(report_code_label_mapping, safe=False) + @action(detail=False, methods=["get"], url_path=r"future") def future_form3x_reports(self, request): - json_date_string = request.GET.get('after', '') + json_date_string = request.GET.get("after", "") data = list( self.get_queryset().filter(coverage_through_date__gt=json_date_string) ) diff --git a/django-backend/fecfiler/reports/report_code_label.py b/django-backend/fecfiler/reports/report_code_label.py new file mode 100644 index 0000000000..ac06dfb2fd --- /dev/null +++ b/django-backend/fecfiler/reports/report_code_label.py @@ -0,0 +1,38 @@ +from django.db.models import Case, When, Value + +report_code_label_mapping = { + "Q1": "APRIL 15 QUARTERLY REPORT (Q1)", + "Q2": "JULY 15 QUARTERLY REPORT (Q2)", + "Q3": "OCTOBER 15 QUARTERLY REPORT (Q3)", + "YE": "JANUARY 31 YEAR-END (YE)", + "TER": "TERMINATION REPORT (TER)", + "MY": "JULY 31 MID-YEAR REPORT (MY)", + "12G": "12-DAY PRE-GENERAL (12G)", + "12P": "12-DAY PRE-PRIMARY (12P)", + "12R": "12-DAY PRE-RUNOFF (12R)", + "12S": "12-DAY PRE-SPECIAL (12S)", + "12C": "12-DAY PRE-CONVENTION (12C)", + "30G": "30-DAY POST-GENERAL (30G)", + "30R": "30-DAY POST-RUNOFF (30R)", + "30S": "30-DAY POST-SPECIAL (30S)", + "M2": "FEBRUARY 20 MONTHLY REPORT (M2)", + "M3": "MARCH 20 MONTHLY REPORT (M3)", + "M4": "APRIL 20 MONTHLY REPORT (M4)", + "M5": "MAY 20 MONTHLY REPORT (M5)", + "M6": "JUNE 20 MONTHLY REPORT (M6)", + "M7": "JULY 20 MONTHLY REPORT (M7)", + "M8": "AUGUST 20 MONTHLY REPORT (M8)", + "M9": "SEPTEMBER 20 MONTHLY REPORT (M9)", + "M10": "OCTOBER 20 MONTHLY REPORT (M10)", + "M11": "NOVEMBER 20 MONTHLY REPORT (M11)", + "M12": "DECEMBER 20 MONTHLY REPORT (M12)", +} + +# Generate the Case object +report_code_label_case = Case( + *[When(report_code=k, then=Value(v)) for k, v in report_code_label_mapping.items()], + When(form_24__report_type_24_48=24, then=Value("24 HOUR")), + When(form_24__report_type_24_48=48, then=Value("48 HOUR")), + When(form_99__isnull=False, then=Value("")), + When(form_1m__isnull=False, then=Value("")), +) diff --git a/django-backend/fecfiler/reports/views.py b/django-backend/fecfiler/reports/views.py index 547a45956c..f0968f4509 100644 --- a/django-backend/fecfiler/reports/views.py +++ b/django-backend/fecfiler/reports/views.py @@ -4,6 +4,7 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet from fecfiler.committee_accounts.views import CommitteeOwnedViewMixin from .models import Report +from .report_code_label import report_code_label_case from fecfiler.transactions.models import Transaction from fecfiler.memo_text.models import MemoText from fecfiler.web_services.models import DotFEC, UploadSubmission, WebPrintSubmission @@ -14,34 +15,6 @@ logger = structlog.get_logger(__name__) -report_code_label_mapping = Case( - When(report_code="Q1", then=Value("APRIL 15 (Q1)")), - When(report_code="Q2", then=Value("JULY 15 (Q2)")), - When(report_code="Q3", then=Value("OCTOBER 15 (Q3)")), - When(report_code="YE", then=Value("JANUARY 31 (YE)")), - When(report_code="TER", then=Value("TERMINATION (TER)")), - When(report_code="MY", then=Value("JULY 31 (MY)")), - When(report_code="12G", then=Value("GENERAL (12G)")), - When(report_code="12P", then=Value("PRIMARY (12P)")), - When(report_code="12R", then=Value("RUNOFF (12R)")), - When(report_code="12S", then=Value("SPECIAL (12S)")), - When(report_code="12C", then=Value("CONVENTION (12C)")), - When(report_code="30G", then=Value("GENERAL (30G)")), - When(report_code="30R", then=Value("RUNOFF (30R)")), - When(report_code="30S", then=Value("SPECIAL (30S)")), - When(report_code="M2", then=Value("FEBRUARY 20 (M2)")), - When(report_code="M3", then=Value("MARCH 30 (M3)")), - When(report_code="M4", then=Value("APRIL 20 (M4)")), - When(report_code="M5", then=Value("MAY 20 (M5)")), - When(report_code="M6", then=Value("JUNE 20 (M6)")), - When(report_code="M7", then=Value("JULY 20 (M7)")), - When(report_code="M8", then=Value("AUGUST 20 (M8)")), - When(report_code="M9", then=Value("SEPTEMBER 20 (M9)")), - When(report_code="M10", then=Value("OCTOBER 20 (M10)")), - When(report_code="M11", then=Value("NOVEMBER 20 (M11)")), - When(report_code="M12", then=Value("DECEMBER 20 (M12)")), -) - version_labels = { "F3XN": "Original", @@ -73,7 +46,7 @@ class ReportViewSet(CommitteeOwnedViewMixin, ModelViewSet): whens = [When(form_type=k, then=Value(v)) for k, v in version_labels.items()] queryset = ( - Report.objects.annotate(report_code_label=report_code_label_mapping) + Report.objects.annotate(report_code_label=report_code_label_case) # alias fields used by the version_label annotation only. not part of payload .alias( form_type_label=Case( diff --git a/django-backend/fecfiler/transactions/managers.py b/django-backend/fecfiler/transactions/managers.py index bd8002f8f1..3ed4e95b22 100644 --- a/django-backend/fecfiler/transactions/managers.py +++ b/django-backend/fecfiler/transactions/managers.py @@ -34,40 +34,12 @@ from enum import Enum from .schedule_b.managers import refunds as schedule_b_refunds from ..reports.models import Report +from fecfiler.reports.report_code_label import report_code_label_case """Manager to deterimine fields that are used the same way across transactions, but are called different names""" -report_code_label_mapping = Case( - When(report_code="Q1", then=Value("APRIL 15 QUARTERLY REPORT (Q1)")), - When(report_code="Q2", then=Value("JULY 15 QUARTERLY REPORT (Q2)")), - When(report_code="Q3", then=Value("OCTOBER 15 QUARTERLY REPORT (Q3)")), - When(report_code="YE", then=Value("JANUARY 31 YEAR-END (YE)")), - When(report_code="TER", then=Value("TERMINATION REPORT (TER)")), - When(report_code="MY", then=Value("JULY 31 MID-YEAR REPORT (MY)")), - When(report_code="12G", then=Value("12-DAY PRE-GENERAL (12G)")), - When(report_code="12P", then=Value("12-DAY PRE-PRIMARY (12P)")), - When(report_code="12R", then=Value("12-DAY PRE-RUNOFF (12R)")), - When(report_code="12S", then=Value("12-DAY PRE-SPECIAL (12S)")), - When(report_code="12C", then=Value("12-DAY PRE-CONVENTION (12C)")), - When(report_code="30G", then=Value("30-DAY POST-GENERAL (30G)")), - When(report_code="30R", then=Value("30-DAY POST-RUNOFF (30R)")), - When(report_code="30S", then=Value("30-DAY POST-SPECIAL (30S)")), - When(report_code="M2", then=Value("FEBRUARY 20 MONTHLY REPORT (M2)")), - When(report_code="M3", then=Value("MARCH 20 MONTHLY REPORT (M3)")), - When(report_code="M4", then=Value("APRIL 20 MONTHLY REPORT (M4)")), - When(report_code="M5", then=Value("MAY 20 MONTHLY REPORT (M5)")), - When(report_code="M6", then=Value("JUNE 20 MONTHLY REPORT (M6)")), - When(report_code="M7", then=Value("JULY 20 MONTHLY REPORT (M7)")), - When(report_code="M8", then=Value("AUGUST 20 MONTHLY REPORT (M8)")), - When(report_code="M9", then=Value("SEPTEMBER 20 MONTHLY REPORT (M9)")), - When(report_code="M10", then=Value("OCTOBER 20 MONTHLY REPORT (M10)")), - When(report_code="M11", then=Value("NOVEMBER 20 MONTHLY REPORT (M11)")), - When(report_code="M12", then=Value("DECEMBER 20 MONTHLY REPORT (M12)")), -) - - class TransactionManager(SoftDeleteManager): entity_aggregate_window = { "partition_by": [ @@ -333,11 +305,6 @@ def PAYMENT_AMOUNT_CLAUSE(self): # noqa: N802 ) def transaction_view(self): - REPORT_CODE_LABEL_CLAUSE = Subquery( # noqa: N806 - Report.objects.filter(transactions=OuterRef("pk")) - .annotate(report_code_label=report_code_label_mapping) - .values("report_code_label")[:1] - ) return ( super() .get_queryset() @@ -359,7 +326,6 @@ def transaction_view(self): transaction_ptr_id=F("id"), back_reference_tran_id_number=self.BACK_REFERENCE_CLAUSE, back_reference_sched_name=self.BACK_REFERENCE_NAME_CLAUSE, - report_code_label=REPORT_CODE_LABEL_CLAUSE, ) ) @@ -368,7 +334,7 @@ class TransactionViewManager(Manager): def get_queryset(self): REPORT_CODE_LABEL_CLAUSE = Subquery( # noqa: N806 Report.objects.filter(transactions=OuterRef("pk")) - .annotate(report_code_label=report_code_label_mapping) + .annotate(report_code_label=report_code_label_case) .values("report_code_label")[:1] ) From 4e4051ff20c68c1025abb45a70786b7cb12c5cd8 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Mon, 10 Jun 2024 12:31:30 -0400 Subject: [PATCH 28/30] Update transactions to include report_code_label so it can be used by transaction history on the contacts page --- django-backend/fecfiler/reports/report_code_label.py | 9 +++++++++ django-backend/fecfiler/transactions/serializers.py | 3 +++ 2 files changed, 12 insertions(+) diff --git a/django-backend/fecfiler/reports/report_code_label.py b/django-backend/fecfiler/reports/report_code_label.py index ac06dfb2fd..c0845c672b 100644 --- a/django-backend/fecfiler/reports/report_code_label.py +++ b/django-backend/fecfiler/reports/report_code_label.py @@ -1,4 +1,5 @@ from django.db.models import Case, When, Value +from fecfiler.reports.models import Report report_code_label_mapping = { "Q1": "APRIL 15 QUARTERLY REPORT (Q1)", @@ -36,3 +37,11 @@ When(form_99__isnull=False, then=Value("")), When(form_1m__isnull=False, then=Value("")), ) + + +def get_report_code_label(report: Report): + if report.form_3x: + return report_code_label_mapping[report.report_code] + if report.form_24: + return f"{report.form_24.report_type_24_48} HOUR" + return "" diff --git a/django-backend/fecfiler/transactions/serializers.py b/django-backend/fecfiler/transactions/serializers.py index 493ff717ee..c61da96b38 100644 --- a/django-backend/fecfiler/transactions/serializers.py +++ b/django-backend/fecfiler/transactions/serializers.py @@ -29,6 +29,8 @@ from fecfiler.transactions.schedule_c2.utils import add_schedule_c2_contact_fields from fecfiler.transactions.schedule_d.utils import add_schedule_d_contact_fields from fecfiler.transactions.schedule_e.utils import add_schedule_e_contact_fields + +from fecfiler.reports.report_code_label import get_report_code_label import structlog logger = structlog.get_logger(__name__) @@ -266,6 +268,7 @@ def to_representation(self, instance): "coverage_through_date": report.coverage_through_date, "report_code": report.report_code, "report_type": report.report_type, + "report_code_label": get_report_code_label(report), } ) From 6b38931ea7a3aa474d56acee0cb2d9d9b2549c89 Mon Sep 17 00:00:00 2001 From: Matt Travers Date: Mon, 10 Jun 2024 15:12:53 -0400 Subject: [PATCH 29/30] Update requirements.txt Update commit hash for validation repo. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index df92a21953..ccbb22ee7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ django-cors-headers==3.13.0 django-storages==1.13.1 djangorestframework==3.14.0 drf-spectacular==0.24.2 -git+https://github.com/fecgov/fecfile-validate@2b8765a913034f90f1475394f5aea9a2fa349f50#egg=fecfile_validate&subdirectory=fecfile_validate_python +git+https://github.com/fecgov/fecfile-validate@0f8b966b623fbca644aebb2054fe4829eb0e0a93#egg=fecfile_validate&subdirectory=fecfile_validate_python GitPython==3.1.42 github3.py==4.0.1 gunicorn==20.1.0 From a3725522c25db633abdb4efe1ce46237e0902b1e Mon Sep 17 00:00:00 2001 From: David Heitzer Date: Thu, 13 Jun 2024 13:36:03 -0400 Subject: [PATCH 30/30] committee list length hotfix --- django-backend/fecfiler/committee_accounts/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/django-backend/fecfiler/committee_accounts/views.py b/django-backend/fecfiler/committee_accounts/views.py index d413123c11..96ebe6836e 100644 --- a/django-backend/fecfiler/committee_accounts/views.py +++ b/django-backend/fecfiler/committee_accounts/views.py @@ -32,8 +32,13 @@ class CommitteeMemberListPagination(pagination.PageNumberPagination): page_size_query_param = "page_size" +class CommitteePagination(pagination.PageNumberPagination): + page_size = 100 + + class CommitteeViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): serializer_class = CommitteeAccountSerializer + pagination_class = CommitteePagination def get_queryset(self): user = self.request.user