local mute and block

This commit is contained in:
Your Name 2023-07-07 16:54:15 -04:00 committed by Henri Dickson
parent 8feebf8e5a
commit d601dde535
19 changed files with 621 additions and 112 deletions

View file

@ -135,6 +135,10 @@ details {
color: var(--pico-primary);
opacity: 1;
}
.tag-list a:not(:hover) {
color: white;
}
}
form img {

View file

@ -32,7 +32,6 @@
</section>
{% endif %}
{% if show_profile %}
{% current_user_relationship user as relationship %}
<section class="profile">
<article>
<details class="auto-collapse" open>
@ -56,46 +55,21 @@
</a>
</div>
</hgroup>
{% if relationship.status %}
<div class="tag-list">
<span><a>{{ relationship.status }}</a></span>
</div>
{% endif %}
</div>
</div>
</summary>
<span class="action">
{% if user.locked %}
<span>
<a title="用户已开启关注审核"
hx-post="{% url 'users:locked' user.handler %}"
hx-target="body"
hx-swap="beforeend">
<i class="fa-solid fa-user-shield"></i>
</a>
</span>
{% elif user != request.user %}
{% if not relationship.following %}
<span class="action" id="profile_actions">
{% if user == request.user %}
{% if user.locked %}
<span>
<a title="关注ta"
hx-post="{% url 'users:follow' user.handler %}"
hx-swap="outerHTML">
<i class="fa-solid fa-user-plus"></i>
<a title="你已开启关注审核">
<i class="fa-solid fa-user-shield"></i>
</a>
</span>
{% elif relationship.unfollowable %}
<span>
<a title="取消关注"
hx-post="{% url 'users:unfollow' user.handler %}"
hx-swap="outerHTML">
<i class="fa-solid fa-user-xmark"></i>
</a>
</span>
{% else %}
<span><a title="已在联邦宇宙关注了ta"><i class="fa-solid fa-user-check"></i></a></span>
{% endif %}
{% else %}
{% include 'users/profile_actions.html' %}
{% endif %}
{% comment %} <span><a href="{% url 'users:report' %}?user_id={{ user.id }}">{% trans '投诉用户' %}</a></span> {% endcomment %}
</span>
<p>{{ user.mastodon_account.note|bleach:"a,p,span,br" }}</p>
</details>

View file

@ -18,26 +18,28 @@ def current_user_relationship(context, user):
current_user = context["request"].user
r = {
"following": False,
"followable": False,
"unfollowable": False,
"muting": False,
"unmutable": False,
"rejecting": False,
"status": "",
}
if (
current_user
and current_user.is_authenticated
and current_user != user
and not current_user.is_blocked_by(user)
):
if current_user.is_following(user):
r["following"] = True
if user in current_user.local_following.all():
r["unfollowable"] = True
if current_user.is_followed_by(user):
r["status"] = _("互相关注")
else:
r["status"] = _("已关注")
if current_user and current_user.is_authenticated and current_user != user:
if current_user.is_blocking(user) or user.is_blocking(current_user):
r["rejecting"] = True
else:
r["followable"] = True
if current_user.is_followed_by(user):
r["status"] = _("被ta关注")
r["muting"] = current_user.is_muting(user)
if user in current_user.local_muting.all():
r["unmutable"] = current_user
if current_user.is_following(user):
r["following"] = True
if user in current_user.local_following.all():
r["unfollowable"] = True
if current_user.is_followed_by(user):
r["status"] = _("互相关注")
else:
r["status"] = _("已关注")
else:
if current_user.is_followed_by(user):
r["status"] = _("被ta关注")
return r

View file

@ -17,11 +17,7 @@ class UserOwnedObjectMixin:
return self.visibility == 0
if self.visibility == 2:
return False
if (
viewer.is_blocking(owner)
or owner.is_blocking(viewer)
or viewer.is_muting(owner)
):
if viewer.is_blocking(owner) or owner.is_blocking(viewer):
return False
if self.visibility == 1:
return viewer.is_following(owner)

View file

@ -61,7 +61,7 @@ def query_visible(user):
Q(visibility=0)
| Q(owner_id__in=user.following if user.is_authenticated else [], visibility=1)
| Q(owner_id=user.id)
)
) & ~Q(owner_id__in=user.ignoring)
def query_following(user):

