Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-03-28 14:49:58 -07:00
commit 7730c9f9a7
21 changed files with 386 additions and 33 deletions

View file

@ -0,0 +1,24 @@
""" Delete user streams """
from django.core.management.base import BaseCommand
import redis
from bookwyrm import settings
r = redis.Redis(
host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0
)
def erase_streams():
""" throw the whole redis away """
r.flushall()
class Command(BaseCommand):
""" delete activity streams for all users """
help = "Delete all the user streams"
# pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options):
""" flush all, baby """
erase_streams()

View file

@ -1,4 +1,4 @@
""" Delete and re-create user feeds """ """ Re-create user streams """
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
import redis import redis
@ -12,13 +12,8 @@ r = redis.Redis(
) )
def erase_feeds(): def populate_streams():
""" throw the whole redis away """ """ build all the streams for all the users """
r.flushall()
def create_feeds():
""" build all the fields for all the users """
users = models.User.objects.filter( users = models.User.objects.filter(
local=True, local=True,
is_active=True, is_active=True,
@ -29,11 +24,10 @@ def create_feeds():
class Command(BaseCommand): class Command(BaseCommand):
""" start all over with user feeds """ """ start all over with user streams """
help = "Delete and re-create all the user feeds" help = "Populate streams for all users"
# pylint: disable=no-self-use,unused-argument # pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options): def handle(self, *args, **options):
""" run feed builder """ """ run feed builder """
erase_feeds() populate_streams()
create_feeds()

View file

@ -35,10 +35,14 @@
</div> </div>
<div class="columns"> <div class="columns">
<div class="column is-one-fifth is-clipped"> <div class="column is-one-fifth">
{% include 'snippets/book_cover.html' with book=book size=large %} <div class="is-clipped">
{% include 'snippets/rate_action.html' with user=request.user book=book %} {% include 'snippets/book_cover.html' with book=book size=large %}
{% include 'snippets/shelve_button/shelve_button.html' %} {% include 'snippets/rate_action.html' with user=request.user book=book %}
</div>
<div class="mb-3">
{% include 'snippets/shelve_button/shelve_button.html' %}
</div>
{% if request.user.is_authenticated and not book.cover %} {% if request.user.is_authenticated and not book.cover %}
<div class="block"> <div class="block">
@ -48,7 +52,7 @@
</div> </div>
{% endif %} {% endif %}
<section class="content"> <section class="content is-clipped">
<dl> <dl>
{% if book.isbn_13 %} {% if book.isbn_13 %}
<div class="is-flex is-justify-content-space-between is-align-items-center"> <div class="is-flex is-justify-content-space-between is-align-items-center">

View file

@ -1,8 +1,21 @@
{% extends 'settings/admin_layout.html' %} {% extends 'settings/admin_layout.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Reports" %}{% endblock %} {% block title %}
{% block header %}{% trans "Reports" %}{% endblock %} {% if server %}
{% blocktrans with server_name=server.server_name %}Reports: {{ server_name }}{% endblocktrans %}
{% else %}
{% trans "Reports" %}
{% endif %}
{% endblock %}
{% block header %}
{% if server %}
{% blocktrans with server_name=server.server_name %}Reports: <small>{{ server_name }}</small>{% endblocktrans %}
<a href="{% url 'settings-reports' %}" class="help has-text-weight-normal">Clear filters</a>
{% else %}
{% trans "Reports" %}
{% endif %}
{% endblock %}
{% block panel %} {% block panel %}
<div class="tabs"> <div class="tabs">
@ -17,6 +30,10 @@
</div> </div>
<div class="block"> <div class="block">
{% if not reports %}
<em>{% trans "No reports found." %}</em>
{% endif %}
{% for report in reports %} {% for report in reports %}
<div class="block"> <div class="block">
{% include 'moderation/report_preview.html' with report=report %} {% include 'moderation/report_preview.html' with report=report %}

View file

@ -14,6 +14,10 @@
{% if perms.bookwyrm.create_invites %} {% if perms.bookwyrm.create_invites %}
<h2 class="menu-label">{% trans "Manage Users" %}</h2> <h2 class="menu-label">{% trans "Manage Users" %}</h2>
<ul class="menu-list"> <ul class="menu-list">
<li>
{% url 'settings-users' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Users" %}</a>
</li>
<li> <li>
{% url 'settings-invite-requests' as url %} {% url 'settings-invite-requests' as url %}
{% url 'settings-invites' as alt_url %} {% url 'settings-invites' as alt_url %}

View file

@ -0,0 +1,68 @@
{% extends 'settings/admin_layout.html' %}
{% block title %}{{ server.server_name }}{% endblock %}
{% load i18n %}
{% block header %}
{{ server.server_name }}
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to server list" %}</a>
{% endblock %}
{% block panel %}
<section class="block content">
<h2 class="title is-4">{% trans "Details" %}</h2>
<dl>
<div class="is-flex">
<dt>{% trans "Software:" %}</dt>
<dd>{{ server.application_type }}</dd>
</div>
<div class="is-flex">
<dt>{% trans "Version:" %}</dt>
<dd>{{ server.application_version }}</dd>
</div>
<div class="is-flex">
<dt>{% trans "Status:" %}</dt>
<dd>Federated</dd>
</div>
</dl>
</section>
<section class="block content">
<h2 class="title is-4">{% trans "Activity" %}</h2>
<dl>
<div class="is-flex">
<dt>{% trans "Users:" %}</dt>
<dd>
{{ users.count }}
{% if server.user_set.count %}(<a href="{% url 'settings-users' %}?server={{ server.id }}">{% trans "View all" %}</a>){% endif %}
</dd>
</div>
<div class="is-flex">
<dt>{% trans "Reports:" %}</dt>
<dd>
{{ reports.count }}
{% if reports.count %}(<a href="{% url 'settings-reports' %}?server={{ server.id }}">{% trans "View all" %}</a>){% endif %}
</dd>
</div>
<div class="is-flex">
<dt>{% trans "Followed by us:" %}</dt>
<dd>
{{ followed_by_us.count }}
</dd>
</div>
<div class="is-flex">
<dt>{% trans "Followed by them:" %}</dt>
<dd>
{{ followed_by_them.count }}
</dd>
</div>
<div class="is-flex">
<dt>{% trans "Blocked by us:" %}</dt>
<dd>
{{ blocked_by_us.count }}
</dd>
</div>
</dl>
</section>
{% endblock %}

View file

@ -1,5 +1,6 @@
{% extends 'settings/admin_layout.html' %} {% extends 'settings/admin_layout.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Federated Servers" %}{% endblock %}
{% block header %}{% trans "Federated Servers" %}{% endblock %} {% block header %}{% trans "Federated Servers" %}{% endblock %}
@ -7,17 +8,30 @@
<table class="table is-striped"> <table class="table is-striped">
<tr> <tr>
<th>{% trans "Server name" %}</th> {% url 'settings-federation' as url %}
<th>{% trans "Software" %}</th> <th>
{% trans "Server name" as text %}
{% include 'snippets/table-sort-header.html' with field="server_name" sort=sort text=text %}
</th>
<th>
{% trans "Date federated" as text %}
{% include 'snippets/table-sort-header.html' with field="created_date" sort=sort text=text %}
</th>
<th>
{% trans "Software" as text %}
{% include 'snippets/table-sort-header.html' with field="application_type" sort=sort text=text %}
</th>
<th>{% trans "Status" %}</th> <th>{% trans "Status" %}</th>
</tr> </tr>
{% for server in servers %} {% for server in servers %}
<tr> <tr>
<td>{{ server.server_name }}</td> <td><a href="{% url 'settings-federated-server' server.id %}">{{ server.server_name }}</a></td>
<td>{{ server.created_date }}</td>
<td>{{ server.application_type }} ({{ server.application_version }})</td> <td>{{ server.application_type }} ({{ server.application_version }})</td>
<td>{{ server.status }}</td> <td>{{ server.status }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% include 'snippets/pagination.html' with page=servers path=request.path %}
{% endblock %} {% endblock %}

View file

@ -23,7 +23,7 @@
{% trans "Ignored Invite Requests" %} {% trans "Ignored Invite Requests" %}
{% else %} {% else %}
{% trans "Invite Requests" %} {% trans "Invite Requests" %}
{% endif %} {% endif %} ({{ count }})
</h2> </h2>
<table class="table is-striped"> <table class="table is-striped">

View file

@ -0,0 +1,59 @@
{% extends 'settings/admin_layout.html' %}
{% load i18n %}
{% block title %}{% trans "Users" %}{% endblock %}
{% block header %}
{% if server %}
{% blocktrans with server_name=server.server_name %}Users: <small>{{ server_name }}</small>{% endblocktrans %}
<a href="{% url 'settings-users' %}" class="help has-text-weight-normal">Clear filters</a>
{% else %}
{% trans "Users" %}
{% endif %}
{% endblock %}
{% block panel %}
<table class="table is-striped">
<tr>
{% url 'settings-users' as url %}
<th>
{% trans "Username" as text %}
{% include 'snippets/table-sort-header.html' with field="username" sort=sort text=text %}
</th>
<th>
{% trans "Date Added" as text %}
{% include 'snippets/table-sort-header.html' with field="created_date" sort=sort text=text %}
</th>
<th>
{% trans "Last Active" as text %}
{% include 'snippets/table-sort-header.html' with field="last_active_date" sort=sort text=text %}
</th>
<th>
{% trans "Status" as text %}
{% include 'snippets/table-sort-header.html' with field="is_active" sort=sort text=text %}
</th>
<th>
{% trans "Remote server" as text %}
{% include 'snippets/table-sort-header.html' with field="federated_server__server_name" sort=sort text=text %}
</th>
</tr>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.created_date }}</td>
<td>{{ user.last_active_date }}</td>
<td>{% if user.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %}</td>
<td>
{% if user.federated_server %}
<a href="{% url 'settings-federated-server' user.federated_server.id %}">{{ user.federated_server.server_name }}</a>
{% elif not user.local %}
<em>{% trans "Not set" %}</em>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% include 'snippets/pagination.html' with page=users path=request.path %}
{% endblock %}

View file

@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% load bookwyrm_tags %} {% load bookwyrm_tags %}
{% if books|length > 0 %} {% if books|length > 0 %}
<div class="table-container"> <div class="scroll-x">
<table class="table is-striped is-fullwidth"> <table class="table is-striped is-fullwidth">
<tr class="book-preview"> <tr class="book-preview">

View file

@ -28,7 +28,7 @@
{% if status.quote %} {% if status.quote %}
<div class="quote block"> <div class="quote block">
<blockquote dir="auto">{{ status.quote | safe }}</blockquote> <blockquote dir="auto" class="mb-2">{{ status.quote | safe }}</blockquote>
<p> &mdash; {% include 'snippets/book_titleby.html' with book=status.book %}</p> <p> &mdash; {% include 'snippets/book_titleby.html' with book=status.book %}</p>
</div> </div>

View file

@ -0,0 +1,13 @@
{% load i18n %}
<a href="{{ url }}?sort={% if sort == field %}-{% endif %}{{ field }}">
{{ text }}
{% if sort == field %}
<span class="icon icon-arrow-up">
<span class="is-sr-only">{% trans "Sorted asccending" %}</span>
</span>
{% elif sort == "-"|add:field %}
<span class="icon icon-arrow-down">
<span class="is-sr-only">{% trans "Sorted descending" %}</span>
</span>
{% endif %}
</a>

View file

@ -3,8 +3,7 @@ from django.template.response import TemplateResponse
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from bookwyrm import models from bookwyrm import models, views
from bookwyrm import views
class FederationViews(TestCase): class FederationViews(TestCase):
@ -32,3 +31,16 @@ class FederationViews(TestCase):
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
result.render() result.render()
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_server_page(self):
""" there are so many views, this just makes sure it LOADS """
server = models.FederatedServer.objects.create(server_name="hi.there.com")
view = views.FederatedServer.as_view()
request = self.factory.get("")
request.user = self.local_user
request.user.is_superuser = True
result = view(request, server.id)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)

