Merge pull request #1771 from bookwyrm-social/about-page

Refactors about page
This commit is contained in:
Mouse Reeve 2022-01-08 13:28:23 -08:00 committed by GitHub
commit a80cb4310e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 63 deletions

View file

@ -0,0 +1,33 @@
# Generated by Django 3.2.10 on 2022-01-06 17:59
from django.contrib.auth.models import AbstractUser
from django.db import migrations
def get_admins(apps, schema_editor):
"""add any superusers to the "admin" group"""
db_alias = schema_editor.connection.alias
groups = apps.get_model("auth", "Group")
try:
group = groups.objects.using(db_alias).get(name="admin")
except groups.DoesNotExist:
# for tests
return
users = apps.get_model("bookwyrm", "User")
admins = users.objects.using(db_alias).filter(is_superuser=True)
for admin in admins:
admin.groups.add(group)
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0123_alter_user_preferred_language"),
]
operations = [
migrations.RunPython(get_admins, reverse_code=migrations.RunPython.noop),
]

View file

@ -0,0 +1,141 @@
{% extends 'about/layout.html' %}
{% load humanize %}
{% load i18n %}
{% load utilities %}
{% load bookwyrm_tags %}
{% load cache %}
{% block title %}
{% trans "About" %}
{% endblock %}
{% block about_content %}
{# seven day cache #}
{% cache 604800 about_page %}
{% get_book_superlatives as superlatives %}
<section class="content pb-4">
<h2>
{% blocktrans with site_name=site.name %}Welcome to {{ site_name }}!{% endblocktrans %}
</h2>
<p class="subtitle notification has-background-primary-light">
{% blocktrans trimmed with site_name=site.name %}
{{ site_name }} is part of <em>BookWyrm</em>, a network of independent, self-directed communities for readers.
While you can interact seemlessly with users anywhere in the <a href="https://joinbookwyrm.com/instances/" target="_blank">BookWyrm network</a>, this community is unique.
{% endblocktrans %}
</p>
<div class="columns">
{% if top_rated %}
{% with book=superlatives.top_rated.default_edition rating=top_rated.rating %}
<div class="column is-one-third is-flex">
<div class="media notification">
<div class="media-left">
<a href="{{ book.local_path }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' size='medium' aria='show' %}
</a>
</div>
<div class="media-content">
{% blocktrans trimmed with title=book|book_title book_path=book.local_path site_name=site.name rating=rating|floatformat:1 %}
<a href="{{ book_path }}"><em>{{ title }}</em></a> is {{ site_name }}'s most beloved book, with an average rating of {{ rating }} out of 5.
{% endblocktrans %}
</div>
</div>
</div>
{% endwith %}
{% endif %}
{% if wanted %}
{% with book=superlatives.wanted.default_edition %}
<div class="column is-one-third is-flex">
<div class="media notification">
<div class="media-left">
<a href="{{ book.local_path }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' size='medium' aria='show' %}
</a>
</div>
<div class="media-content">
{% blocktrans trimmed with title=book|book_title book_path=book.local_path site_name=site.name %}
More {{ site_name }} users want to read <a href="{{ book_path }}"><em>{{ title }}</em></a> than any other book.
{% endblocktrans %}
</div>
</div>
</div>
{% endwith %}
{% endif %}
{% if controversial %}
{% with book=superlatives.controversial.default_edition %}
<div class="column is-one-third is-flex">
<div class="media notification">
<div class="media-left">
<a href="{{ book.local_path }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' size='medium' aria='show' %}
</a>
</div>
<div class="media-content">
{% blocktrans trimmed with title=book|book_title book_path=book.local_path site_name=site.name %}
<a href="{{ book_path }}"><em>{{ title }}</em></a> has the most divisive ratings of any book on {{ site_name }}.
{% endblocktrans %}
</div>
</div>
</div>
{% endwith %}
{% endif %}
</div>
<p>
{% trans "Track your reading, talk about books, write reviews, and discover what to read next. Always ad-free, anti-corporate, and community-oriented, BookWyrm is human-scale software, designed to stay small and personal. If you have feature requests, bug reports, or grand dreams, <a href='https://joinbookwyrm.com/get-involved' target='_blank'>reach out</a> and make yourself heard." %}
</p>
</section>
<section class="block">
<header class="content">
<h2 class="title is-3">{% trans "Meet your admins" %}</h2>
<p>
{% url "conduct" as coc_path %}
{% blocktrans with site_name=site.name %}
{{ site_name }}'s moderators and administrators keep the site up and running, enforce the <a href="coc_path">code of conduct</a>, and respond when users report spam and bad behavior.
{% endblocktrans %}
</p>
</header>
<div class="columns is-multiline">
{% for user in admins %}
<div class="column">
<div class="card is-stretchable">
{% with role=user.groups.first.name %}
<div class="card-header {% if role == "moderator" %}has-background-info-light{% else %}has-background-success-light{% endif %}">
<span class="card-header-title is-size-7 pt-1 pb-1">
{% if role == "moderator" %}
{% trans "Moderator" %}
{% else %}
{% trans "Admin" %}
{% endif %}
</span>
</div>
{% endwith %}
<div class="cord-content p-5">
{% include 'user/user_preview.html' with user=user %}
</div>
{% if request.user.is_authenticated and user.id != request.user.id %}
<div class="has-background-white-bis card-footer">
<div class="card-footer-item">
{% include 'snippets/follow_button.html' with user=user minimal=True %}
</div>
<div class="card-footer-item">
<a href="{% url 'direct-messages-user' user|username %}">{% trans "Send direct message" %}</a>
</div>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</section>
{% endcache %}
{% endblock %}

View file

@ -0,0 +1,15 @@
{% extends 'about/layout.html' %}
{% load i18n %}
{% block title %}{% trans "Code of Conduct" %}{% endblock %}
{% block about_content %}
<div class="block content">
<h2>{% trans "Code of Conduct" %}</h2>
<div class="content">
{{ site.code_of_conduct | safe }}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,57 @@
{% extends 'landing/layout.html' %}
{% load humanize %}
{% load i18n %}
{% block about_panel %}
<div class="box">
{% include "snippets/about.html" with size="m" %}
{% if active_users %}
<ul>
<li class="tag is-size-6">
<span class="mr-1">{% trans "Active users:" %}</span>
<strong>{{ active_users|intcomma }}</strong>
</li>
<li class="tag is-size-6">
<span class="mr-1">{% trans "Statuses posted:" %}</span>
<strong>{{ status_count|intcomma }}</strong>
</li>
<li class="tag is-size-6">
<span class="mr-1">{% trans "Software version:" %}</span>
<strong>{{ version }}</strong>
</li>
</ul>
{% endif %}
</div>
{% endblock %}
{% block panel %}
<div class="block columns pt-4">
<nav class="menu column is-one-quarter">
<h2 class="menu-label">{% blocktrans with site_name=site.name %}About {{ site_name }}{% endblocktrans %}</h2>
<ul class="menu-list">
<li>
{% url 'about' as path %}
<a href="{{ path }}" {% if request.path in path %}class="is-active"{% endif %}>
{% trans "About" %}
</a>
</li>
<li>
{% url 'conduct' as path %}
<a href="{{ path }}" {% if request.path in path %}class="is-active"{% endif %}>
{% trans "Code of Conduct" %}
</a>
</li>
<li>
{% url 'privacy' as path %}
<a href="{{ path }}" {% if request.path in path %}class="is-active"{% endif %}>
{% trans "Privacy Policy" %}
</a>
</li>
</ul>
</nav>
<div class="column">
{% block about_content %}{% endblock %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,15 @@
{% extends 'about/layout.html' %}
{% load i18n %}
{% block title %}{% trans "Privacy Policy" %}{% endblock %}
{% block about_content %}
<div class="block">
<h2 class="title">{% trans "Privacy Policy" %}</h2>
<div class="content">
{{ site.privacy_policy | safe }}
</div>
</div>
{% endblock %}

View file

@ -1,37 +0,0 @@
{% extends 'landing/layout.html' %}
{% load i18n %}
{% block panel %}
<div class="block columns mt-4">
<nav class="menu column is-one-quarter">
<h2 class="menu-label">{% blocktrans with site_name=site.name %}About {{ site_name }}{% endblocktrans %}</h2>
<ul class="menu-list">
<li>
<a href="#coc">{% trans "Code of Conduct" %}</a>
</li>
<li>
<a href="#privacy">{% trans "Privacy Policy" %}</a>
</li>
</ul>
</nav>
<div class="column content">
<div class="block" id="coc">
<h2 class="title">{% trans "Code of Conduct" %}</h2>
<div class="content">
{{ site.code_of_conduct | safe }}
</div>
</div>
<hr aria-hidden="true">
<div class="block" id="privacy">
<h2 class="title">{% trans "Privacy Policy" %}</h2>
<div class="content">
{{ site.privacy_policy | safe }}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -31,6 +31,7 @@
</div>
</section>
{% block about_panel %}
<section class="tile is-ancestor">
<div class="tile is-7 is-parent">
<div class="tile is-child box">
@ -89,6 +90,7 @@
{% endif %}
</div>
</section>
{% endblock %}
{% block panel %}{% endblock %}

View file

@ -2,7 +2,7 @@
<div class="columns">
<div class="column is-narrow is-hidden-mobile">
<figure class="block is-w-xl">
<figure class="block is-w-{% if size %}{{ size }}{% else %}xl{% endif %}">
<img src="{% if site.logo %}{% get_media_prefix %}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}" alt="BookWyrm logo">
</figure>
</div>

View file

@ -3,6 +3,7 @@
{% load i18n %}
{% load static %}
{% load humanize %}
{% load cache %}
{% with status_type=status.status_type %}
<div
@ -15,6 +16,7 @@
<div class="columns is-gapless">
{% if not hide_book %}
{% cache 259200 content_status_book status.id %}
{% with book=status.book|default:status.mention_books.first %}
{% if book %}
<div class="column is-cover">
@ -34,6 +36,7 @@
</div>
{% endif %}
{% endwith %}
{% endcache %}
{% endif %}
<article class="column ml-3-tablet my-3-mobile">

View file

@ -3,8 +3,10 @@
{% load bookwyrm_tags %}
{% load markdown %}
{% load i18n %}
{% load cache %}
{% if not hide_book %}
{% cache 259200 generated_status_book status.id %}
{% with book=status.book|default:status.mention_books.first %}
<div class="columns is-mobile is-gapless">
<a class="column is-cover is-narrow" href="{{ book.local_path }}">
@ -24,6 +26,7 @@
</div>
</div>
{% endwith %}
{% endcache %}
{% endif %}
{% endspaceless %}

View file

@ -21,23 +21,23 @@
<p><a href="{{ user.remote_id }}">{{ user.username }}</a></p>
<p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p>
<p>
{% if is_self %}
{% if request.user.id == user.id %}
<a href="{% url 'user-followers' user|username %}">{% blocktrans count counter=user.followers.count %}{{ counter }} follower{% plural %}{{ counter }} followers{% endblocktrans %}</a>,
<a href="{% url 'user-following' user|username %}">{% blocktrans with counter=user.following.count %}{{ counter }} following{% endblocktrans %}</a>
<a href="{% url 'user-followers' user|username %}">{% blocktrans count counter=user.followers.count %}{{ counter }} follower{% plural %}{{ counter }} followers{% endblocktrans %}</a>,
<a href="{% url 'user-following' user|username %}">{% blocktrans with counter=user.following.count %}{{ counter }} following{% endblocktrans %}</a>
{% elif request.user.is_authenticated %}
{% mutuals_count user as mutuals %}
<a href="{% url 'user-followers' user|username %}">
{% if mutuals %}
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %}
{% elif request.user in user.following.all %}
{% trans "Follows you" %}
{% else %}
{% trans "No followers you follow" %}
{% endif %}
</a>
{% mutuals_count user as mutuals %}
<a href="{% url 'user-followers' user|username %}">
{% if mutuals %}
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %}
{% elif request.user in user.following.all %}
{% trans "Follows you" %}
{% else %}
{% trans "No followers you follow" %}
{% endif %}
</a>
{% endif %}
</p>

