Added the "accounting.report.reports.csv_export" module to handle the CSV export in one place.

This commit is contained in:
依瑪貓 2023-03-07 22:45:29 +08:00
parent f838e7f893
commit 9f1e724875
7 changed files with 72 additions and 72 deletions

View File

@ -17,9 +17,7 @@
"""The balance sheet.
"""
import csv
from decimal import Decimal
from io import StringIO
import sqlalchemy as sa
from flask import url_for, render_template, Response
@ -29,6 +27,7 @@ from accounting.locale import gettext
from accounting.models import Currency, BaseAccount, Account, Transaction, \
JournalEntry
from accounting.report.period import Period
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import BalanceSheetPeriodChooser
@ -275,7 +274,7 @@ class CSVHalfRow:
"""The amount."""
class CSVRow:
class CSVRow(BaseCSVRow):
"""A row in the CSV balance sheet."""
def __init__(self):
@ -431,15 +430,7 @@ class BalanceSheet:
"""
filename: str = "balance-sheet-{currency}-{period}.csv"\
.format(currency=self.__currency.code, period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows()
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response
return csv_download(filename, self.__get_csv_rows())
def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows.

View File

@ -17,10 +17,8 @@
"""The income and expenses log.
"""
import csv
from datetime import date
from decimal import Decimal
from io import StringIO
import sqlalchemy as sa
from flask import url_for, render_template, Response
@ -30,6 +28,7 @@ from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period
from accounting.utils.pagination import Pagination
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import IncomeExpensesPeriodChooser
@ -187,7 +186,7 @@ class EntryCollector:
entry.balance = balance
class CSVRow:
class CSVRow(BaseCSVRow):
"""A row in the CSV income and expenses log."""
def __init__(self, txn_date: date | str | None,
@ -395,15 +394,7 @@ class IncomeExpenses:
filename: str = "income-expenses-{currency}-{account}-{period}.csv"\
.format(currency=self.__currency.code, account=self.__account.code,
period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows()
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response
return csv_download(filename, self.__get_csv_rows())
def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows.

View File

@ -17,9 +17,7 @@
"""The income statement.
"""
import csv
from decimal import Decimal
from io import StringIO
import sqlalchemy as sa
from flask import url_for, render_template, Response
@ -29,6 +27,7 @@ from accounting.locale import gettext
from accounting.models import Currency, BaseAccount, Account, Transaction, \
JournalEntry
from accounting.report.period import Period
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import IncomeStatementPeriodChooser
@ -115,7 +114,7 @@ class IncomeStatementSection:
return sum([x.total for x in self.subsections])
class CSVRow:
class CSVRow(BaseCSVRow):
"""A row in the CSV income statement."""
def __init__(self, text: str | None, amount: str | Decimal | None):
@ -311,15 +310,7 @@ class IncomeStatement:
"""
filename: str = "income-statement-{currency}-{period}.csv"\
.format(currency=self.__currency.code, period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows()
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response
return csv_download(filename, self.__get_csv_rows())
def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows.

View File

@ -17,10 +17,8 @@
"""The journal.
"""
import csv
from datetime import date
from decimal import Decimal
from io import StringIO
import sqlalchemy as sa
from flask import render_template, Response
@ -30,6 +28,7 @@ from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period
from accounting.utils.pagination import Pagination
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.page_params import PageParams
from .utils.period_choosers import JournalPeriodChooser
from .utils.report_chooser import ReportChooser
@ -70,7 +69,7 @@ class Entry:
self.amount = entry.amount
class CSVRow:
class CSVRow(BaseCSVRow):
"""A row in the CSV journal."""
def __init__(self, txn_date: str | date,
@ -209,15 +208,7 @@ class Journal:
:return: The response of the report for download.
"""
filename: str = f"journal-{self.__period.spec}.csv"
rows: list[CSVRow] = self.__get_csv_rows()
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response
return csv_download(filename, self.__get_csv_rows())
def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows.

View File

@ -17,10 +17,8 @@
"""The ledger.
"""
import csv
from datetime import date
from decimal import Decimal
from io import StringIO
import sqlalchemy as sa
from flask import url_for, render_template, Response
@ -30,6 +28,7 @@ from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period
from accounting.utils.pagination import Pagination
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import LedgerPeriodChooser
@ -180,7 +179,7 @@ class EntryCollector:
entry.balance = balance
class CSVRow:
class CSVRow(BaseCSVRow):
"""A row in the CSV ledger."""
def __init__(self, txn_date: date | str | None,
@ -374,15 +373,7 @@ class Ledger:
filename: str = "ledger-{currency}-{account}-{period}.csv"\
.format(currency=self.__currency.code, account=self.__account.code,
period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows()
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response
return csv_download(filename, self.__get_csv_rows())
def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows.

View File

@ -17,9 +17,7 @@
"""The trial balance.
"""
import csv
from decimal import Decimal
from io import StringIO
import sqlalchemy as sa
from flask import url_for, Response, render_template
@ -28,6 +26,7 @@ from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import TrialBalancePeriodChooser
@ -70,7 +69,7 @@ class TrialBalanceTotal:
"""The credit amount."""
class CSVRow:
class CSVRow(BaseCSVRow):
"""A row in the CSV trial balance."""
def __init__(self, text: str | None,
@ -235,15 +234,7 @@ class TrialBalance:
"""
filename: str = "trial-balance-{currency}-{period}.csv"\
.format(currency=self.__currency.code, period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows()
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response
return csv_download(filename, self.__get_csv_rows())
def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows.

View File

@ -0,0 +1,54 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The utility to export the report as CSV for download.
"""
import csv
from abc import ABC, abstractmethod
from decimal import Decimal
from io import StringIO
from flask import Response
class BaseCSVRow(ABC):
"""The base CSV row."""
@property
@abstractmethod
def values(self) -> list[str | Decimal | None]:
"""Returns the values of the row.
:return: The values of the row.
"""
def csv_download(filename: str, rows: list[BaseCSVRow]) -> Response:
"""Exports the data rows as a CSV file for download.
:param filename: The download file name.
:param rows: The data rows.
:return: The response for download the CSV file.
"""
with StringIO() as fp:
writer = csv.writer(fp)
writer.writerows([x.values for x in rows])
fp.seek(0)
response: Response = Response(fp.read(), mimetype="text/csv")
response.headers["Content-Disposition"] \
= f"attachment; filename={filename}"
return response