Browse Source

Add monthly donation goal to home page sidebar

This uses the data in the financials table to generate a donation goal
meter at the top of the home page's sidebar. It uses the new-ish HTML
<meter> element, which will automatically change colors as it hits
different thresholds.
merge-requests/85/head
Deimos 5 years ago
parent
commit
2fcce83965
  1. 26
      tildes/scss/modules/_donation.scss
  2. 9
      tildes/scss/modules/_site-footer.scss
  3. 1
      tildes/scss/styles.scss
  4. 34
      tildes/scss/themes/_theme_base.scss
  5. 2
      tildes/tildes/templates/base.jinja2
  6. 23
      tildes/tildes/templates/home.jinja2
  7. 35
      tildes/tildes/views/topic.py

26
tildes/scss/modules/_donation.scss

@ -0,0 +1,26 @@
// Copyright (c) 2019 Tildes contributors <code@tildes.net>
// SPDX-License-Identifier: AGPL-3.0-or-later
.donation-goal {
display: flex;
flex-direction: column;
align-items: center;
padding: 0.4rem 0.4rem 0;
margin: 1rem 0;
border: 1px solid;
border-color: inherit;
font-size: 0.6rem;
text-align: center;
header {
font-weight: bold;
}
meter {
width: 100%;
height: 0.4rem;
margin: 0.4rem 0 0.2rem;
}
}

9
tildes/scss/modules/_site-footer.scss

@ -6,14 +6,7 @@
padding-bottom: 1rem; padding-bottom: 1rem;
font-size: 0.6rem; font-size: 0.6rem;
font-style: italic;
text-align: center; text-align: center;
p {
max-width: none;
margin-bottom: 0.2rem;
line-height: 0.7rem;
}
} }
.site-footer-links { .site-footer-links {
@ -23,8 +16,6 @@
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
margin-top: 0.4rem;
font-style: normal;
} }
.site-footer-link { .site-footer-link {

1
tildes/scss/styles.scss

@ -14,6 +14,7 @@
@import "modules/chip"; @import "modules/chip";
@import "modules/comment"; @import "modules/comment";
@import "modules/divider"; @import "modules/divider";
@import "modules/donation";
@import "modules/empty"; @import "modules/empty";
@import "modules/form"; @import "modules/form";
@import "modules/group"; @import "modules/group";

34
tildes/scss/themes/_theme_base.scss

@ -82,6 +82,40 @@
background-color: map-get($theme, "background-primary"); background-color: map-get($theme, "background-primary");
} }
meter {
// Crazy styles to get this to work adapted from Spectre.css's _meters.scss
background: map-get($theme, "background-secondary");
&::-webkit-meter-bar {
background: map-get($theme, "background-secondary");
}
// For some mysterious reason, none of the below rules can be merged
&::-webkit-meter-optimum-value {
background: map-get($theme, "success");
}
&:-moz-meter-optimum::-moz-meter-bar {
background: map-get($theme, "success");
}
&::-webkit-meter-suboptimum-value {
background: map-get($theme, "warning");
}
&:-moz-meter-sub-optimum::-moz-meter-bar {
background: map-get($theme, "warning");
}
&::-webkit-meter-even-less-good-value {
background: map-get($theme, "error");
}
&:-moz-meter-sub-sub-optimum::-moz-meter-bar {
background: map-get($theme, "error");
}
}
tbody tr:nth-of-type(2n + 1) { tbody tr:nth-of-type(2n + 1) {
background-color: map-get($theme, "background-secondary"); background-color: map-get($theme, "background-secondary");
} }

2
tildes/tildes/templates/base.jinja2

@ -133,8 +133,6 @@
</div> </div>
{% endif %} {% endif %}
<p>Tildes is a non-profit site that respects its users and prioritizes quality content.</p>
<p>It has no advertising, no investors, and is supported by <a href="https://docs.tildes.net/donate">your donations</a>.</p>
<ul class="site-footer-links"> <ul class="site-footer-links">
<li class="site-footer-link"><a href="https://docs.tildes.net">Docs</a></li> <li class="site-footer-link"><a href="https://docs.tildes.net">Docs</a></li>
<li class="site-footer-link"><a href="https://blog.tildes.net">Blog</a></li> <li class="site-footer-link"><a href="https://blog.tildes.net">Blog</a></li>

