Merge pull request #2247 from bookwyrm-social/followers-following-views

Merges follower/following views
This commit is contained in:
Mouse Reeve 2022-08-02 11:59:06 -07:00 committed by GitHub
commit 3270d0a7d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 101 additions and 98 deletions

View file

@ -4,7 +4,7 @@
{% load utilities %} {% load utilities %}
{% block primary_link %}{% spaceless %} {% block primary_link %}{% spaceless %}
{% url 'user-followers' request.user.localname %} {% url 'user-relationships' request.user.localname 'followers' %}
{% endspaceless %}{% endblock %} {% endspaceless %}{% endblock %}
{% block icon %} {% block icon %}

View file

@ -4,7 +4,7 @@
{% load utilities %} {% load utilities %}
{% block primary_link %}{% spaceless %} {% block primary_link %}{% spaceless %}
{% url 'user-followers' request.user.localname %} {% url 'user-relationships' request.user.localname 'followers' %}
{% endspaceless %}{% endblock %} {% endspaceless %}{% endblock %}
{% block icon %} {% block icon %}

View file

@ -6,11 +6,11 @@
{% with user|username as username %} {% with user|username as username %}
<nav class="tabs"> <nav class="tabs">
<ul> <ul>
{% url 'user-followers' user|username as url %} {% url 'user-relationships' user|username 'followers' as url %}
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}> <li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
<a href="{{ url }}">{% trans "Followers" %}</a> <a href="{{ url }}">{% trans "Followers" %}</a>
</li> </li>
{% url 'user-following' user|username as url %} {% url 'user-relationships' user|username 'following' as url %}
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}> <li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
<a href="{{ url }}">{% trans "Following" %}</a> <a href="{{ url }}">{% trans "Following" %}</a>
</li> </li>

View file

@ -23,8 +23,14 @@
<p> <p>
{% if request.user.id == user.id or admin_mode %} {% if request.user.id == user.id or admin_mode %}
<a href="{% url 'user-followers' user|username %}">{% blocktrans count counter=user.followers.count %}{{ counter }} follower{% plural %}{{ counter }} followers{% endblocktrans %}</a>, <a href="{% url 'user-relationships' user|username 'followers' %}">{% blocktrans trimmed count counter=user.followers.count %}
<a href="{% url 'user-following' user|username %}">{% blocktrans with counter=user.following.count %}{{ counter }} following{% endblocktrans %}</a> {{ counter }} follower
{% plural %}
{{ counter }} followers
{% endblocktrans %}</a>,
<a href="{% url 'user-relationships' user|username 'following' %}">{% blocktrans trimmed with counter=user.following.count %}
{{ counter }} following
{% endblocktrans %}</a>
{% elif request.user.is_authenticated %} {% elif request.user.is_authenticated %}
@ -34,7 +40,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% mutuals_count user as mutuals %} {% mutuals_count user as mutuals %}
<a href="{% url 'user-followers' user|username %}"> <a href="{% url 'user-relationships' user|username 'followers' %}">
{% if mutuals %} {% if mutuals %}
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %} {% 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 %} {% elif request.user in user.following.all %}

View file

