diff --git a/tildes/scss/modules/_table.scss b/tildes/scss/modules/_table.scss new file mode 100644 index 0000000..2304d83 --- /dev/null +++ b/tildes/scss/modules/_table.scss @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Tildes contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +.table-financials { + max-width: $paragraph-max-width; + margin-top: 1rem; + margin-bottom: 2rem; + + .tr-summary { + font-weight: bold; + } +} diff --git a/tildes/scss/styles.scss b/tildes/scss/styles.scss index 3d3b6f4..19f1c75 100644 --- a/tildes/scss/styles.scss +++ b/tildes/scss/styles.scss @@ -35,6 +35,7 @@ @import "modules/site-header"; @import "modules/static-site"; @import "modules/tab"; +@import "modules/table"; @import "modules/text"; @import "modules/theme-preview"; @import "modules/time"; diff --git a/tildes/tildes/routes.py b/tildes/tildes/routes.py index 55731dc..2e045df 100644 --- a/tildes/tildes/routes.py +++ b/tildes/tildes/routes.py @@ -22,6 +22,8 @@ def includeme(config: Configurator) -> None: config.add_route("search", "/search") + config.add_route("financials", "/financials") + config.add_route("groups", "/groups") config.add_route("login", "/login") diff --git a/tildes/tildes/templates/financials.jinja2 b/tildes/tildes/templates/financials.jinja2 new file mode 100644 index 0000000..12a9cbe --- /dev/null +++ b/tildes/tildes/templates/financials.jinja2 @@ -0,0 +1,64 @@ +{# Copyright (c) 2019 Tildes contributors #} +{# SPDX-License-Identifier: AGPL-3.0-or-later #} + +{% extends 'base_no_sidebar.jinja2' %} + +{% from "macros/utils.jinja2" import format_money %} + +{% block title %}Tildes financials{% endblock %} + +{% block main_classes %}text-formatted{% endblock %} + +{% block main_heading %}Tildes financials{% endblock %} + +{% block content %} +

This page is a view into Tildes's financials: operating expenses, income from the various donation methods, and the overall goal for monthly donations. Currently, it only contains data for November 2019, but more historical data will be available eventually.

+ +

Amounts on this page are in USD unless otherwise noted. Even though Tildes is a Canadian non-profit, many of its costs and donations are in USD. People from other parts of the world are also generally most familiar with the relative value of USD, so using it makes this info more understandable to everyone.

+ +

This page and the donation goal meter on the home page do not update in real-time. I will generally try to keep them current within a day or two (and automate some pieces eventually), but new donations will not show up immediately, and this information may be incomplete or outdated.

+ +
+ +

The current donation goal is {{ format_money(entries["goal"]|sum(attribute="amount")) }} per month.

+ +

The actual costs solely to keep Tildes running are much lower than this (see table below), but this represents the amount that I believe will make Tildes truly independently sustainable. It will cover all of the operating costs and also allow me (Deimos) to pay myself a somewhat respectable (but low) salary of about $35,000/year. This goal may not be achievable in the near term, but it is the point where I will be comfortable focusing on Tildes without still needing to find additional outside income.

+ +

Please donate—any amount will help get us closer to the goal!

+ +
+ +

{{ current_time.strftime("%B %Y") }} expenses and income

+ + + + +{% for entry in entries["expense"] %} + {{ entry_table_row(entry.description, entry.amount, entry.is_approximate) }} +{% endfor %} + +{{ entry_table_row("{} total expenses".format(current_time.strftime("%B %Y")), entries["expense"]|sum(attribute="amount"), is_summary=True) }} +
Expenses
+ + + + +{% for entry in entries["income"] %} + {{ entry_table_row(entry.description, entry.amount, entry.is_approximate) }} +{% endfor %} + +{{ entry_table_row("{} total income (so far)".format(current_time.strftime("%B %Y")), entries["income"]|sum(attribute="amount"), is_summary=True) }} +
Income
+ +* Approximate, due to currency conversion, incomplete data, or uncertain fees. +{% endblock %} + +{% macro entry_table_row(description, amount, is_approximate=False, is_summary=False) %} + + {{ description }} + + {% if is_approximate %}*{% endif %} + {{ format_money(amount) }} + + +{% endmacro %} diff --git a/tildes/tildes/templates/home.jinja2 b/tildes/tildes/templates/home.jinja2 index 44a9101..a953325 100644 --- a/tildes/tildes/templates/home.jinja2 +++ b/tildes/tildes/templates/home.jinja2 @@ -106,7 +106,7 @@

Tildes is a non-profit site with no ads or investors, funded entirely by donations.
- Please donate to support its continued development! + Please donate to support its continued development! (more details)

{% endmacro %} diff --git a/tildes/tildes/templates/macros/utils.jinja2 b/tildes/tildes/templates/macros/utils.jinja2 index 70f1afd..28166ef 100644 --- a/tildes/tildes/templates/macros/utils.jinja2 +++ b/tildes/tildes/templates/macros/utils.jinja2 @@ -13,3 +13,6 @@ {%- endtrans %} {% endmacro %} +{% macro format_money(amount) %} + {{ "${:,.2f}".format(amount) }} +{% endmacro %} diff --git a/tildes/tildes/views/financials.py b/tildes/tildes/views/financials.py new file mode 100644 index 0000000..624bf2e --- /dev/null +++ b/tildes/tildes/views/financials.py @@ -0,0 +1,34 @@ +# Copyright (c) 2019 Tildes contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +"""The view for displaying entries in the financials table.""" + +from collections import defaultdict +from typing import Dict, List + +from pyramid.request import Request +from pyramid.view import view_config +from sqlalchemy.sql.expression import text + +from tildes.lib.datetime import utc_now +from tildes.models.financials import Financials + + +@view_config( + route_name="financials", request_method="GET", renderer="financials.jinja2" +) +def get_financials(request: Request) -> dict: + """Display the financials page.""" + financial_entries = ( + request.query(Financials) + .filter(Financials.date_range.op("@>")(text("CURRENT_DATE"))) + .order_by(Financials.entry_id) + .all() + ) + + # split the entries up by type + entries: Dict[str, List] = defaultdict(list) + for entry in financial_entries: + entries[entry.entry_type.name.lower()].append(entry) + + return {"entries": entries, "current_time": utc_now()}