View file

@ -1,6 +1,6 @@
""" template filters """
from django import template
from django.db.models import Avg
from django.db.models import Avg, StdDev, Count, F, Q
from bookwyrm import models
from bookwyrm.views.feed import get_suggested_books
@ -68,6 +68,57 @@ def load_subclass(status):
return status
@register.simple_tag(takes_context=False)
def get_book_superlatives():
"""get book stats for the about page"""
total_ratings = models.Review.objects.filter(local=True, deleted=False).count()
data = {}
data["top_rated"] = (
models.Work.objects.annotate(
rating=Avg(
"editions__review__rating",
filter=Q(editions__review__local=True, editions__review__deleted=False),
),
rating_count=Count(
"editions__review",
filter=Q(editions__review__local=True, editions__review__deleted=False),
),
)
.annotate(weighted=F("rating") * F("rating_count") / total_ratings)
.filter(rating__gt=4, weighted__gt=0)
.order_by("-weighted")
.first()
)
data["controversial"] = (
models.Work.objects.annotate(
deviation=StdDev(
"editions__review__rating",
filter=Q(editions__review__local=True, editions__review__deleted=False),
),
rating_count=Count(
"editions__review",
filter=Q(editions__review__local=True, editions__review__deleted=False),
),
)
.annotate(weighted=F("deviation") * F("rating_count") / total_ratings)
.filter(weighted__gt=0)
.order_by("-weighted")
.first()
)
data["wanted"] = (
models.Work.objects.annotate(
shelf_count=Count(
"editions__shelves", filter=Q(editions__shelves__identifier="to-read")
)
)
.order_by("-shelf_count")
.first()
)
return data
@register.simple_tag(takes_context=False)
def related_status(notification):
"""for notifications"""

