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. """The balance sheet.
""" """
import csv
from decimal import Decimal from decimal import Decimal
from io import StringIO
import sqlalchemy as sa import sqlalchemy as sa
from flask import url_for, render_template, Response 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, \ from accounting.models import Currency, BaseAccount, Account, Transaction, \
JournalEntry JournalEntry
from accounting.report.period import Period from accounting.report.period import Period
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink from .utils.option_link import OptionLink
from .utils.page_params import PageParams from .utils.page_params import PageParams
from .utils.period_choosers import BalanceSheetPeriodChooser from .utils.period_choosers import BalanceSheetPeriodChooser
@ -275,7 +274,7 @@ class CSVHalfRow:
"""The amount.""" """The amount."""
class CSVRow: class CSVRow(BaseCSVRow):
"""A row in the CSV balance sheet.""" """A row in the CSV balance sheet."""
def __init__(self): def __init__(self):
@ -431,15 +430,7 @@ class BalanceSheet:
""" """
filename: str = "balance-sheet-{currency}-{period}.csv"\ filename: str = "balance-sheet-{currency}-{period}.csv"\
.format(currency=self.__currency.code, period=self.__period.spec) .format(currency=self.__currency.code, period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows() return csv_download(filename, 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
def __get_csv_rows(self) -> list[CSVRow]: def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows. """Composes and returns the CSV rows.

View File

@ -17,10 +17,8 @@
"""The income and expenses log. """The income and expenses log.
""" """
import csv
from datetime import date from datetime import date
from decimal import Decimal from decimal import Decimal
from io import StringIO
import sqlalchemy as sa import sqlalchemy as sa
from flask import url_for, render_template, Response 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.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period from accounting.report.period import Period
from accounting.utils.pagination import Pagination from accounting.utils.pagination import Pagination
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink from .utils.option_link import OptionLink
from .utils.page_params import PageParams from .utils.page_params import PageParams
from .utils.period_choosers import IncomeExpensesPeriodChooser from .utils.period_choosers import IncomeExpensesPeriodChooser
@ -187,7 +186,7 @@ class EntryCollector:
entry.balance = balance entry.balance = balance
class CSVRow: class CSVRow(BaseCSVRow):
"""A row in the CSV income and expenses log.""" """A row in the CSV income and expenses log."""
def __init__(self, txn_date: date | str | None, def __init__(self, txn_date: date | str | None,
@ -395,15 +394,7 @@ class IncomeExpenses:
filename: str = "income-expenses-{currency}-{account}-{period}.csv"\ filename: str = "income-expenses-{currency}-{account}-{period}.csv"\
.format(currency=self.__currency.code, account=self.__account.code, .format(currency=self.__currency.code, account=self.__account.code,
period=self.__period.spec) period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows() return csv_download(filename, 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
def __get_csv_rows(self) -> list[CSVRow]: def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows. """Composes and returns the CSV rows.

View File

@ -17,9 +17,7 @@
"""The income statement. """The income statement.
""" """
import csv
from decimal import Decimal from decimal import Decimal
from io import StringIO
import sqlalchemy as sa import sqlalchemy as sa
from flask import url_for, render_template, Response 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, \ from accounting.models import Currency, BaseAccount, Account, Transaction, \
JournalEntry JournalEntry
from accounting.report.period import Period from accounting.report.period import Period
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink from .utils.option_link import OptionLink
from .utils.page_params import PageParams from .utils.page_params import PageParams
from .utils.period_choosers import IncomeStatementPeriodChooser from .utils.period_choosers import IncomeStatementPeriodChooser
@ -115,7 +114,7 @@ class IncomeStatementSection:
return sum([x.total for x in self.subsections]) return sum([x.total for x in self.subsections])
class CSVRow: class CSVRow(BaseCSVRow):
"""A row in the CSV income statement.""" """A row in the CSV income statement."""
def __init__(self, text: str | None, amount: str | Decimal | None): def __init__(self, text: str | None, amount: str | Decimal | None):
@ -311,15 +310,7 @@ class IncomeStatement:
""" """
filename: str = "income-statement-{currency}-{period}.csv"\ filename: str = "income-statement-{currency}-{period}.csv"\
.format(currency=self.__currency.code, period=self.__period.spec) .format(currency=self.__currency.code, period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows() return csv_download(filename, 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
def __get_csv_rows(self) -> list[CSVRow]: def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows. """Composes and returns the CSV rows.

View File

@ -17,10 +17,8 @@
"""The journal. """The journal.
""" """
import csv
from datetime import date from datetime import date
from decimal import Decimal from decimal import Decimal
from io import StringIO
import sqlalchemy as sa import sqlalchemy as sa
from flask import render_template, Response 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.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period from accounting.report.period import Period
from accounting.utils.pagination import Pagination from accounting.utils.pagination import Pagination
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.page_params import PageParams from .utils.page_params import PageParams
from .utils.period_choosers import JournalPeriodChooser from .utils.period_choosers import JournalPeriodChooser
from .utils.report_chooser import ReportChooser from .utils.report_chooser import ReportChooser
@ -70,7 +69,7 @@ class Entry:
self.amount = entry.amount self.amount = entry.amount
class CSVRow: class CSVRow(BaseCSVRow):
"""A row in the CSV journal.""" """A row in the CSV journal."""
def __init__(self, txn_date: str | date, def __init__(self, txn_date: str | date,
@ -209,15 +208,7 @@ class Journal:
:return: The response of the report for download. :return: The response of the report for download.
""" """
filename: str = f"journal-{self.__period.spec}.csv" filename: str = f"journal-{self.__period.spec}.csv"
rows: list[CSVRow] = self.__get_csv_rows() return csv_download(filename, 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
def __get_csv_rows(self) -> list[CSVRow]: def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows. """Composes and returns the CSV rows.

View File

@ -17,10 +17,8 @@
"""The ledger. """The ledger.
""" """
import csv
from datetime import date from datetime import date
from decimal import Decimal from decimal import Decimal
from io import StringIO
import sqlalchemy as sa import sqlalchemy as sa
from flask import url_for, render_template, Response 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.models import Currency, Account, Transaction, JournalEntry
from accounting.report.period import Period from accounting.report.period import Period
from accounting.utils.pagination import Pagination from accounting.utils.pagination import Pagination
from .utils.csv_export import BaseCSVRow, csv_download
from .utils.option_link import OptionLink from .utils.option_link import OptionLink
from .utils.page_params import PageParams from .utils.page_params import PageParams
from .utils.period_choosers import LedgerPeriodChooser from .utils.period_choosers import LedgerPeriodChooser
@ -180,7 +179,7 @@ class EntryCollector:
entry.balance = balance entry.balance = balance
class CSVRow: class CSVRow(BaseCSVRow):
"""A row in the CSV ledger.""" """A row in the CSV ledger."""
def __init__(self, txn_date: date | str | None, def __init__(self, txn_date: date | str | None,
@ -374,15 +373,7 @@ class Ledger:
filename: str = "ledger-{currency}-{account}-{period}.csv"\ filename: str = "ledger-{currency}-{account}-{period}.csv"\
.format(currency=self.__currency.code, account=self.__account.code, .format(currency=self.__currency.code, account=self.__account.code,
period=self.__period.spec) period=self.__period.spec)
rows: list[CSVRow] = self.__get_csv_rows() return csv_download(filename, 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
def __get_csv_rows(self) -> list[CSVRow]: def __get_csv_rows(self) -> list[CSVRow]:
"""Composes and returns the CSV rows. """Composes and returns the CSV rows.

View File

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