View file

@ -0,0 +1,33 @@
""" test for app action functionality """
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import models, views
class UserAdminViews(TestCase):
""" every response to a get request, html or json """
def setUp(self):
""" we need basic test data and mocks """
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
"mouse@local.com",
"mouse@mouse.mouse",
"password",
local=True,
localname="mouse",
)
models.SiteSettings.objects.create()
def test_user_admin_page(self):
""" there are so many views, this just makes sure it LOADS """
view = views.UserAdmin.as_view()
request = self.factory.get("")
request.user = self.local_user
request.user.is_superuser = True
result = view(request)
self.assertIsInstance(result, TemplateResponse)
result.render()
self.assertEqual(result.status_code, 200)

View file

@ -54,8 +54,16 @@ urlpatterns = [
views.site.email_preview, views.site.email_preview,
name="settings-email-preview", name="settings-email-preview",
), ),
re_path(r"^settings/users", views.UserAdmin.as_view(), name="settings-users"),
re_path( re_path(
r"^settings/federation", views.Federation.as_view(), name="settings-federation" r"^settings/federation/?$",
views.Federation.as_view(),
name="settings-federation",
),
re_path(
r"^settings/federation/(?P<server>\d+)/?$",
views.FederatedServer.as_view(),
name="settings-federated-server",
), ),
re_path( re_path(
r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites" r"^settings/invites/?$", views.ManageInvites.as_view(), name="settings-invites"

View file

@ -6,7 +6,7 @@ from .books import Book, EditBook, ConfirmEditBook, Editions
from .books import upload_cover, add_description, switch_edition, resolve_book from .books import upload_cover, add_description, switch_edition, resolve_book
from .directory import Directory from .directory import Directory
from .error import not_found_page, server_error_page from .error import not_found_page, server_error_page
from .federation import Federation from .federation import Federation, FederatedServer
from .feed import DirectMessage, Feed, Replies, Status from .feed import DirectMessage, Feed, Replies, Status
from .follow import follow, unfollow from .follow import follow, unfollow
from .follow import accept_follow_request, delete_follow_request from .follow import accept_follow_request, delete_follow_request
@ -35,4 +35,5 @@ from .status import CreateStatus, DeleteStatus
from .tag import Tag, AddTag, RemoveTag from .tag import Tag, AddTag, RemoveTag
from .updates import get_notification_count, get_unread_status_count from .updates import get_notification_count, get_unread_status_count
from .user import User, EditUser, Followers, Following from .user import User, EditUser, Followers, Following
from .user_admin import UserAdmin
from .wellknown import webfinger, nodeinfo_pointer, nodeinfo, instance_info, peers from .wellknown import webfinger, nodeinfo_pointer, nodeinfo, instance_info, peers

View file

@ -1,10 +1,13 @@
""" manage federated servers """ """ manage federated servers """
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from bookwyrm import models from bookwyrm import models
from bookwyrm.settings import PAGE_LENGTH
# pylint: disable= no-self-use # pylint: disable= no-self-use
@ -17,7 +20,44 @@ class Federation(View):
""" what servers do we federate with """ """ what servers do we federate with """
def get(self, request): def get(self, request):
""" edit form """ """ list of servers """
try:
page = int(request.GET.get("page", 1))
except ValueError:
page = 1
servers = models.FederatedServer.objects.all() servers = models.FederatedServer.objects.all()
data = {"servers": servers}
sort = request.GET.get("sort")
sort_fields = ["created_date", "application_type", "server_name"]
if sort in sort_fields + ["-{:s}".format(f) for f in sort_fields]:
servers = servers.order_by(sort)
paginated = Paginator(servers, PAGE_LENGTH)
data = {"servers": paginated.page(page), "sort": sort}
return TemplateResponse(request, "settings/federation.html", data) return TemplateResponse(request, "settings/federation.html", data)
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.control_federation", raise_exception=True),
name="dispatch",
)
class FederatedServer(View):
""" views for handling a specific federated server """
def get(self, request, server):
""" load a server """
server = get_object_or_404(models.FederatedServer, id=server)
users = server.user_set
data = {
"server": server,
"users": users,
"reports": models.Report.objects.filter(user__in=users.all()),
"followed_by_us": users.filter(followers__local=True),
"followed_by_them": users.filter(following__local=True),
"blocked_by_us": models.UserBlocks.objects.filter(
user_subject__in=users.all()
),
}
return TemplateResponse(request, "settings/federated_server.html", data)

View file

@ -101,6 +101,7 @@ class ManageInviteRequests(View):
data = { data = {
"ignored": ignored, "ignored": ignored,
"count": paginated.count,
"requests": paginated.page(page), "requests": paginated.page(page),
} }
return TemplateResponse(request, "settings/manage_invite_requests.html", data) return TemplateResponse(request, "settings/manage_invite_requests.html", data)

View file

@ -24,10 +24,18 @@ class Reports(View):
def get(self, request): def get(self, request):
""" view current reports """ """ view current reports """
filters = {}
resolved = request.GET.get("resolved") == "true" resolved = request.GET.get("resolved") == "true"
server = request.GET.get("server")
if server:
server = get_object_or_404(models.FederatedServer, id=server)
filters["user__federated_server"] = server
filters["resolved"] = resolved
data = { data = {
"resolved": resolved, "resolved": resolved,
"reports": models.Report.objects.filter(resolved=resolved), "server": server,
"reports": models.Report.objects.filter(**filters),
} }
return TemplateResponse(request, "moderation/reports.html", data) return TemplateResponse(request, "moderation/reports.html", data)

View file

@ -0,0 +1,50 @@
""" manage user """
from django.contrib.auth.decorators import login_required, permission_required
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm import models
from bookwyrm.settings import PAGE_LENGTH
# pylint: disable= no-self-use
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.moderate_users", raise_exception=True),
name="dispatch",
)
class UserAdmin(View):
""" admin view of users on this server """
def get(self, request):
""" list of users """
try:
page = int(request.GET.get("page", 1))
except ValueError:
page = 1
filters = {}
server = request.GET.get("server")
if server:
server = get_object_or_404(models.FederatedServer, id=server)
filters["federated_server"] = server
users = models.User.objects.filter(**filters).all()
sort = request.GET.get("sort", "-created_date")
sort_fields = [
"created_date",
"last_active_date",
"username",
"federated_server__server_name",
"is_active",
]
if sort in sort_fields + ["-{:s}".format(f) for f in sort_fields]:
users = users.order_by(sort)
paginated = Paginator(users, PAGE_LENGTH)
data = {"users": paginated.page(page), "sort": sort, "server": server}
return TemplateResponse(request, "settings/user_admin.html", data)

5
bw-dev
View file

@ -76,7 +76,10 @@ case "$CMD" in
docker-compose exec web python manage.py collectstatic --no-input docker-compose exec web python manage.py collectstatic --no-input
docker-compose restart docker-compose restart
;; ;;
populate_feeds)
execweb python manage.py populate_streams
;;
*) *)
echo "Unrecognised command. Try: build, up, initdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, update" echo "Unrecognised command. Try: build, up, initdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, update, populate_feeds"
;; ;;
esac esac