diff --git a/tildes/scss/modules/_donation.scss b/tildes/scss/modules/_donation.scss index 826c87a..2bbbbab 100644 --- a/tildes/scss/modules/_donation.scss +++ b/tildes/scss/modules/_donation.scss @@ -5,8 +5,9 @@ display: flex; flex-direction: column; align-items: center; + max-width: $paragraph-max-width; - padding: 0.4rem 0.4rem 0; + padding: 0.4rem; margin: 1rem 0; border: 1px solid; border-color: inherit; diff --git a/tildes/scss/modules/_sidebar.scss b/tildes/scss/modules/_sidebar.scss index 75794d9..61a68bb 100644 --- a/tildes/scss/modules/_sidebar.scss +++ b/tildes/scss/modules/_sidebar.scss @@ -2,10 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later #sidebar { - p { - margin-bottom: 0.4rem; - } - .btn { width: 100%; } diff --git a/tildes/tildes/templates/financials.jinja2 b/tildes/tildes/templates/financials.jinja2 index 4615d4d..f030f4f 100644 --- a/tildes/tildes/templates/financials.jinja2 +++ b/tildes/tildes/templates/financials.jinja2 @@ -4,6 +4,7 @@ {% extends 'base_no_sidebar.jinja2' %} {% from "macros/utils.jinja2" import format_money %} +{% from "macros/donation_goal.jinja2" import donation_goal %} {% block title %}Tildes financials{% endblock %} @@ -12,6 +13,7 @@ {% 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 {{ current_time.strftime("%B %Y") }}, 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.

@@ -22,6 +24,10 @@

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

+{% if financial_data %} + {{ donation_goal(financial_data, current_time) }} +{% endif %} +

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!

diff --git a/tildes/tildes/templates/home.jinja2 b/tildes/tildes/templates/home.jinja2 index feae5ad..1e342d4 100644 --- a/tildes/tildes/templates/home.jinja2 +++ b/tildes/tildes/templates/home.jinja2 @@ -4,6 +4,7 @@ {% extends 'topic_listing.jinja2' %} {% from 'macros/forms.jinja2' import search_form %} +{% from 'macros/donation_goal.jinja2' import donation_goal %} {% block title_full %}Tildes{% endblock %} @@ -109,35 +110,3 @@ {% endif %} {% endblock %} - -{% macro donation_goal(financial_data, current_time) %} -
-
Tildes's progress to sustainability
- -
- - {{ financial_data["goal_percentage"] }}% -
- -

{{ current_time.strftime("%B %Y") }} donations

- -

Tildes is a non-profit site with no ads or investors, funded entirely by donations.

- -

Please donate to support its continued development! (more details)