View file

@ -55,7 +55,8 @@ class ActivityManager:
self.owner = user
def get_timeline(self, before_time=None):
q = Q(owner_id__in=self.owner.following, visibility__lt=2) | Q(owner=self.owner)
following = [x for x in self.owner.following if x not in self.owner.ignoring]
q = Q(owner_id__in=following, visibility__lt=2) | Q(owner=self.owner)
if before_time:
q = q & Q(created_time__lt=before_time)
return (

View file

@ -42,7 +42,7 @@ class SocialTest(TestCase):
# bob follows alice, see 2 activities
self.bob.mastodon_following = ["Alice@MySpace"]
self.alice.mastodon_follower = ["Bob@KKCity"]
self.bob.following = self.bob.get_following_ids()
self.bob.following = self.bob.merge_following_ids()
timeline2 = self.bob.activity_manager.get_timeline()
self.assertEqual(len(timeline2), 2)

View file

@ -123,7 +123,7 @@ def sync_mastodon(request):
refresh_mastodon_data_task, request.user.pk
)
messages.add_message(request, messages.INFO, _("同步已开始。"))
return redirect(reverse("users:data"))
return redirect(reverse("users:info"))
@login_required

View file

@ -11,7 +11,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
count = 0
for user in tqdm(User.objects.all()):
user.following = user.get_following_ids()
user.following = user.merge_following_ids()
if user.following:
count += 1
user.save(update_fields=["following"])

View file

@ -1,30 +1,9 @@
from django.core.management.base import BaseCommand
from users.models import User
from datetime import timedelta
from django.utils import timezone
from tqdm import tqdm
from users.tasks import refresh_all_mastodon_data_task
class Command(BaseCommand):
help = "Refresh Mastodon data for all users if not updated in last 24h"
def handle(self, *args, **options):
count = 0
for user in tqdm(
User.objects.filter(
mastodon_last_refresh__lt=timezone.now() - timedelta(hours=24),
is_active=True,
)
):
if user.mastodon_token or user.mastodon_refresh_token:
tqdm.write(f"Refreshing {user}")
if user.refresh_mastodon_data():
tqdm.write(f"Refreshed {user}")
count += 1
else:
tqdm.write(f"Refresh failed for {user}")
user.save()
else:
tqdm.write(f"Missing token for {user}")
print(f"{count} users updated")
refresh_all_mastodon_data_task(24)

View file

