mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-23 00:26:33 +00:00
Merge pull request #2247 from bookwyrm-social/followers-following-views
Merges follower/following views
This commit is contained in:
commit
3270d0a7d4
9 changed files with 101 additions and 98 deletions
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
50
bookwyrm/views/relationships.py
Normal file
50
bookwyrm/views/relationships.py
Normal 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")
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue