From 63286679ad42c7c8acdd7823eacd867c90f1d1aa Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 12 Sep 2023 20:49:18 -0500 Subject: [PATCH] Add cache table, importer for NationBuilder donations --- .../c3cb75afcae2_add_donations_cache_table.py | 81 +++++++++++++++++++ rattail_nationbuilder/db/model/__init__.py | 2 +- .../db/model/nationbuilder.py | 34 ++++++++ rattail_nationbuilder/importing/model.py | 3 + .../importing/nationbuilder.py | 63 +++++++++++++-- rattail_nationbuilder/importing/versions.py | 8 ++ 6 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 rattail_nationbuilder/db/alembic/versions/c3cb75afcae2_add_donations_cache_table.py diff --git a/rattail_nationbuilder/db/alembic/versions/c3cb75afcae2_add_donations_cache_table.py b/rattail_nationbuilder/db/alembic/versions/c3cb75afcae2_add_donations_cache_table.py new file mode 100644 index 0000000..7858c44 --- /dev/null +++ b/rattail_nationbuilder/db/alembic/versions/c3cb75afcae2_add_donations_cache_table.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8; -*- +"""add donations cache table + +Revision ID: c3cb75afcae2 +Revises: 1e17031c4b3e +Create Date: 2023-09-12 19:30:47.583505 + +""" + +# revision identifiers, used by Alembic. +revision = 'c3cb75afcae2' +down_revision = '1e17031c4b3e' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa +import rattail.db.types + + + +def upgrade(): + + # nationbuilder_cache_donation + op.create_table('nationbuilder_cache_donation', + sa.Column('uuid', sa.String(length=32), nullable=False), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('author_id', sa.Integer(), nullable=True), + sa.Column('membership_id', sa.Integer(), nullable=True), + sa.Column('donor_id', sa.Integer(), nullable=True), + sa.Column('donor_external_id', sa.String(length=50), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.Column('amount', sa.Numeric(precision=10, scale=2), nullable=True), + sa.Column('payment_type_name', sa.String(length=100), nullable=True), + sa.Column('check_number', sa.String(length=255), nullable=True), + sa.Column('tracking_code_slug', sa.String(length=255), nullable=True), + sa.Column('note', sa.Text(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('succeeded_at', sa.DateTime(), nullable=True), + sa.Column('failed_at', sa.DateTime(), nullable=True), + sa.Column('canceled_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('uuid'), + sa.UniqueConstraint('id', name='nationbuilder_cache_donation_uq_id') + ) + op.create_table('nationbuilder_cache_donation_version', + sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False), + sa.Column('id', sa.Integer(), autoincrement=False, nullable=False), + sa.Column('author_id', sa.Integer(), nullable=True), + sa.Column('membership_id', sa.Integer(), nullable=True), + sa.Column('donor_id', sa.Integer(), nullable=True), + sa.Column('donor_external_id', sa.String(length=50), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.Column('amount', sa.Numeric(precision=10, scale=2), nullable=True), + sa.Column('payment_type_name', sa.String(length=100), nullable=True), + sa.Column('check_number', sa.String(length=255), nullable=True), + sa.Column('tracking_code_slug', sa.String(length=255), nullable=True), + sa.Column('note', sa.Text(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('succeeded_at', sa.DateTime(), nullable=True), + sa.Column('failed_at', sa.DateTime(), nullable=True), + sa.Column('canceled_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False), + sa.Column('end_transaction_id', sa.BigInteger(), nullable=True), + sa.Column('operation_type', sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint('uuid', 'transaction_id') + ) + op.create_index(op.f('ix_nationbuilder_cache_donation_version_end_transaction_id'), 'nationbuilder_cache_donation_version', ['end_transaction_id'], unique=False) + op.create_index(op.f('ix_nationbuilder_cache_donation_version_operation_type'), 'nationbuilder_cache_donation_version', ['operation_type'], unique=False) + op.create_index(op.f('ix_nationbuilder_cache_donation_version_transaction_id'), 'nationbuilder_cache_donation_version', ['transaction_id'], unique=False) + + +def downgrade(): + + # nationbuilder_cache_donation + op.drop_index(op.f('ix_nationbuilder_cache_donation_version_transaction_id'), table_name='nationbuilder_cache_donation_version') + op.drop_index(op.f('ix_nationbuilder_cache_donation_version_operation_type'), table_name='nationbuilder_cache_donation_version') + op.drop_index(op.f('ix_nationbuilder_cache_donation_version_end_transaction_id'), table_name='nationbuilder_cache_donation_version') + op.drop_table('nationbuilder_cache_donation_version') + op.drop_table('nationbuilder_cache_donation') diff --git a/rattail_nationbuilder/db/model/__init__.py b/rattail_nationbuilder/db/model/__init__.py index 9499a43..dc3f017 100644 --- a/rattail_nationbuilder/db/model/__init__.py +++ b/rattail_nationbuilder/db/model/__init__.py @@ -24,4 +24,4 @@ DB schema for NationBuilder integration """ -from .nationbuilder import NationBuilderCachePerson +from .nationbuilder import NationBuilderCachePerson, NationBuilderCacheDonation diff --git a/rattail_nationbuilder/db/model/nationbuilder.py b/rattail_nationbuilder/db/model/nationbuilder.py index ad9b03f..7de9d29 100644 --- a/rattail_nationbuilder/db/model/nationbuilder.py +++ b/rattail_nationbuilder/db/model/nationbuilder.py @@ -79,3 +79,37 @@ class NationBuilderCachePerson(model.Base): if value == tag: return True return False + + +class NationBuilderCacheDonation(model.Base): + """ + Represents a Donation record in NationBuilder. + + https://apiexplorer.nationbuilder.com/nationbuilder#Donations + """ + __tablename__ = 'nationbuilder_cache_donation' + __table_args__ = ( + sa.UniqueConstraint('id', name='nationbuilder_cache_donation_uq_id'), + ) + __versioned__ = {} + model_title = "NationBuilder Donation" + model_title_plural = "NationBuilder Donations" + + uuid = model.uuid_column() + + id = sa.Column(sa.Integer(), nullable=False) + author_id = sa.Column(sa.Integer(), nullable=True) + membership_id = sa.Column(sa.Integer(), nullable=True) + donor_id = sa.Column(sa.Integer(), nullable=True) + donor_external_id = sa.Column(sa.String(length=50), nullable=True) + email = sa.Column(sa.String(length=255), nullable=True) + amount = sa.Column(sa.Numeric(precision=10, scale=2), nullable=True) + payment_type_name = sa.Column(sa.String(length=100), nullable=True) + check_number = sa.Column(sa.String(length=255), nullable=True) + tracking_code_slug = sa.Column(sa.String(length=255), nullable=True) + note = sa.Column(sa.Text(), nullable=True) + created_at = sa.Column(sa.DateTime(), nullable=True) + succeeded_at = sa.Column(sa.DateTime(), nullable=True) + failed_at = sa.Column(sa.DateTime(), nullable=True) + canceled_at = sa.Column(sa.DateTime(), nullable=True) + updated_at = sa.Column(sa.DateTime(), nullable=True) diff --git a/rattail_nationbuilder/importing/model.py b/rattail_nationbuilder/importing/model.py index c0ac289..ad4c7a9 100644 --- a/rattail_nationbuilder/importing/model.py +++ b/rattail_nationbuilder/importing/model.py @@ -34,3 +34,6 @@ from rattail_nationbuilder.db import model class NationBuilderCachePersonImporter(ToRattail): model_class = model.NationBuilderCachePerson + +class NationBuilderCacheDonationImporter(ToRattail): + model_class = model.NationBuilderCacheDonation diff --git a/rattail_nationbuilder/importing/nationbuilder.py b/rattail_nationbuilder/importing/nationbuilder.py index 2507f86..43cbf9a 100644 --- a/rattail_nationbuilder/importing/nationbuilder.py +++ b/rattail_nationbuilder/importing/nationbuilder.py @@ -25,6 +25,7 @@ NationBuilder -> Rattail importing """ import datetime +import decimal from collections import OrderedDict from rattail import importing @@ -43,6 +44,7 @@ class FromNationBuilderToRattail(importing.ToRattailHandler): def get_importers(self): importers = OrderedDict() importers['NationBuilderCachePerson'] = NationBuilderCachePersonImporter + importers['NationBuilderCacheDonation'] = NationBuilderCacheDonationImporter return importers @@ -58,6 +60,14 @@ class FromNationBuilder(importing.Importer): def setup_api(self): self.nationbuilder = NationBuilderWebAPI(self.config) + def normalize_timestamp(self, value): + if not value: + return + + dt = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S%z') + dt = self.app.localtime(dt) + return self.app.make_utc(dt) + class NationBuilderCachePersonImporter(FromNationBuilder, nationbuilder_importing.model.NationBuilderCachePersonImporter): """ @@ -95,14 +105,6 @@ class NationBuilderCachePersonImporter(FromNationBuilder, nationbuilder_importin def get_host_objects(self): return self.nationbuilder.get_people(page_size=500) - def normalize_timestamp(self, value): - if not value: - return - - dt = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S%z') - dt = self.app.localtime(dt) - return self.app.make_utc(dt) - def normalize_host_object(self, person): # nb. some fields may not be present in person dict @@ -130,3 +132,48 @@ class NationBuilderCachePersonImporter(FromNationBuilder, nationbuilder_importin }) return data + + +class NationBuilderCacheDonationImporter(FromNationBuilder, nationbuilder_importing.model.NationBuilderCacheDonationImporter): + """ + Importer for NB Donation cache + """ + key = 'id' + supported_fields = [ + 'id', + 'author_id', + 'membership_id', + 'donor_id', + 'donor_external_id', + 'email', + 'amount', + 'payment_type_name', + 'check_number', + 'tracking_code_slug', + 'note', + 'created_at', + 'succeeded_at', + 'failed_at', + 'canceled_at', + 'updated_at', + ] + + def get_host_objects(self): + return self.nationbuilder.get_donations(page_size=500) + + def normalize_host_object(self, donation): + + # nb. some fields may not be present in donation dict + data = dict([(field, donation.get(field)) + for field in self.fields]) + if data: + + donor = donation.get('donor') + data['donor_external_id'] = donor.get('external_id') if donor else None + + data['amount'] = decimal.Decimal('{:0.2f}'.format(donation['amount_in_cents'] / 100)) + + for field in ('created_at', 'succeeded_at', 'failed_at', 'canceled_at', 'updated_at'): + data[field] = self.normalize_timestamp(data[field]) + + return data diff --git a/rattail_nationbuilder/importing/versions.py b/rattail_nationbuilder/importing/versions.py index 414c912..ce9a391 100644 --- a/rattail_nationbuilder/importing/versions.py +++ b/rattail_nationbuilder/importing/versions.py @@ -31,6 +31,7 @@ class NationBuilderVersionMixin(object): def add_nationbuilder_importers(self, importers): importers['NationBuilderCachePerson'] = NationBuilderCachePersonImporter + importers['NationBuilderCacheDonation'] = NationBuilderCacheDonationImporter return importers @@ -39,3 +40,10 @@ class NationBuilderCachePersonImporter(base.VersionImporter): @property def host_model_class(self): return self.model.NationBuilderCachePerson + + +class NationBuilderCacheDonationImporter(base.VersionImporter): + + @property + def host_model_class(self): + return self.model.NationBuilderCacheDonation