mirror of
https://github.com/jointakahe/takahe.git
synced 2024-11-22 15:21:01 +00:00
Add public following/followers pages
This commit is contained in:
parent
932cfe9243
commit
c9794c0fcf
8 changed files with 116 additions and 28 deletions
|
@ -904,7 +904,11 @@ table.metadata td .emoji {
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-options {
|
.view-options {
|
||||||
margin: 0 0 10px 3px;
|
margin: 0 0 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-options.follows {
|
||||||
|
margin: 0 0 20px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-options a {
|
.view-options a {
|
||||||
|
|
|
@ -161,6 +161,8 @@ urlpatterns = [
|
||||||
path("@<handle>/action/", identity.ActionIdentity.as_view()),
|
path("@<handle>/action/", identity.ActionIdentity.as_view()),
|
||||||
path("@<handle>/rss/", identity.IdentityFeed()),
|
path("@<handle>/rss/", identity.IdentityFeed()),
|
||||||
path("@<handle>/report/", report.SubmitReport.as_view()),
|
path("@<handle>/report/", report.SubmitReport.as_view()),
|
||||||
|
path("@<handle>/following/", identity.IdentityFollows.as_view(inbound=False)),
|
||||||
|
path("@<handle>/followers/", identity.IdentityFollows.as_view(inbound=True)),
|
||||||
# Posts
|
# Posts
|
||||||
path("compose/", compose.Compose.as_view(), name="compose"),
|
path("compose/", compose.Compose.as_view(), name="compose"),
|
||||||
path(
|
path(
|
||||||
|
|
24
templates/identity/follows.html
Normal file
24
templates/identity/follows.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "identity/view.html" %}
|
||||||
|
|
||||||
|
{% block title %}{% if self.inbound %}Followers{% else %}Following{% endif %} - {{ identity }}{% endblock %}
|
||||||
|
|
||||||
|
{% block subcontent %}
|
||||||
|
|
||||||
|
{% for identity in page_obj %}
|
||||||
|
{% include "activities/_identity.html" %}
|
||||||
|
{% empty %}
|
||||||
|
<span class="empty">
|
||||||
|
This person has no {% if self.inbound %}followers{% else %}follows{% endif %} yet.
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="load-more">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
{% if request.identity %}
|
{% if request.identity %}
|
||||||
{% if identity == request.identity %}
|
{% if identity == request.identity %}
|
||||||
<form class="inline follow">
|
<form class="inline follow-profile">
|
||||||
<a class="button" href="{% url "settings_profile" %}">Edit Profile</a>
|
<a class="button" href="{% url "settings_profile" %}">Edit Profile</a>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -72,13 +72,11 @@
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if identity.config_identity.visible_follows %}
|
{% if identity.local and identity.config_identity.visible_follows %}
|
||||||
<div class="stats">
|
<div class="view-options follows">
|
||||||
<ul>
|
<a href="{{ identity.urls.following }}" {% if not inbound or not follows_page %}class="selected"{% endif %}><strong>{{ following_count }}</strong> following</a>
|
||||||
<li><strong>{{ following_count }}</strong> following</li>
|
<a href="{{ identity.urls.followers }}" {% if inbound or not follows_page %}class="selected"{% endif %}><strong>{{ followers_count }}</strong> follower{{ followers_count|pluralize }}</a>
|
||||||
<li><strong>{{ followers_count }}</strong> follower{{ followers_count|pluralize }}</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not identity.local %}
|
{% if not identity.local %}
|
||||||
|
@ -94,24 +92,28 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for post in page_obj %}
|
{% block subcontent %}
|
||||||
{% include "activities/_post.html" %}
|
|
||||||
{% empty %}
|
|
||||||
<span class="empty">
|
|
||||||
{% if identity.local %}
|
|
||||||
No posts yet.
|
|
||||||
{% else %}
|
|
||||||
No posts have been received/retrieved by this server yet.
|
|
||||||
|
|
||||||
{% if identity.profile_uri %}
|
{% for post in page_obj %}
|
||||||
You might find historical posts at
|
{% include "activities/_post.html" %}
|
||||||
<a href="{{ identity.profile_uri }}">their original profile ➔</a>
|
{% empty %}
|
||||||
|
<span class="empty">
|
||||||
|
{% if identity.local %}
|
||||||
|
No posts yet.
|
||||||
|
{% else %}
|
||||||
|
No posts have been received/retrieved by this server yet.
|
||||||
|
|
||||||
|
{% if identity.profile_uri %}
|
||||||
|
You might find historical posts at
|
||||||
|
<a href="{{ identity.profile_uri }}">their original profile ➔</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
</span>
|
||||||
</span>
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<div class="load-more"><a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a></div>
|
<div class="load-more"><a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -21,6 +21,9 @@ def test_visible_follows_disabled(client, identity):
|
||||||
"""
|
"""
|
||||||
Tests that disabling visible follows hides it from profile
|
Tests that disabling visible follows hides it from profile
|
||||||
"""
|
"""
|
||||||
|
Config.set_identity(identity, "visible_follows", True)
|
||||||
|
response = client.get(identity.urls.view)
|
||||||
|
assertContains(response, '<div class="view-options follows">', status_code=200)
|
||||||
Config.set_identity(identity, "visible_follows", False)
|
Config.set_identity(identity, "visible_follows", False)
|
||||||
response = client.get(identity.urls.view)
|
response = client.get(identity.urls.view)
|
||||||
assertNotContains(response, '<div class="stats">', status_code=200)
|
assertNotContains(response, '<div class="view-options follows">', status_code=200)
|
||||||
|
|
|
@ -219,6 +219,8 @@ class Identity(StatorModel):
|
||||||
class urls(urlman.Urls):
|
class urls(urlman.Urls):
|
||||||
view = "/@{self.username}@{self.domain_id}/"
|
view = "/@{self.username}@{self.domain_id}/"
|
||||||
action = "{view}action/"
|
action = "{view}action/"
|
||||||
|
followers = "{view}followers/"
|
||||||
|
following = "{view}following/"
|
||||||
activate = "{view}activate/"
|
activate = "{view}activate/"
|
||||||
admin = "/admin/identities/"
|
admin = "/admin/identities/"
|
||||||
admin_edit = "{admin}{self.pk}/"
|
admin_edit = "{admin}{self.pk}/"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
from users.models import Follow, FollowStates, Identity
|
from users.models import Follow, FollowStates, Identity
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +13,16 @@ class IdentityService:
|
||||||
def __init__(self, identity: Identity):
|
def __init__(self, identity: Identity):
|
||||||
self.identity = identity
|
self.identity = identity
|
||||||
|
|
||||||
|
def following(self) -> models.QuerySet[Identity]:
|
||||||
|
return Identity.objects.filter(
|
||||||
|
inbound_follows__source=self.identity
|
||||||
|
).not_deleted()
|
||||||
|
|
||||||
|
def followers(self) -> models.QuerySet[Identity]:
|
||||||
|
return Identity.objects.filter(
|
||||||
|
outbound_follows__target=self.identity
|
||||||
|
).not_deleted()
|
||||||
|
|
||||||
def follow_from(self, from_identity: Identity) -> Follow:
|
def follow_from(self, from_identity: Identity) -> Follow:
|
||||||
"""
|
"""
|
||||||
Follows a user (or does nothing if already followed).
|
Follows a user (or does nothing if already followed).
|
||||||
|
|
|
@ -29,7 +29,7 @@ class ViewIdentity(ListView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template_name = "identity/view.html"
|
template_name = "identity/view.html"
|
||||||
paginate_by = 5
|
paginate_by = 25
|
||||||
|
|
||||||
def get(self, request, handle):
|
def get(self, request, handle):
|
||||||
# Make sure we understand this handle
|
# Make sure we understand this handle
|
||||||
|
@ -140,6 +140,45 @@ class IdentityFeed(Feed):
|
||||||
return item.published
|
return item.published
|
||||||
|
|
||||||
|
|
||||||
|
class IdentityFollows(ListView):
|
||||||
|
"""
|
||||||
|
Shows following/followers for an identity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name = "identity/follows.html"
|
||||||
|
paginate_by = 25
|
||||||
|
inbound = False
|
||||||
|
|
||||||
|
def get(self, request, handle):
|
||||||
|
self.identity = by_handle_or_404(
|
||||||
|
self.request,
|
||||||
|
handle,
|
||||||
|
local=False,
|
||||||
|
)
|
||||||
|
if not Config.load_identity(self.identity).visible_follows:
|
||||||
|
raise Http404("Hidden follows")
|
||||||
|
return super().get(request, identity=self.identity)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.inbound:
|
||||||
|
return IdentityService(self.identity).followers()
|
||||||
|
else:
|
||||||
|
return IdentityService(self.identity).following()
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
context = super().get_context_data()
|
||||||
|
context["identity"] = self.identity
|
||||||
|
context["inbound"] = self.inbound
|
||||||
|
context["follows_page"] = True
|
||||||
|
context["followers_count"] = self.identity.inbound_follows.filter(
|
||||||
|
state__in=FollowStates.group_active()
|
||||||
|
).count()
|
||||||
|
context["following_count"] = self.identity.outbound_follows.filter(
|
||||||
|
state__in=FollowStates.group_active()
|
||||||
|
).count()
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(identity_required, name="dispatch")
|
@method_decorator(identity_required, name="dispatch")
|
||||||
class ActionIdentity(View):
|
class ActionIdentity(View):
|
||||||
def post(self, request, handle):
|
def post(self, request, handle):
|
||||||
|
|
Loading…
Reference in a new issue