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
import redis
@ -12,13 +12,8 @@ r = redis.Redis(
)
def erase_feeds():
""" throw the whole redis away """
r.flushall()
def create_feeds():
""" build all the fields for all the users """
def populate_streams():
""" build all the streams for all the users """
users = models.User.objects.filter(
local=True,
is_active=True,
@ -29,11 +24,10 @@ def create_feeds():
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
def handle(self, *args, **options):
""" run feed builder """
erase_feeds()
create_feeds()
populate_streams()

View file

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

View file

@ -1,8 +1,21 @@
{% extends 'settings/admin_layout.html' %}
{% load i18n %}
{% block title %}{% trans "Reports" %}{% endblock %}
{% block header %}{% trans "Reports" %}{% endblock %}
{% block title %}
{% 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 %}
<div class="tabs">
@ -17,6 +30,10 @@
</div>
<div class="block">
{% if not reports %}
<em>{% trans "No reports found." %}</em>
{% endif %}
{% for report in reports %}
<div class="block">
{% include 'moderation/report_preview.html' with report=report %}

View file

@ -14,6 +14,10 @@
{% if perms.bookwyrm.create_invites %}
<h2 class="menu-label">{% trans "Manage Users" %}</h2>
<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>
{% url 'settings-invite-requests' as 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' %}
{% load i18n %}
{% block title %}{% trans "Federated Servers" %}{% endblock %}
{% block header %}{% trans "Federated Servers" %}{% endblock %}
@ -7,17 +8,30 @@
<table class="table is-striped">
<tr>
<th>{% trans "Server name" %}</th>
<th>{% trans "Software" %}</th>
{% url 'settings-federation' as url %}
<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>
</tr>
{% for server in servers %}
<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.status }}</td>
</tr>
{% endfor %}
</table>
{% include 'snippets/pagination.html' with page=servers path=request.path %}
{% endblock %}

View file

@ -23,7 +23,7 @@
{% trans "Ignored Invite Requests" %}
{% else %}
{% trans "Invite Requests" %}
{% endif %}
{% endif %} ({{ count }})
</h2>
<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 bookwyrm_tags %}
{% if books|length > 0 %}
<div class="table-container">
<div class="scroll-x">
<table class="table is-striped is-fullwidth">
<tr class="book-preview">

View file

@ -28,7 +28,7 @@
{% if status.quote %}
<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>
</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.client import RequestFactory
from bookwyrm import models
from bookwyrm import views
from bookwyrm import models, views
class FederationViews(TestCase):
@ -32,3 +31,16 @@ class FederationViews(TestCase):
self.assertIsInstance(result, TemplateResponse)
result.render()
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,
name="settings-email-preview",
),
re_path(r"^settings/users", views.UserAdmin.as_view(), name="settings-users"),
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(
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 .directory import Directory
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 .follow import follow, unfollow
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 .updates import get_notification_count, get_unread_status_count
from .user import User, EditUser, Followers, Following
from .user_admin import UserAdmin
from .wellknown import webfinger, nodeinfo_pointer, nodeinfo, instance_info, peers

View file

@ -1,10 +1,13 @@
""" manage federated servers """
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
@ -17,7 +20,44 @@ class Federation(View):
""" what servers do we federate with """
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()
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)
@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 = {
"ignored": ignored,
"count": paginated.count,
"requests": paginated.page(page),
}
return TemplateResponse(request, "settings/manage_invite_requests.html", data)

View file

@ -24,10 +24,18 @@ class Reports(View):
def get(self, request):
""" view current reports """
filters = {}
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 = {
"resolved": resolved,
"reports": models.Report.objects.filter(resolved=resolved),
"server": server,
"reports": models.Report.objects.filter(**filters),
}
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 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