mirror of
https://github.com/jointakahe/takahe.git
synced 2024-11-22 07:10:59 +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 {
|
||||
margin: 0 0 10px 3px;
|
||||
margin: 0 0 10px 0px;
|
||||
}
|
||||
|
||||
.view-options.follows {
|
||||
margin: 0 0 20px 0px;
|
||||
}
|
||||
|
||||
.view-options a {
|
||||
|
|
|
@ -161,6 +161,8 @@ urlpatterns = [
|
|||
path("@<handle>/action/", identity.ActionIdentity.as_view()),
|
||||
path("@<handle>/rss/", identity.IdentityFeed()),
|
||||
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
|
||||
path("compose/", compose.Compose.as_view(), name="compose"),
|
||||
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 identity == request.identity %}
|
||||
<form class="inline follow">
|
||||
<form class="inline follow-profile">
|
||||
<a class="button" href="{% url "settings_profile" %}">Edit Profile</a>
|
||||
</form>
|
||||
{% else %}
|
||||
|
@ -72,13 +72,11 @@
|
|||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if identity.config_identity.visible_follows %}
|
||||
<div class="stats">
|
||||
<ul>
|
||||
<li><strong>{{ following_count }}</strong> following</li>
|
||||
<li><strong>{{ followers_count }}</strong> follower{{ followers_count|pluralize }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% if identity.local and identity.config_identity.visible_follows %}
|
||||
<div class="view-options follows">
|
||||
<a href="{{ identity.urls.following }}" {% if not inbound or not follows_page %}class="selected"{% endif %}><strong>{{ following_count }}</strong> following</a>
|
||||
<a href="{{ identity.urls.followers }}" {% if inbound or not follows_page %}class="selected"{% endif %}><strong>{{ followers_count }}</strong> follower{{ followers_count|pluralize }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not identity.local %}
|
||||
|
@ -94,24 +92,28 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% for post in page_obj %}
|
||||
{% 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.
|
||||
{% block subcontent %}
|
||||
|
||||
{% if identity.profile_uri %}
|
||||
You might find historical posts at
|
||||
<a href="{{ identity.profile_uri }}">their original profile ➔</a>
|
||||
{% for post in page_obj %}
|
||||
{% 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 %}
|
||||
You might find historical posts at
|
||||
<a href="{{ identity.profile_uri }}">their original profile ➔</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<div class="load-more"><a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a></div>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<div class="load-more"><a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a></div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -21,6 +21,9 @@ def test_visible_follows_disabled(client, identity):
|
|||
"""
|
||||
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)
|
||||
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):
|
||||
view = "/@{self.username}@{self.domain_id}/"
|
||||
action = "{view}action/"
|
||||
followers = "{view}followers/"
|
||||
following = "{view}following/"
|
||||
activate = "{view}activate/"
|
||||
admin = "/admin/identities/"
|
||||
admin_edit = "{admin}{self.pk}/"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from typing import cast
|
||||
|
||||
from django.db import models
|
||||
|
||||
from users.models import Follow, FollowStates, Identity
|
||||
|
||||
|
||||
|
@ -11,6 +13,16 @@ class IdentityService:
|
|||
def __init__(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:
|
||||
"""
|
||||
Follows a user (or does nothing if already followed).
|
||||
|
|
|
@ -29,7 +29,7 @@ class ViewIdentity(ListView):
|
|||
"""
|
||||
|
||||
template_name = "identity/view.html"
|
||||
paginate_by = 5
|
||||
paginate_by = 25
|
||||
|
||||
def get(self, request, handle):
|
||||
# Make sure we understand this handle
|
||||
|
@ -140,6 +140,45 @@ class IdentityFeed(Feed):
|
|||
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")
|
||||
class ActionIdentity(View):
|
||||
def post(self, request, handle):
|
||||
|
|
Loading…
Reference in a new issue