View file

@ -49,7 +49,27 @@ class LandingViews(TestCase):
def test_about_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.About.as_view()
view = views.about
request = self.factory.get("")
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_conduct_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.conduct
request = self.factory.get("")
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
validate_html(result.render())
self.assertEqual(result.status_code, 200)
def test_privacy_page(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.privacy
request = self.factory.get("")
request.user = self.local_user
result = view(request)

View file

@ -197,7 +197,9 @@ urlpatterns = [
),
re_path(r"^report/?$", views.make_report, name="report"),
# landing pages
re_path(r"^about/?$", views.About.as_view(), name="about"),
re_path(r"^about/?$", views.about, name="about"),
re_path(r"^privacy/?$", views.privacy, name="privacy"),
re_path(r"^conduct/?$", views.conduct, name="conduct"),
path("", views.Home.as_view(), name="landing"),
re_path(r"^discover/?$", views.Discover.as_view(), name="discover"),
re_path(r"^notifications/?$", views.Notifications.as_view(), name="notifications"),

View file

@ -34,7 +34,8 @@ from .books.edit_book import EditBook, ConfirmEditBook
from .books.editions import Editions, switch_edition
# landing
from .landing.landing import About, Home, Landing
from .landing.about import about, privacy, conduct
from .landing.landing import Home, Landing
from .landing.login import Login, Logout
from .landing.register import Register, ConfirmEmail, ConfirmEmailCode, resend_link
from .landing.password import PasswordResetRequest, PasswordReset

View file

@ -0,0 +1,38 @@
""" non-interactive pages """
from dateutil.relativedelta import relativedelta
from django.template.response import TemplateResponse
from django.utils import timezone
from django.views.decorators.http import require_GET
from bookwyrm import models, settings
@require_GET
def about(request):
"""more information about the instance"""
six_months_ago = timezone.now() - relativedelta(months=6)
six_month_count = models.User.objects.filter(
is_active=True, local=True, last_active_date__gt=six_months_ago
).count()
data = {
"active_users": six_month_count,
"status_count": models.Status.objects.filter(
user__local=True, deleted=False
).count(),
"admins": models.User.objects.filter(groups__name__in=["admin", "moderator"]),
"version": settings.VERSION,
}
return TemplateResponse(request, "about/about.html", data)
@require_GET
def conduct(request):
"""more information about the instance"""
return TemplateResponse(request, "about/conduct.html")
@require_GET
def privacy(request):
"""more information about the instance"""
return TemplateResponse(request, "about/privacy.html")

View file

@ -8,14 +8,6 @@ from bookwyrm.views.feed import Feed
# pylint: disable= no-self-use
class About(View):
"""create invites"""
def get(self, request):
"""more information about the instance"""
return TemplateResponse(request, "landing/about.html")
class Home(View):
"""landing page or home feed depending on auth"""