Skip to content

Commit

Permalink
Better invoice export
Browse files Browse the repository at this point in the history
  • Loading branch information
MrNaif2018 committed May 1, 2022
1 parent 97ec029 commit 8ee3f9c
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 17 deletions.
18 changes: 13 additions & 5 deletions api/ext/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@


def merge_keys(k1, k2):
return f"{k1}_{k2}" if k1 and k2 else k1 or k2
return f"{k1}_{k2}" if k1 is not None and k2 is not None else k1 if k1 is not None else k2


def db_to_json(data):
return map(lambda x: DisplayInvoice.from_orm(x).dict(), data)
def process_invoice(invoice, add_payments=False):
if not add_payments:
invoice.pop("payments", None)
return invoice


def get_leaves(item, key=None):
if isinstance(item, dict):
def db_to_json(data, add_payments=False):
return map(lambda x: process_invoice(DisplayInvoice.from_orm(x).dict(), add_payments), data)


def get_leaves(item, key=None): # pragma: no cover
if isinstance(item, list) and key is not None and key != "payments":
return {key: "[" + ",".join(map(str, item)) + "]"}
elif isinstance(item, dict):
leaves = {}
for i in item.keys():
leaves.update(get_leaves(item[i], merge_keys(key, i)))
Expand Down
22 changes: 12 additions & 10 deletions api/views/invoices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from typing import List

from fastapi import APIRouter, Response, Security
from fastapi import APIRouter, HTTPException, Response, Security
from fastapi.responses import StreamingResponse

from api import crud, models, schemes, utils
Expand All @@ -23,18 +21,22 @@ async def get_invoice_by_order_id(order_id: str):
return item


@router.get("/export", response_model=List[schemes.DisplayInvoice])
@router.get("/export")
async def export_invoices(
response: Response,
export_format: str = "json",
add_payments: bool = False,
all_users: bool = False,
user: models.User = Security(utils.authorization.AuthDependency(), scopes=["invoice_management"]),
):
data = (
await models.Invoice.query.where(models.User.id == user.id)
.where(models.Invoice.status == InvoiceStatus.COMPLETE)
.gino.all()
)
if all_users and not user.is_superuser:
raise HTTPException(403, "Not enough permissions")
query = models.Invoice.query.where(models.Invoice.status == InvoiceStatus.COMPLETE)
if not all_users:
query = query.where(models.Invoice.user_id == user.id)
data = await query.gino.all()
await utils.database.postprocess_func(data)
data = list(export_ext.db_to_json(data, add_payments))
now = utils.time.now()
filename = now.strftime(f"bitcartcc-export-%Y%m%d-%H%M%S.{export_format}")
headers = {"Content-Disposition": f"attachment; filename={filename}"}
Expand All @@ -43,7 +45,7 @@ async def export_invoices(
return data
else:
return StreamingResponse(
iter([export_ext.json_to_csv(export_ext.db_to_json(data)).getvalue()]),
iter([export_ext.json_to_csv(data).getvalue()]),
media_type="application/csv",
headers=headers,
)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ext/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ def test_json_to_csv():
converted = json_to_csv([{"test": 1, "list": [1, 2, 3], "obj": {"obj2": {"field": 4}}}])
assert isinstance(converted, io.StringIO)
value = converted.getvalue()
assert value.strip() == "list,list_1,list_2,obj_obj2_field,test\r\n1,2,3,4,1"
assert value.strip() == 'list,obj_obj2_field,test\r\n"[1,2,3]",4,1'
25 changes: 24 additions & 1 deletion tests/test_views/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,18 +545,41 @@ async def test_services(client: TestClient, token: str):
assert resp2.json() == await tor_ext.get_data("services_dict", {}, json_decode=True)


async def test_export_invoices(client: TestClient, token: str):
async def test_export_invoices(client: TestClient, token: str, limited_user):
limited_token = (await create_token(client, limited_user))["access_token"]
invoice = await create_invoice(client, limited_user["id"], limited_token)
await client.post(
"/invoices/batch",
json={"ids": [invoice["id"]], "command": "mark_complete"},
headers={"Authorization": f"Bearer {limited_token}"},
)
assert (await client.get("/invoices/export")).status_code == 401
assert (
await client.get("/invoices/export?all_users=true", headers={"Authorization": f"Bearer {limited_token}"})
).status_code == 403
assert len((await client.get("/invoices/export", headers={"Authorization": f"Bearer {limited_token}"})).json()) > 0
json_resp = await client.get("/invoices/export", headers={"Authorization": f"Bearer {token}"})
assert json_resp.status_code == 200
assert isinstance(json_resp.json(), list)
assert len(json_resp.json()) == 0
assert "bitcartcc-export" in json_resp.headers["content-disposition"]
resp2 = await client.get("/invoices/export?export_format=json", headers={"Authorization": f"Bearer {token}"})
assert resp2.json() == json_resp.json()
csv_resp = await client.get("/invoices/export?export_format=csv", headers={"Authorization": f"Bearer {token}"})
assert csv_resp.status_code == 200
assert "bitcartcc-export" in csv_resp.headers["content-disposition"]
assert csv_resp.text.endswith("\r\n")
json_resp = await client.get("/invoices/export?all_users=true", headers={"Authorization": f"Bearer {token}"})
data = json_resp.json()
assert len(data) == 1
assert data[0]["id"] == invoice["id"]
assert "payments" not in data[0]
assert (
"payments"
in (
await client.get("/invoices/export?all_users=true&add_payments=true", headers={"Authorization": f"Bearer {token}"})
).json()[0]
)


async def test_batch_commands(client: TestClient, token: str, store):
Expand Down

0 comments on commit 8ee3f9c

Please sign in to comment.