-
-{% endmacro %} diff --git a/tildes/tildes/templates/macros/donation_goal.jinja2 b/tildes/tildes/templates/macros/donation_goal.jinja2 new file mode 100644 index 0000000..c3373ff --- /dev/null +++ b/tildes/tildes/templates/macros/donation_goal.jinja2 @@ -0,0 +1,34 @@ +{# Copyright (c) 2020 Tildes contributors #} +{# SPDX-License-Identifier: AGPL-3.0-or-later #} + +{% macro donation_goal(financial_data, current_time) %} +
+
Tildes's progress to sustainability
+ +
+ + {{ financial_data["goal_percentage"] }}% +
+ +

{{ current_time.strftime("%B %Y") }} donations

+ +

Tildes is a non-profit site with no ads or investors, funded entirely by donations.

+ +

Please donate to support its continued development! (more details)

+
+{% endmacro %} diff --git a/tildes/tildes/views/financials.py b/tildes/tildes/views/financials.py index 624bf2e..0ee33c4 100644 --- a/tildes/tildes/views/financials.py +++ b/tildes/tildes/views/financials.py @@ -4,10 +4,13 @@ """The view for displaying entries in the financials table.""" from collections import defaultdict -from typing import Dict, List +from decimal import Decimal +from typing import Dict, List, Optional from pyramid.request import Request from pyramid.view import view_config +from sqlalchemy import func +from sqlalchemy.orm.session import Session from sqlalchemy.sql.expression import text from tildes.lib.datetime import utc_now @@ -31,4 +34,33 @@ def get_financials(request: Request) -> dict: for entry in financial_entries: entries[entry.entry_type.name.lower()].append(entry) - return {"entries": entries, "current_time": utc_now()} + financial_data = get_financial_data(request.db_session) + + return { + "entries": entries, + "current_time": utc_now(), + "financial_data": financial_data, + } + + +def get_financial_data(db_session: Session) -> Optional[Dict[str, Decimal]]: + """Return financial data used to render the donation goal box.""" + # get the total sum for each entry type in the financials table relevant to today + financial_totals = ( + db_session.query(Financials.entry_type, func.sum(Financials.amount)) + .filter(Financials.date_range.op("@>")(text("CURRENT_DATE"))) + .group_by(Financials.entry_type) + .all() + ) + + financial_data = {entry[0].name.lower(): entry[1] for entry in financial_totals} + + # if any of the entry types were missing, the data won't be usable + if any(key not in financial_data for key in ("expense", "goal", "income")): + return None + + financial_data["goal_percentage"] = round( + financial_data["income"] / financial_data["goal"] * 100 + ) + + return financial_data diff --git a/tildes/tildes/views/topic.py b/tildes/tildes/views/topic.py index 248caac..14d0d4e 100644 --- a/tildes/tildes/views/topic.py +++ b/tildes/tildes/views/topic.py @@ -4,9 +4,8 @@ """Views related to posting/viewing topics and comments on them.""" from collections import namedtuple -from decimal import Decimal from difflib import SequenceMatcher -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union from marshmallow import missing, ValidationError from marshmallow.fields import Boolean, String @@ -15,8 +14,7 @@ from pyramid.renderers import render_to_response from pyramid.request import Request from pyramid.response import Response from pyramid.view import view_config -from sqlalchemy import cast, func, or_ -from sqlalchemy.orm.session import Session +from sqlalchemy import cast, or_ from sqlalchemy.sql.expression import any_, desc, text from sqlalchemy_utils import Ltree from webargs.pyramidparser import use_kwargs @@ -31,7 +29,6 @@ from tildes.enums import ( from tildes.lib.database import TagList from tildes.lib.datetime import SimpleHoursPeriod, utc_now from tildes.models.comment import Comment, CommentNotification, CommentTree -from tildes.models.financials import Financials from tildes.models.group import Group, GroupWikiPage from tildes.models.log import LogComment, LogTopic from tildes.models.topic import Topic, TopicSchedule, TopicVisit @@ -41,6 +38,7 @@ from tildes.schemas.fields import Enum, ShortTimePeriod from tildes.schemas.listing import TopicListingSchema from tildes.schemas.topic import TopicSchema from tildes.views.decorators import rate_limit_view +from tildes.views.financials import get_financial_data DefaultSettings = namedtuple("DefaultSettings", ["order", "period"]) @@ -307,7 +305,7 @@ def get_group_topics( # noqa most_recent_scheduled_topics = None if is_home_page: - financial_data = _get_financial_data(request.db_session) + financial_data = get_financial_data(request.db_session) else: financial_data = None @@ -560,26 +558,3 @@ def _get_default_settings( default_period = None return DefaultSettings(order=default_order, period=default_period) - - -def _get_financial_data(db_session: Session) -> Optional[Dict[str, Decimal]]: - """Return financial data used to render the donation goal box.""" - # get the total sum for each entry type in the financials table relevant to today - financial_totals = ( - db_session.query(Financials.entry_type, func.sum(Financials.amount)) - .filter(Financials.date_range.op("@>")(text("CURRENT_DATE"))) - .group_by(Financials.entry_type) - .all() - ) - - financial_data = {entry[0].name.lower(): entry[1] for entry in financial_totals} - - # if any of the entry types were missing, the data won't be usable - if any(key not in financial_data for key in ("expense", "goal", "income")): - return None - - financial_data["goal_percentage"] = round( - financial_data["income"] / financial_data["goal"] * 100 - ) - - return financial_data