@ -0,0 +1,104 @@
# Generated by Django 4.2.3 on 2023-07-07 07:22
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("users", "0009_add_local_follow"),
]
operations = [
migrations.AddField(
model_name="user",
name="muting",
field=models.JSONField(default=list),
),
migrations.AddField(
model_name="user",
name="rejecting",
field=models.JSONField(default=list),
),
migrations.CreateModel(
name="Mute",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_time", models.DateTimeField(auto_now_add=True)),
("edited_time", models.DateTimeField(auto_now=True)),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"target",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.CreateModel(
name="Block",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_time", models.DateTimeField(auto_now_add=True)),
("edited_time", models.DateTimeField(auto_now=True)),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"target",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.AddField(
model_name="user",
name="local_blocking",
field=models.ManyToManyField(
related_name="local_blocked_by",
through="users.Block",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AddField(
model_name="user",
name="local_muting",
field=models.ManyToManyField(
related_name="+", through="users.Mute", to=settings.AUTH_USER_MODEL
),
),
]

View file

@ -1,3 +1,4 @@
from functools import cached_property
import re
from django.core import validators
from django.core.exceptions import ValidationError
@ -13,7 +14,8 @@ from django.conf import settings
from management.models import Announcement
from mastodon.api import *
from django.urls import reverse
from django.db.models import Q
from django.db.models import Q, F, Value
from django.db.models.functions import Concat
from django.templatetags.static import static
import hashlib
@ -78,8 +80,24 @@ class User(AbstractUser):
symmetrical=False,
related_name="local_followers",
)
local_blocking = models.ManyToManyField(
through="Block",
to="self",
through_fields=("owner", "target"),
symmetrical=False,
related_name="local_blocked_by",
)
local_muting = models.ManyToManyField(
through="Mute",
to="self",
through_fields=("owner", "target"),
symmetrical=False,
related_name="+",
)
following = models.JSONField(default=list)
# followers = models.JSONField(default=list)
muting = models.JSONField(default=list)
# rejecting = local/external blocking + local/external blocked_by + domain_blocking + domain_blocked_by
rejecting = models.JSONField(default=list)
mastodon_id = models.CharField(max_length=100, default=None, null=True)
mastodon_username = models.CharField(max_length=100, default=None, null=True)
mastodon_site = models.CharField(max_length=100, default=None, null=True)
@ -127,7 +145,7 @@ class User(AbstractUser):
),
]
@property
@cached_property
def mastodon_acct(self):
return (
f"{self.mastodon_username}@{self.mastodon_site}"
@ -171,30 +189,98 @@ class User(AbstractUser):
def __str__(self):
return f'{self.pk}:{self.username or ""}:{self.mastodon_acct}'
@property
def ignoring(self):
return self.muting + self.rejecting
def follow(self, target: "User"):
if (
target is None
or target.pk in self.following
or target.locked
or self.mastodon_acct in target.mastodon_blocks
or target.mastodon_acct in self.mastodon_blocks
or self.is_following(target)
or self.is_blocking(target)
or self.is_blocked_by(target)
):
return False
self.local_following.add(target)
self.following.append(target.pk)
self.save()
self.save(update_fields=["following"])
return True
def unfollow(self, target: "User"):
print(target)
print(target in self.local_following.all())
if target and target in self.local_following.all():
self.local_following.remove(target)
try:
if (
target.pk in self.following
and target.mastodon_acct not in self.mastodon_following
):
self.following.remove(target.pk)
self.save(update_fields=["following"])
return True
return False
def remove_follower(self, target: "User"):
if target is None or self not in target.local_following.all():
return False
target.local_following.remove(self)
if (
self.pk in target.following
and self.mastodon_acct not in target.mastodon_following
):
target.following.remove(self.pk)
target.save(update_fields=["following"])
return True
def block(self, target: "User"):
if target is None or target in self.local_blocking.all():
return False
self.local_blocking.add(target)
if target.pk in self.following:
self.following.remove(target.pk)
self.save(update_fields=["following"])
if self.pk in target.following:
target.following.remove(self.pk)
target.save(update_fields=["following"])
if target.pk not in self.rejecting:
self.rejecting.append(target.pk)
self.save(update_fields=["rejecting"])
if self.pk not in target.rejecting:
target.rejecting.append(self.pk)
target.save(update_fields=["rejecting"])
return True
def unblock(self, target: "User"):
if target and target in self.local_blocking.all():
self.local_blocking.remove(target)
if not self.is_blocked_by(target):
if target.pk in self.rejecting:
self.rejecting.remove(target.pk)
self.save(update_fields=["rejecting"])
if self.pk in target.rejecting:
target.rejecting.remove(self.pk)
target.save(update_fields=["rejecting"])
return True
return False
def mute(self, target: "User"):
if (
target is None
or target in self.local_muting.all()
or target.mastodon_acct in self.mastodon_mutes
):
return False
self.local_muting.add(target)
if target.pk not in self.muting:
self.muting.append(target.pk)
self.save()
return True
def unmute(self, target: "User"):
if target and target in self.local_muting.all():
self.local_muting.remove(target)
if target.pk in self.muting:
self.muting.remove(target.pk)
self.save()
except ValueError:
pass
return True
return False
@ -225,6 +311,40 @@ class User(AbstractUser):
self.mastodon_domain_blocks = []
self.mastodon_account = {}
def merge_relationships(self):
self.muting = self.merged_muting_ids()
self.rejecting = self.merged_rejecting_ids()
# caculate following after rejecting is merged
self.following = self.merged_following_ids()
@classmethod
def merge_rejected_by(cls):
"""
Caculate rejecting field to include blocked by for external users
Should be invoked after invoking merge_relationships() for all users
"""
# FIXME this is quite inifficient, should only invoked in async task
external_users = list(
cls.objects.filter(mastodon_username__isnull=False, is_active=True)
)
reject_changed = []
follow_changed = []
for u in external_users:
for v in external_users:
if v.pk in u.rejecting and u.pk not in v.rejecting:
v.rejecting.append(u.pk)
if v not in reject_changed:
reject_changed.append(v)
if u.pk in v.following:
v.following.remove(u.pk)
if v not in follow_changed:
follow_changed.append(v)
for u in reject_changed:
u.save(update_fields=["rejecting"])
for u in follow_changed:
u.save(update_fields=["following"])
return len(follow_changed) + len(reject_changed)
def refresh_mastodon_data(self):
"""Try refresh account data from mastodon server, return true if refreshed successfully, note it will not save to db"""
self.mastodon_last_refresh = timezone.now()
@ -267,14 +387,14 @@ class User(AbstractUser):
self.mastodon_domain_blocks = get_related_acct_list(
self.mastodon_site, self.mastodon_token, "/api/v1/domain_blocks"
)
self.following = self.get_following_ids()
self.merge_relationships()
updated = True
elif code == 401:
print(f"401 {self}")
self.mastodon_token = ""
return updated
def get_following_ids(self):
def merged_following_ids(self):
fl = []
for m in self.mastodon_following:
target = User.get(m)
@ -286,12 +406,52 @@ class User(AbstractUser):
for user in self.local_following.all():
if user.pk not in fl and not user.locked and not user.is_blocking(self):
fl.append(user.pk)
return fl
fl = [x for x in fl if x not in self.rejecting]
return sorted(fl)
def merged_muting_ids(self):
external_muting_user_ids = list(
User.objects.all()
.annotate(acct=Concat("mastodon_username", Value("@"), "mastodon_site"))
.filter(acct__in=self.mastodon_mutes)
.values_list("pk", flat=True)
)
l = list(
set(
external_muting_user_ids
+ list(self.local_muting.all().values_list("pk", flat=True))
)
)
return sorted(l)
def merged_rejecting_ids(self):
domain_blocked_user_ids = list(
User.objects.filter(
mastodon_site__in=self.mastodon_domain_blocks
).values_list("pk", flat=True)
)
external_blocking_user_ids = list(
User.objects.all()
.annotate(acct=Concat("mastodon_username", Value("@"), "mastodon_site"))
.filter(acct__in=self.mastodon_blocks)
.values_list("pk", flat=True)
)
l = list(
set(
domain_blocked_user_ids
+ external_blocking_user_ids
+ list(self.local_blocking.all().values_list("pk", flat=True))
+ list(self.local_blocked_by.all().values_list("pk", flat=True)) # type: ignore
+ list(self.local_muting.all().values_list("pk", flat=True))
)
)
return sorted(l)
def is_blocking(self, target):
return (
(
target.mastodon_acct in self.mastodon_blocks
target in self.local_blocking.all()
or target.mastodon_acct in self.mastodon_blocks
or target.mastodon_site in self.mastodon_domain_blocks
)
if target.is_authenticated
@ -302,7 +462,7 @@ class User(AbstractUser):
return target.is_authenticated and target.is_blocking(self)
def is_muting(self, target):
return target.mastodon_acct in self.mastodon_mutes
return target.pk in self.muting or target.mastodon_acct in self.mastodon_mutes
def is_following(self, target):
return (
@ -397,6 +557,20 @@ class Follow(models.Model):
edited_time = models.DateTimeField(auto_now=True)
class Block(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+")
target = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+")
created_time = models.DateTimeField(auto_now_add=True)
edited_time = models.DateTimeField(auto_now=True)
class Mute(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+")
target = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+")
created_time = models.DateTimeField(auto_now_add=True)
edited_time = models.DateTimeField(auto_now=True)
class Report(models.Model):
submit_user = models.ForeignKey(
User, on_delete=models.SET_NULL, related_name="sumbitted_reports", null=True

View file

@ -1,5 +1,8 @@
from django.conf import settings
from .models import User
from datetime import timedelta
from django.utils import timezone
from tqdm import tqdm
from loguru import logger
@ -15,3 +18,26 @@ def refresh_mastodon_data_task(user_id, token=None):
logger.info(f"{user} mastodon data refreshed")
else:
logger.error(f"{user} mastodon data refresh failed")
def refresh_all_mastodon_data_task(ttl_hours):
count = 0
for user in tqdm(
User.objects.filter(
mastodon_last_refresh__lt=timezone.now() - timedelta(hours=ttl_hours),
is_active=True,
)
):
if user.mastodon_token or user.mastodon_refresh_token:
tqdm.write(f"Refreshing {user}")
if user.refresh_mastodon_data():
tqdm.write(f"Refreshed {user}")
count += 1
else:
tqdm.write(f"Refresh failed for {user}")
user.save()
else:
tqdm.write(f"Missing token for {user}")
logger.info(f"{count} users updated")
c = User.merge_rejected_by()
logger.info(f"{c} users's rejecting list updated")

View file

@ -1,3 +0,0 @@
<a title="取消关注"
hx-post="{% url 'users:unfollow' user.handler %}"
hx-swap="outerHTML"><i class="fa-solid fa-user-xmark"></i></a>

View file

@ -0,0 +1,84 @@
{% load mastodon %}
{% current_user_relationship user as relationship %}
{% if relationship.rejecting %}
<span class="tag-list">
<span><a>已屏蔽</a></span>
</span>
{% else %}
{% if relationship.status %}
<span class="tag-list">
<span><a>{{ relationship.status }}</a></span>
</span>
{% endif %}
{% if relationship.following %}
{% if relationship.unfollowable %}
<span>
<a title="已关注,点击可取消关注"
class="activated"
hx-post="{% url 'users:unfollow' user.handler %}"
hx-target="#profile_actions"
hx-swap="innerHTML">
<i class="fa-solid fa-user-check"></i>
</a>
</span>
{% else %}
<span><a title="已在联邦宇宙关注该用户" class="activated"><i class="fa-solid fa-circle-check"></i></a></span>
{% endif %}
{% else %}
{% if user.locked %}
<span>
<a title="用户已开启关注审核"
hx-post="{% url 'users:locked' user.handler %}"
hx-target="body"
hx-swap="beforeend">
<i class="fa-solid fa-user-shield"></i>
</a>
</span>
{% else %}
<span>
<a title="点击可关注该用户"
hx-post="{% url 'users:follow' user.handler %}"
hx-target="#profile_actions"
hx-swap="innerHTML">
<i class="fa-solid fa-user-plus"></i>
</a>
</span>
{% endif %}
{% endif %}
{% if not relationship.muting %}
<span>
<a title="点击可忽略该用户"
hx-post="{% url 'users:mute' user.handler %}"
hx-target="#profile_actions"
hx-swap="innerHTML">
<i class="fa-solid fa-volume-high"></i>
</a>
</span>
{% elif relationship.unmutable %}
<span>
<a title="已忽略,点击可取消忽略"
class="activated"
hx-post="{% url 'users:unmute' user.handler %}"
hx-target="#profile_actions"
hx-swap="innerHTML">
<i class="fa-solid fa-volume-off"></i>
</a>
</span>
{% else %}
<span>
<a title="已在联邦宇宙中忽略" class="activated">
<i class="fa-solid fa-volume-xmark"></i>
</a>
</span>
{% endif %}
<span>
<a title="点击可屏蔽该用户"
hx-confirm="确定要屏蔽该用户吗?"
hx-post="{% url 'users:block' user.handler %}"
hx-target="#profile_actions"
hx-swap="innerHTML">
<i class="fa-solid fa-user-slash"></i>
</a>
</span>
{% comment %} <span><a href="{% url 'users:report' %}?user_id={{ user.id }}">{% trans '投诉用户' %}</a></span> {% endcomment %}
{% endif %}

View file

@ -1,3 +0,0 @@
<a title="关注ta"
hx-post="{% url 'users:follow' user.handler %}"
hx-swap="outerHTML"><i class="fa-solid fa-user-plus"></i></a>

View file

@ -14,6 +14,7 @@ class UserTest(TestCase):
self.assertTrue(
Follow.objects.filter(owner=self.alice, target=self.bob).exists()
)
self.assertEqual(self.alice.merged_following_ids(), [self.bob.pk])
self.assertEqual(self.alice.following, [self.bob.pk])
self.assertTrue(self.alice.is_following(self.bob))
self.assertTrue(self.bob.is_followed_by(self.alice))
@ -31,3 +32,125 @@ class UserTest(TestCase):
self.assertFalse(self.alice.is_following(self.bob))
self.assertFalse(self.bob.is_followed_by(self.alice))
self.assertEqual(self.alice.following, [])
def test_external_follow(self):
self.alice.mastodon_following.append(self.bob.mastodon_acct)
self.alice.merge_relationships()
self.alice.save()
self.assertTrue(self.alice.is_following(self.bob))
self.assertEqual(self.alice.following, [self.bob.pk])
self.assertFalse(self.alice.follow(self.bob))
self.alice.mastodon_following.remove(self.bob.mastodon_acct)
self.alice.merge_relationships()
self.alice.save()
self.assertFalse(self.alice.is_following(self.bob))
self.assertEqual(self.alice.following, [])
self.assertTrue(self.alice.follow(self.bob))
self.assertTrue(self.alice.is_following(self.bob))
def test_local_mute(self):
self.alice.mute(self.bob)
self.assertTrue(Mute.objects.filter(owner=self.alice, target=self.bob).exists())
self.assertEqual(self.alice.merged_muting_ids(), [self.bob.pk])
self.assertEqual(self.alice.ignoring, [self.bob.pk])
self.assertTrue(self.alice.is_muting(self.bob))
self.alice.mute(self.bob)
self.assertEqual(
Mute.objects.filter(owner=self.alice, target=self.bob).count(), 1
)
self.assertEqual(self.alice.ignoring, [self.bob.pk])
self.alice.unmute(self.bob)
self.assertFalse(
Mute.objects.filter(owner=self.alice, target=self.bob).exists()
)
self.assertFalse(self.alice.is_muting(self.bob))
self.assertEqual(self.alice.ignoring, [])
self.assertEqual(self.alice.merged_muting_ids(), [])
def test_external_mute(self):
self.alice.mastodon_mutes.append(self.bob.mastodon_acct)
self.alice.save()
self.assertTrue(self.alice.is_muting(self.bob))
self.assertEqual(self.alice.merged_muting_ids(), [self.bob.pk])
self.alice.mastodon_mutes.remove(self.bob.mastodon_acct)
self.assertFalse(self.alice.is_muting(self.bob))
self.assertEqual(self.alice.merged_muting_ids(), [])
def test_local_block_follow(self):
self.alice.block(self.bob)
self.assertEqual(self.bob.follow(self.alice), False)
self.alice.unblock(self.bob)
self.assertEqual(self.bob.follow(self.alice), True)
self.assertEqual(self.bob.following, [self.alice.pk])
self.alice.block(self.bob)
self.assertEqual(self.bob.following, [])
def test_local_block(self):
self.alice.block(self.bob)
self.assertTrue(
Block.objects.filter(owner=self.alice, target=self.bob).exists()
)
self.assertEqual(self.alice.merged_rejecting_ids(), [self.bob.pk])
self.assertEqual(self.alice.ignoring, [self.bob.pk])
self.assertTrue(self.alice.is_blocking(self.bob))
self.assertTrue(self.bob.is_blocked_by(self.alice))
self.alice.block(self.bob)
self.assertEqual(
Block.objects.filter(owner=self.alice, target=self.bob).count(), 1
)
self.assertEqual(self.alice.ignoring, [self.bob.pk])
self.alice.unblock(self.bob)
self.assertFalse(
Block.objects.filter(owner=self.alice, target=self.bob).exists()
)
self.assertFalse(self.alice.is_blocking(self.bob))
self.assertFalse(self.bob.is_blocked_by(self.alice))
self.assertEqual(self.alice.ignoring, [])
self.assertEqual(self.alice.merged_rejecting_ids(), [])
def test_external_block(self):
self.bob.follow(self.alice)
self.assertEqual(self.bob.following, [self.alice.pk])
self.alice.mastodon_blocks.append(self.bob.mastodon_acct)
self.alice.save()
self.assertTrue(self.alice.is_blocking(self.bob))
self.assertTrue(self.bob.is_blocked_by(self.alice))
self.assertEqual(self.alice.merged_rejecting_ids(), [self.bob.pk])
self.alice.merge_relationships()
self.assertEqual(self.alice.rejecting, [self.bob.pk])
self.alice.save()
self.assertEqual(self.bob.following, [self.alice.pk])
self.assertEqual(self.bob.rejecting, [])
self.assertEqual(User.merge_rejected_by(), 2)
self.bob.refresh_from_db()
self.assertEqual(self.bob.rejecting, [self.alice.pk])
self.assertEqual(self.bob.following, [])
self.alice.mastodon_blocks.remove(self.bob.mastodon_acct)
self.assertFalse(self.alice.is_blocking(self.bob))
self.assertFalse(self.bob.is_blocked_by(self.alice))
self.assertEqual(self.alice.merged_rejecting_ids(), [])
def test_external_domain_block(self):
self.alice.mastodon_domain_blocks.append(self.bob.mastodon_site)
self.alice.save()
self.assertTrue(self.alice.is_blocking(self.bob))
self.assertTrue(self.bob.is_blocked_by(self.alice))
self.assertEqual(self.alice.merged_rejecting_ids(), [self.bob.pk])
self.alice.merge_relationships()
self.assertEqual(self.alice.rejecting, [self.bob.pk])
self.alice.save()
self.assertEqual(User.merge_rejected_by(), 1)
self.bob.refresh_from_db()
self.assertEqual(self.bob.rejecting, [self.alice.pk])
self.alice.mastodon_domain_blocks.remove(self.bob.mastodon_site)
self.assertFalse(self.alice.is_blocking(self.bob))
self.assertFalse(self.bob.is_blocked_by(self.alice))
self.assertEqual(self.alice.merged_rejecting_ids(), [])

View file

@ -28,6 +28,10 @@ urlpatterns = [
path("locked/<str:user_name>", follow_locked, name="locked"),
path("follow/<str:user_name>", follow, name="follow"),
path("unfollow/<str:user_name>", unfollow, name="unfollow"),
path("mute/<str:user_name>", mute, name="mute"),
path("unmute/<str:user_name>", unmute, name="unmute"),
path("block/<str:user_name>", block, name="block"),
path("unblock/<str:user_name>", unblock, name="unblock"),
path("<str:id>/followers", followers, name="followers"),
path("<str:id>/following", following, name="following"),
path("report", report, name="report"),

View file

@ -16,8 +16,8 @@ from discord import SyncWebhook
def render_user_not_found(request):
msg = _("😖哎呀,这位用户还没有加入本站快去联邦宇宙呼唤TA来注册吧")
sec_msg = _("")
sec_msg = _("😖哎呀,这位用户好像还没有加入本站快去联邦宇宙呼唤TA来注册吧")
msg = _("未找到该用户")
return render(
request,
"common/error.html",
@ -29,7 +29,7 @@ def render_user_not_found(request):
def render_user_blocked(request):
msg = _("你没有访问TA主页的权限😥")
msg = _("没有访问该用户主页的权限")
return render(
request,
"common/error.html",
@ -81,7 +81,7 @@ def follow(request, user_name):
raise BadRequest()
user = User.get(user_name)
if request.user.follow(user):
return render(request, "users/followed.html", context={"user": user})
return render(request, "users/profile_actions.html", context={"user": user})
else:
raise BadRequest()
@ -92,7 +92,51 @@ def unfollow(request, user_name):
raise BadRequest()
user = User.get(user_name)
if request.user.unfollow(user):
return render(request, "users/unfollowed.html", context={"user": user})
return render(request, "users/profile_actions.html", context={"user": user})
else:
raise BadRequest()
@login_required
def mute(request, user_name):
if request.method != "POST":
raise BadRequest()
user = User.get(user_name)
if request.user.mute(user):
return render(request, "users/profile_actions.html", context={"user": user})
else:
raise BadRequest()
@login_required
def unmute(request, user_name):
if request.method != "POST":
raise BadRequest()
user = User.get(user_name)
if request.user.unmute(user):
return render(request, "users/profile_actions.html", context={"user": user})
else:
raise BadRequest()
@login_required
def block(request, user_name):
if request.method != "POST":
raise BadRequest()
user = User.get(user_name)
if request.user.block(user):
return render(request, "users/profile_actions.html", context={"user": user})
else:
raise BadRequest()
@login_required
def unblock(request, user_name):
if request.method != "POST":
raise BadRequest()
user = User.get(user_name)
if request.user.unblock(user):
return render(request, "users/profile_actions.html", context={"user": user})
else:
raise BadRequest()