23
tildes/tildes/templates/home.jinja2

@ -33,6 +33,10 @@
{% block sidebar %} {% block sidebar %}
{{ search_form() }} {{ search_form() }}
{% if financial_data %}
{{ donation_goal(financial_data, current_time) }}
{% endif %}
<h2>Home</h2> <h2>Home</h2>
{% if request.user %} {% if request.user %}
@ -87,3 +91,22 @@
</ul> </ul>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% macro donation_goal(financial_data, current_time) %}
<div class="donation-goal">
<header>{{ current_time.strftime("%B %Y") }} donation goal</header>
<meter
max="{{ financial_data["goal"] }}"
low="{{ financial_data["expense"] }}"
high="{{ financial_data["goal"] // 4 * 3 }}" {# 3/4 of the goal #}
optimum="{{ financial_data["goal"] - 1 }}" {# just needs to be between high and max #}
value="{{ financial_data["income"] }}"
title="${{ financial_data["income"] }} / ${{ financial_data["goal"] }} (USD)"
></meter>
<p>
Tildes is a non-profit site with no ads or investors, funded entirely by donations.<br>
<a href="https://docs.tildes.net/donate">Please donate</a> to support its continued development!
</p>
</div>
{% endmacro %}

35
tildes/tildes/views/topic.py

@ -4,7 +4,8 @@
"""Views related to posting/viewing topics and comments on them.""" """Views related to posting/viewing topics and comments on them."""
from collections import namedtuple from collections import namedtuple
from typing import Any, Optional, Union
from decimal import Decimal
from typing import Any, Dict, Optional, Union
from datetime import timedelta from datetime import timedelta
from marshmallow import missing, ValidationError from marshmallow import missing, ValidationError
@ -14,7 +15,8 @@ from pyramid.renderers import render_to_response
from pyramid.request import Request from pyramid.request import Request
from pyramid.response import Response from pyramid.response import Response
from pyramid.view import view_config from pyramid.view import view_config
from sqlalchemy import cast
from sqlalchemy import cast, func
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.expression import any_, desc, text from sqlalchemy.sql.expression import any_, desc, text
from sqlalchemy_utils import Ltree from sqlalchemy_utils import Ltree
from webargs.pyramidparser import use_kwargs from webargs.pyramidparser import use_kwargs
@ -28,8 +30,9 @@ from tildes.enums import (
TopicSortOption, TopicSortOption,
) )
from tildes.lib.database import TagList from tildes.lib.database import TagList
from tildes.lib.datetime import SimpleHoursPeriod
from tildes.lib.datetime import SimpleHoursPeriod, utc_now
from tildes.models.comment import Comment, CommentNotification, CommentTree from tildes.models.comment import Comment, CommentNotification, CommentTree
from tildes.models.financials import Financials
from tildes.models.group import Group, GroupWikiPage from tildes.models.group import Group, GroupWikiPage
from tildes.models.log import LogComment, LogTopic from tildes.models.log import LogComment, LogTopic
from tildes.models.topic import Topic, TopicSchedule, TopicVisit from tildes.models.topic import Topic, TopicSchedule, TopicVisit
@ -262,6 +265,11 @@ def get_group_topics(
else: else:
most_recent_scheduled_topics = None most_recent_scheduled_topics = None
if is_home_page:
financial_data = _get_financial_data(request.db_session)
else:
financial_data = None
return { return {
"group": request.context, "group": request.context,
"groups": groups, "groups": groups,
@ -281,6 +289,8 @@ def get_group_topics(
"wiki_has_index": wiki_has_index, "wiki_has_index": wiki_has_index,
"subgroups": subgroups, "subgroups": subgroups,
"most_recent_scheduled_topics": most_recent_scheduled_topics, "most_recent_scheduled_topics": most_recent_scheduled_topics,
"financial_data": financial_data,
"current_time": utc_now(),
} }
@ -488,3 +498,22 @@ def _get_default_settings(request: Request, order: Any) -> DefaultSettings: # n
default_period = None default_period = None
return DefaultSettings(order=default_order, period=default_period) 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
return financial_data
Loading…
Cancel
Save