python: Initial documentation.
This commit is contained in:
parent
8eb1854b8b
commit
3751fe870f
4 changed files with 62 additions and 11 deletions
37
python/README.rst
Normal file
37
python/README.rst
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
Python Supporter Database
|
||||||
|
=========================
|
||||||
|
|
||||||
|
This directory contains a Python module and set of scripts to load Supporter payment information into a database, and query that information using consistent business logic.
|
||||||
|
|
||||||
|
As of today it only imports payment information from Ledger. It doesn't know about the supplemental Supporter database with contact information, requests, etc. For that, use the Perl module.
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
You'll need Python 3, Django, and Ledger::
|
||||||
|
|
||||||
|
# apt install python3 python3-django ledger
|
||||||
|
|
||||||
|
Create the database::
|
||||||
|
|
||||||
|
$ ./manage.py migrate
|
||||||
|
|
||||||
|
Load data from Ledger. Depending on how you've configured Ledger, you may need to tell it where to find the books with Supporter payments. You can pass additional arguments to configure how the import is done; run the script with the ``--help`` flag for details. A typical first import looks like::
|
||||||
|
|
||||||
|
$ ./load_ledger.py -- --file /path/to/supporters.ledger
|
||||||
|
|
||||||
|
Importing More Data
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you run ``load_ledger.py`` multiple times, each run will add the imported transactions to the database each time. It doesn't know how to import "new" data only yet.
|
||||||
|
|
||||||
|
If you can specify what "new" data is with Ledger search criteria, you can use those to add new payments to the database. For example, if you have all payments through 2016, and now you want to import payments from 2017, you might run::
|
||||||
|
|
||||||
|
$ ./load_ledger.py -- --begin 2017-01-01
|
||||||
|
|
||||||
|
If you're unsure, you can just remove the ``db.sqlite3`` file, then recreate the database following the steps in the `Getting Started`_ section.
|
||||||
|
|
||||||
|
Reports
|
||||||
|
-------
|
||||||
|
|
||||||
|
Other scripts in this directory generate reports from the payment database. You can run any script with the ``--help`` flag to learn more about what it does and how to run it.
|
6
python/TODO.rst
Normal file
6
python/TODO.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
TODO
|
||||||
|
====
|
||||||
|
|
||||||
|
* Teach ``load_ledger.py`` to automatically find and import new, and only new, payments in the Ledger. One possible strategy: find the newest payment in the database, run Ledger with a default ``--begin`` argument some days before that (30? 60?), then use ``Payment.objects.get_or_create()``.
|
||||||
|
* A few Supporters are "both" annual and monthly; i.e., they're a monthly Supporter who occasionally makes an additional large donation. Right now their lapse date and status are calculated by whatever payment was made most recently, so that can weirdly fluctuate between the time they make the large donation and the time their next monthly payment comes in. Should their lapse date be the greater of "one year out from their extra donation" and "one month out from their last monthly donation?"
|
||||||
|
* Optimize ``status_report.py``. Right now it loads and calculates all data from scratch for each month. Keeping some stuff in memory could probably reduce the runtime noticeably. All the Supporter objects would be a good start; if that's not reasonable, at least all the entity names.
|
|
@ -21,14 +21,18 @@ COLUMNS = collections.OrderedDict([
|
||||||
])
|
])
|
||||||
|
|
||||||
def parse_arguments(arglist):
|
def parse_arguments(arglist):
|
||||||
parser = argparse.ArgumentParser(prog='load_ledger')
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='load_ledger',
|
||||||
|
usage="load_ledger.py [flags ...] [-- ledger_arguments ...]",
|
||||||
|
description="Import payment data from Ledger to the Django database",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--ledger-from-scratch', default=False, action='store_true',
|
'--ledger-from-scratch', default=False, action='store_true',
|
||||||
help="""By default, this script imports all Supporter payments,
|
help="""By default, this script runs Ledger with some default search
|
||||||
and you can narrow the set of payments it imports by providing
|
criteria to find Supporter payments only, and additional search
|
||||||
additional search criteria as arguments. If you set this flag,
|
criteria you specify further limit that set. If you set this flag,
|
||||||
the script will not use any default criteria, and only use the criteria
|
this script will not use any default search criteria, and import
|
||||||
you specify.""")
|
exactly the payments found by your search criteria.""")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--ledger-command', default='ledger', metavar='COMMAND',
|
'--ledger-command', default='ledger', metavar='COMMAND',
|
||||||
help="Name or path of ledger executable")
|
help="Name or path of ledger executable")
|
||||||
|
|
|
@ -19,14 +19,18 @@ def month_date(arg_s):
|
||||||
return Date.from_pydate(datetime.datetime.strptime(arg_s, MONTH_FMT))
|
return Date.from_pydate(datetime.datetime.strptime(arg_s, MONTH_FMT))
|
||||||
|
|
||||||
def parse_arguments(arglist):
|
def parse_arguments(arglist):
|
||||||
parser = argparse.ArgumentParser(prog='status_report')
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='status_report',
|
||||||
|
description="Print a CSV report counting Supporters over time",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--start-month', type=month_date,
|
'--start-month', type=month_date, metavar='YYYY-MM',
|
||||||
default=Payment.objects.order_by('date').first().date,
|
default=Payment.objects.order_by('date').first().date,
|
||||||
help="First month in report, in YYYY-MM format")
|
help="First month in report")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--end-month', default=Date.today(), type=month_date,
|
'--end-month', type=month_date, metavar='YYYY-MM',
|
||||||
help="Last month in report, in YYYY-MM format")
|
default=Date.today(),
|
||||||
|
help="Last month in report")
|
||||||
args = parser.parse_args(arglist)
|
args = parser.parse_args(arglist)
|
||||||
if args.end_month < args.start_month:
|
if args.end_month < args.start_month:
|
||||||
parser.error("End month predates start month")
|
parser.error("End month predates start month")
|
||||||
|
|
Loading…
Reference in a new issue