Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix sched-a by-employer,by-occupation and schedule_b/by_recipient fulltext and n/a search issue #5809

Merged
merged 2 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions tests/test_aggregates.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sqlalchemy as sa
from tests import factories
from tests.common import ApiBaseTest, assert_dicts_subset

Expand Down Expand Up @@ -133,16 +134,15 @@ def test_disbursement_recipient(self):
aggregate = factories.ScheduleBByRecipientFactory(
committee_id=committee.committee_id,
cycle=committee.cycle,
recipient_name='STARBOARD STRATEGIES, INC.',
recipient_name_text=sa.func.to_tsvector('STARBOARD STRATEGIES, INC.'),
recipient_name='STARBOARD STRATEGIES, INC.'
)
results = self._results(
api.url_for(
ScheduleBByRecipientView,
committee_id=committee.committee_id,
cycle=2012,
recipient_name='Starboard Strategies',
)
)
recipient_name='Starboard Strategies',))
self.assertEqual(len(results), 1)
expected = {
'committee_id': committee.committee_id,
Expand Down
10 changes: 6 additions & 4 deletions webservices/common/models/aggregates.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from sqlalchemy.dialects.postgresql import TSVECTOR
from webservices import docs, utils

from .base import db, BaseModel
Expand Down Expand Up @@ -33,15 +34,15 @@ class ScheduleAByZip(BaseAggregate):


class ScheduleAByEmployer(BaseAggregate):
__table_args__ = {'schema': 'disclosure'}
__tablename__ = 'dsc_sched_a_aggregate_employer'
__tablename__ = 'ofec_sched_a_aggregate_employer_mv'
employer = db.Column(db.String, primary_key=True, doc=docs.EMPLOYER)
employer_text = db.Column(TSVECTOR)


class ScheduleAByOccupation(BaseAggregate):
__table_args__ = {'schema': 'disclosure'}
__tablename__ = 'dsc_sched_a_aggregate_occupation'
__tablename__ = 'ofec_sched_a_aggregate_occupation_mv'
occupation = db.Column(db.String, primary_key=True, doc=docs.OCCUPATION)
occupation_text = db.Column(TSVECTOR)


class BaseDisbursementAggregate(BaseAggregate):
Expand All @@ -58,6 +59,7 @@ class ScheduleBByRecipient(BaseDisbursementAggregate):

recipient_name = db.Column('recipient_nm', db.String, primary_key=True, doc=docs.RECIPIENT_NAME)
committee_total_disbursements = db.Column('disbursements', db.Numeric(30, 2), index=True, doc=docs.DISBURSEMENTS)
recipient_name_text = db.Column('recipient_nm_text', TSVECTOR)

@property
def recipient_disbursement_percent(self):
Expand Down
2 changes: 2 additions & 0 deletions webservices/common/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ApiResource(utils.Resource):
filter_multi_fields = []
filter_range_fields = []
filter_fulltext_fields = []
filter_fulltext_fields_NA = []
filter_overlap_fields = []
query_options = []
join_columns = {}
Expand Down Expand Up @@ -53,6 +54,7 @@ def build_query(self, *args, _apply_options=True, **kwargs):
query = filters.filter_multi(query, kwargs, self.filter_multi_fields)
query = filters.filter_range(query, kwargs, self.filter_range_fields)
query = filters.filter_fulltext(query, kwargs, self.filter_fulltext_fields)
query = filters.filter_fulltext_NA(query, kwargs, self.filter_fulltext_fields_NA)
query = filters.filter_overlap(query, kwargs, self.filter_overlap_fields)
if _apply_options:
query = query.options(*self.query_options)
Expand Down
27 changes: 27 additions & 0 deletions webservices/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,33 @@
return query


# Support N/A search, 'column' is tsvector datatype, 'original_column' is varchar datatype.
def filter_fulltext_NA(query, kwargs, fields):
for key, column, original_column in fields:
if kwargs.get(key):
exclude_list = build_exclude_list(kwargs.get(key))
include_list = build_include_list(kwargs.get(key))
if exclude_list:
filters = []
for value in exclude_list:
if value.strip().upper() == "N/A":
query = query.filter(original_column != 'N/A')

Check warning on line 113 in webservices/filters.py

View check run for this annotation

Codecov / codecov/patch

webservices/filters.py#L110-L113

Added lines #L110 - L113 were not covered by tests
else:
filters.append(sa.not_(column.match(utils.parse_fulltext(value))))
query = query.filter(sa.and_(*filters))

Check warning on line 116 in webservices/filters.py

View check run for this annotation

Codecov / codecov/patch

webservices/filters.py#L115-L116

Added lines #L115 - L116 were not covered by tests
if include_list:
filters = []
for value in include_list:
if value.strip().upper() == "N/A":
query = query.filter(original_column == 'N/A')

Check warning on line 121 in webservices/filters.py

View check run for this annotation

Codecov / codecov/patch

webservices/filters.py#L121

Added line #L121 was not covered by tests
else:
filters.append(column.match(utils.parse_fulltext(value)))
if value.upper() == 'NULL':
filters.append(column.is_(None))

Check warning on line 125 in webservices/filters.py

View check run for this annotation

Codecov / codecov/patch

webservices/filters.py#L125

Added line #L125 was not covered by tests
query = query.filter(sa.or_(*filters))
return query


def filter_multi_start_with(query, kwargs, fields):
for key, column in fields:
if kwargs.get(key):
Expand Down
16 changes: 9 additions & 7 deletions webservices/resources/aggregates.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ class ScheduleAByEmployerView(AggregateResource):
('cycle', models.ScheduleAByEmployer.cycle),
('committee_id', models.ScheduleAByEmployer.committee_id),
]
filter_fulltext_fields = [
('employer', models.ScheduleAByEmployer.employer),

filter_fulltext_fields_NA = [
('employer', models.ScheduleAByEmployer.employer_text, models.ScheduleAByEmployer.employer),
]


Expand All @@ -76,8 +77,8 @@ class ScheduleAByOccupationView(AggregateResource):
('cycle', models.ScheduleAByOccupation.cycle),
('committee_id', models.ScheduleAByOccupation.committee_id),
]
filter_fulltext_fields = [
('occupation', models.ScheduleAByOccupation.occupation),
filter_fulltext_fields_NA = [
('occupation', models.ScheduleAByOccupation.occupation_text, models.ScheduleAByOccupation.occupation),
]


Expand Down Expand Up @@ -179,7 +180,8 @@ class ScheduleBByPurposeView(AggregateResource):

# used for endpoint:`/schedules/schedule_b/by_recipient/`
# under tag: disbursements
# Ex: http://127.0.0.1:5000/v1/schedules/schedule_b/by_recipient/
# Ex: http://127.0.0.1:5000/v1/schedules/schedule_b/by_recipient/?cycle=2022
# &committee_id=C00160937&recipient_name=zoom.com
@doc(
tags=['disbursements'],
description=docs.SCHEDULE_B_BY_RECIPIENT,
Expand All @@ -194,8 +196,8 @@ class ScheduleBByRecipientView(AggregateResource):
('cycle', models.ScheduleBByRecipient.cycle),
('committee_id', models.ScheduleBByRecipient.committee_id),
]
filter_fulltext_fields = [
('recipient_name', models.ScheduleBByRecipient.recipient_name),
filter_fulltext_fields_NA = [
('recipient_name', models.ScheduleBByRecipient.recipient_name_text, models.ScheduleBByRecipient.recipient_name),
]


Expand Down
29 changes: 26 additions & 3 deletions webservices/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -1060,9 +1060,10 @@ class CandidateHistoryTotalSchema(schemas['CandidateHistorySchema'],
'recipient_disbursement_percent': ma.fields.Float(),
'committee_total_disbursements': ma.fields.Float(),
'total': ma.fields.Float(),
'memo_total': ma.fields.Float()
'memo_total': ma.fields.Float(),
'recipient_name': ma.fields.Str(),
},
options={'exclude': ('idx', 'committee')}
options={'exclude': ('idx', 'committee', 'recipient_name_text')}
)

ScheduleBByRecipientPageSchema = make_page_schema(ScheduleBByRecipientSchema, page_type=paging_schemas.SeekPageSchema)
Expand All @@ -1075,9 +1076,31 @@ class CandidateHistoryTotalSchema(schemas['CandidateHistorySchema'],
models.ScheduleAByZip,
models.ScheduleABySize,
models.ScheduleAByState,
models.ScheduleBByPurpose,
)

ScheduleAByEmployerSchema = make_schema(
models.ScheduleAByEmployer,
fields={
'committee': ma.fields.Nested(schemas['CommitteeHistorySchema']),
'employer': ma.fields.Str(),
},
options={'exclude': ('idx', 'committee', 'employer_text',)}
)
ScheduleAByEmployerPageSchema = make_page_schema(
ScheduleAByEmployerSchema
)

ScheduleAByOccupationSchema = make_schema(
models.ScheduleAByOccupation,
models.ScheduleBByPurpose,
fields={
'committee': ma.fields.Nested(schemas['CommitteeHistorySchema']),
'occupation': ma.fields.Str(),
},
options={'exclude': ('idx', 'committee', 'occupation_text',)}
)
ScheduleAByOccupationPageSchema = make_page_schema(
ScheduleAByOccupationSchema
)

make_aggregate_schema = functools.partial(
Expand Down