@ -116,30 +116,38 @@ class UserViews(TestCase):
def test_followers_page(self): def test_followers_page(self):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Followers.as_view() view = views.Relationships.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
with patch("bookwyrm.views.user.is_api_request") as is_api: with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False is_api.return_value = False
result = view(request, "mouse") result = view(request, "mouse", "followers")
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
validate_html(result.render()) validate_html(result.render())
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
with patch("bookwyrm.views.user.is_api_request") as is_api: def test_followers_page_ap(self):
"""JSON response"""
view = views.Relationships.as_view()
request = self.factory.get("")
request.user = self.local_user
with patch("bookwyrm.views.relationships.is_api_request") as is_api:
is_api.return_value = True is_api.return_value = True
result = view(request, "mouse") result = view(request, "mouse", "followers")
self.assertIsInstance(result, ActivitypubResponse) self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_followers_page_anonymous(self): def test_followers_page_anonymous(self):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Followers.as_view() view = views.Relationships.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.anonymous_user request.user = self.anonymous_user
with patch("bookwyrm.views.user.is_api_request") as is_api: with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False is_api.return_value = False
result = view(request, "mouse") result = view(request, "mouse", "followers")
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
validate_html(result.render()) validate_html(result.render())
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
@ -148,55 +156,63 @@ class UserViews(TestCase):
@patch("bookwyrm.activitystreams.populate_stream_task.delay") @patch("bookwyrm.activitystreams.populate_stream_task.delay")
def test_followers_page_blocked(self, *_): def test_followers_page_blocked(self, *_):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Followers.as_view() view = views.Relationships.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
self.rat.blocks.add(self.local_user) self.rat.blocks.add(self.local_user)
with patch("bookwyrm.views.user.is_api_request") as is_api: with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False is_api.return_value = False
with self.assertRaises(Http404): with self.assertRaises(Http404):
view(request, "rat") view(request, "rat", "followers")
def test_following_page(self): def test_following_page(self):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Following.as_view() view = views.Relationships.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
with patch("bookwyrm.views.user.is_api_request") as is_api: with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False is_api.return_value = False
result = view(request, "mouse") result = view(request, "mouse", "following")
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
validate_html(result.render()) validate_html(result.render())
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
with patch("bookwyrm.views.user.is_api_request") as is_api: def test_following_page_json(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.Relationships.as_view()
request = self.factory.get("")
request.user = self.local_user
with patch("bookwyrm.views.relationships.is_api_request") as is_api:
is_api.return_value = True is_api.return_value = True
result = view(request, "mouse") result = view(request, "mouse", "following")
self.assertIsInstance(result, ActivitypubResponse) self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_following_page_anonymous(self): def test_following_page_anonymous(self):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Following.as_view() view = views.Relationships.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.anonymous_user request.user = self.anonymous_user
with patch("bookwyrm.views.user.is_api_request") as is_api: with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False is_api.return_value = False
result = view(request, "mouse") result = view(request, "mouse", "following")
self.assertIsInstance(result, TemplateResponse) self.assertIsInstance(result, TemplateResponse)
validate_html(result.render()) validate_html(result.render())
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
def test_following_page_blocked(self): def test_following_page_blocked(self):
"""there are so many views, this just makes sure it LOADS""" """there are so many views, this just makes sure it LOADS"""
view = views.Following.as_view() view = views.Relationships.as_view()
request = self.factory.get("") request = self.factory.get("")
request.user = self.local_user request.user = self.local_user
self.rat.blocks.add(self.local_user) self.rat.blocks.add(self.local_user)
with patch("bookwyrm.views.user.is_api_request") as is_api: with patch("bookwyrm.views.user.is_api_request") as is_api:
is_api.return_value = False is_api.return_value = False
with self.assertRaises(Http404): with self.assertRaises(Http404):
view(request, "rat") view(request, "rat", "following")
def test_hide_suggestions(self): def test_hide_suggestions(self):
"""update suggestions settings""" """update suggestions settings"""

View file

@ -376,14 +376,9 @@ urlpatterns = [
re_path(rf"^@(?P<username>{regex.USERNAME})$", views.user_redirect), re_path(rf"^@(?P<username>{regex.USERNAME})$", views.user_redirect),
re_path(rf"{USER_PATH}/rss/?$", views.rss_feed.RssFeed(), name="user-rss"), re_path(rf"{USER_PATH}/rss/?$", views.rss_feed.RssFeed(), name="user-rss"),
re_path( re_path(
rf"{USER_PATH}/followers(.json)?/?$", rf"{USER_PATH}/(?P<direction>(followers|following))(.json)?/?$",
views.Followers.as_view(), views.Relationships.as_view(),
name="user-followers", name="user-relationships",
),
re_path(
rf"{USER_PATH}/following(.json)?/?$",
views.Following.as_view(),
name="user-following",
), ),
re_path(r"^hide-suggestions/?$", views.hide_suggestions, name="hide-suggestions"), re_path(r"^hide-suggestions/?$", views.hide_suggestions, name="hide-suggestions"),
# groups # groups

View file

@ -127,14 +127,8 @@ from .setup import InstanceConfig, CreateAdmin
from .status import CreateStatus, EditStatus, DeleteStatus, update_progress from .status import CreateStatus, EditStatus, DeleteStatus, update_progress
from .status import edit_readthrough from .status import edit_readthrough
from .updates import get_notification_count, get_unread_status_string from .updates import get_notification_count, get_unread_status_string
from .user import ( from .user import User, hide_suggestions, user_redirect, toggle_guided_tour
User, from .relationships import Relationships
Followers,
Following,
hide_suggestions,
user_redirect,
toggle_guided_tour,
)
from .wellknown import * from .wellknown import *
from .annual_summary import ( from .annual_summary import (
AnnualSummary, AnnualSummary,

View file

@ -0,0 +1,50 @@
""" Following and followers lists """
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator
from django.db.models import Q, Count
from django.template.response import TemplateResponse
from django.views import View
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import get_user_from_username, is_api_request
# pylint: disable=no-self-use
class Relationships(View):
"""list of followers/following view"""
def get(self, request, username, direction):
"""list of followers"""
user = get_user_from_username(request.user, username)
if is_api_request(request):
if direction == "followers":
return ActivitypubResponse(user.to_followers_activity(**request.GET))
return ActivitypubResponse(user.to_following_activity(**request.GET))
if user.hide_follows and user != request.user:
raise PermissionDenied()
annotation_queryset = (
user.followers if direction == "followers" else user.following
)
follows = annotate_if_follows(request.user, annotation_queryset)
paginated = Paginator(follows.all(), PAGE_LENGTH)
data = {
"user": user,
"is_self": request.user.id == user.id,
"follow_list": paginated.get_page(request.GET.get("page")),
}
return TemplateResponse(request, f"user/relationships/{direction}.html", data)
def annotate_if_follows(user, queryset):
"""Sort a list of users by if you follow them"""
if not user.is_authenticated:
return queryset.order_by("-created_date")
return queryset.annotate(
request_user_follows=Count("followers", filter=Q(followers=user))
).order_by("-request_user_follows", "-created_date")

View file

@ -1,8 +1,6 @@
""" non-interactive pages """ """ The user profile """
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Q, Count
from django.http import Http404 from django.http import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@ -102,62 +100,6 @@ class User(View):
return TemplateResponse(request, "user/user.html", data) return TemplateResponse(request, "user/user.html", data)
class Followers(View):
"""list of followers view"""
def get(self, request, username):
"""list of followers"""
user = get_user_from_username(request.user, username)
if is_api_request(request):
return ActivitypubResponse(user.to_followers_activity(**request.GET))
if user.hide_follows and user != request.user:
raise PermissionDenied()
followers = annotate_if_follows(request.user, user.followers)
paginated = Paginator(followers.all(), PAGE_LENGTH)
data = {
"user": user,
"is_self": request.user.id == user.id,
"follow_list": paginated.get_page(request.GET.get("page")),
}
return TemplateResponse(request, "user/relationships/followers.html", data)
class Following(View):
"""list of following view"""
def get(self, request, username):
"""list of followers"""
user = get_user_from_username(request.user, username)
if is_api_request(request):
return ActivitypubResponse(user.to_following_activity(**request.GET))
if user.hide_follows and user != request.user:
raise PermissionDenied()
following = annotate_if_follows(request.user, user.following)
paginated = Paginator(following.all(), PAGE_LENGTH)
data = {
"user": user,
"is_self": request.user.id == user.id,
"follow_list": paginated.get_page(request.GET.get("page")),
}
return TemplateResponse(request, "user/relationships/following.html", data)
def annotate_if_follows(user, queryset):
"""Sort a list of users by if you follow them"""
if not user.is_authenticated:
return queryset.order_by("-created_date")
return queryset.annotate(
request_user_follows=Count("followers", filter=Q(followers=user))
).order_by("-request_user_follows", "-created_date")
@require_POST @require_POST
@login_required @login_required
def hide_suggestions(request): def hide_suggestions(request):