2023-08-10 11:27:31 -04:00
|
|
|
import hashlib
|
2023-06-30 23:53:53 -04:00
|
|
|
import re
|
2023-08-10 11:27:31 -04:00
|
|
|
from functools import cached_property
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
from django.contrib.auth.models import AbstractUser
|
2023-08-11 01:43:19 -04:00
|
|
|
from django.contrib.auth.validators import UnicodeUsernameValidator
|
2023-06-30 23:53:53 -04:00
|
|
|
from django.core import validators
|
2023-07-04 17:21:17 -04:00
|
|
|
from django.core.exceptions import ValidationError
|
2020-05-01 22:46:15 +08:00
|
|
|
from django.db import models
|
2023-08-10 11:27:31 -04:00
|
|
|
from django.db.models import F, Q, Value
|
|
|
|
from django.db.models.functions import Concat, Lower
|
|
|
|
from django.templatetags.static import static
|
|
|
|
from django.urls import reverse
|
2020-05-05 23:50:48 +08:00
|
|
|
from django.utils import timezone
|
2023-08-10 11:27:31 -04:00
|
|
|
from django.utils.deconstruct import deconstructible
|
2021-12-24 11:56:04 -08:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2023-08-10 11:27:31 -04:00
|
|
|
from loguru import logger
|
|
|
|
|
2023-04-20 13:36:12 -04:00
|
|
|
from management.models import Announcement
|
2021-12-14 16:06:27 -05:00
|
|
|
from mastodon.api import *
|
2023-08-10 11:27:31 -04:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from .preference import Preference
|
2023-07-04 17:21:17 -04:00
|
|
|
|
2023-07-12 01:11:15 -04:00
|
|
|
_RESERVED_USERNAMES = [
|
2023-07-04 17:21:17 -04:00
|
|
|
"connect",
|
|
|
|
"oauth2_login",
|
|
|
|
"__",
|
|
|
|
"admin",
|
|
|
|
"api",
|
|
|
|
"me",
|
|
|
|
]
|
2020-05-01 22:46:15 +08:00
|
|
|
|
|
|
|
|
2023-06-30 23:53:53 -04:00
|
|
|
@deconstructible
|
2023-08-11 01:43:19 -04:00
|
|
|
class UsernameValidator(UnicodeUsernameValidator):
|
2023-07-04 17:21:17 -04:00
|
|
|
regex = r"^[a-zA-Z0-9_]{2,30}$"
|
2023-06-30 23:53:53 -04:00
|
|
|
message = _(
|
2023-08-10 11:27:31 -04:00
|
|
|
"Enter a valid username. This value may contain only unaccented lowercase a-z and uppercase A-Z letters, numbers, and _ characters."
|
2023-06-30 23:53:53 -04:00
|
|
|
)
|
|
|
|
flags = re.ASCII
|
|
|
|
|
2023-07-04 17:21:17 -04:00
|
|
|
def __call__(self, value):
|
2023-07-12 01:11:15 -04:00
|
|
|
if value and value.lower() in _RESERVED_USERNAMES:
|
2023-07-04 17:21:17 -04:00
|
|
|
raise ValidationError(self.message, code=self.code)
|
|
|
|
return super().__call__(value)
|
|
|
|
|
2023-06-30 23:53:53 -04:00
|
|
|
|
2020-05-01 22:46:15 +08:00
|
|
|
class User(AbstractUser):
|
2023-07-12 01:11:15 -04:00
|
|
|
preference: "Preference"
|
2023-06-30 23:53:53 -04:00
|
|
|
username_validator = UsernameValidator()
|
|
|
|
username = models.CharField(
|
|
|
|
_("username"),
|
2023-07-02 16:56:09 -04:00
|
|
|
max_length=100,
|
2023-06-30 23:53:53 -04:00
|
|
|
unique=True,
|
|
|
|
null=True, # allow null for newly registered users who has not set a user name
|
|
|
|
help_text=_("Required. 50 characters or fewer. Letters, digits and _ only."),
|
|
|
|
validators=[username_validator],
|
|
|
|
error_messages={
|
|
|
|
"unique": _("A user with that username already exists."),
|
|
|
|
},
|
|
|
|
)
|
2023-07-07 02:02:48 -04:00
|
|
|
email = models.EmailField(
|
|
|
|
_("email address"),
|
|
|
|
unique=True,
|
|
|
|
default=None,
|
|
|
|
null=True,
|
|
|
|
)
|
2023-07-04 17:21:17 -04:00
|
|
|
pending_email = models.EmailField(
|
|
|
|
_("email address pending verification"), default=None, null=True
|
|
|
|
)
|
2023-07-07 02:02:48 -04:00
|
|
|
local_following = models.ManyToManyField(
|
|
|
|
through="Follow",
|
|
|
|
to="self",
|
|
|
|
through_fields=("owner", "target"),
|
|
|
|
symmetrical=False,
|
|
|
|
related_name="local_followers",
|
|
|
|
)
|
2023-07-07 16:54:15 -04:00
|
|
|
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="+",
|
|
|
|
)
|
2022-05-30 17:54:35 -04:00
|
|
|
following = models.JSONField(default=list)
|
2023-07-07 16:54:15 -04:00
|
|
|
muting = models.JSONField(default=list)
|
|
|
|
# rejecting = local/external blocking + local/external blocked_by + domain_blocking + domain_blocked_by
|
|
|
|
rejecting = models.JSONField(default=list)
|
2023-06-30 23:53:53 -04:00
|
|
|
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)
|
2023-01-01 23:50:57 -05:00
|
|
|
mastodon_token = models.CharField(max_length=2048, default="")
|
|
|
|
mastodon_refresh_token = models.CharField(max_length=2048, default="")
|
2021-12-14 16:06:27 -05:00
|
|
|
mastodon_locked = models.BooleanField(default=False)
|
|
|
|
mastodon_followers = models.JSONField(default=list)
|
|
|
|
mastodon_following = models.JSONField(default=list)
|
|
|
|
mastodon_mutes = models.JSONField(default=list)
|
|
|
|
mastodon_blocks = models.JSONField(default=list)
|
2021-12-15 00:16:48 -05:00
|
|
|
mastodon_domain_blocks = models.JSONField(default=list)
|
2021-12-14 16:06:27 -05:00
|
|
|
mastodon_account = models.JSONField(default=dict)
|
|
|
|
mastodon_last_refresh = models.DateTimeField(default=timezone.now)
|
2022-05-13 00:11:59 -04:00
|
|
|
# store the latest read announcement id,
|
2020-12-09 13:47:00 +01:00
|
|
|
# every time user read the announcement update this field
|
|
|
|
read_announcement_index = models.PositiveIntegerField(default=0)
|
2020-10-22 21:45:05 +02:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
constraints = [
|
|
|
|
models.UniqueConstraint(
|
2023-07-06 20:50:06 -04:00
|
|
|
Lower("username"),
|
|
|
|
name="unique_username",
|
|
|
|
),
|
|
|
|
models.UniqueConstraint(
|
|
|
|
Lower("email"),
|
|
|
|
name="unique_email",
|
|
|
|
),
|
|
|
|
models.UniqueConstraint(
|
|
|
|
Lower("mastodon_username"),
|
|
|
|
Lower("mastodon_site"),
|
2023-06-30 23:53:53 -04:00
|
|
|
name="unique_mastodon_username",
|
|
|
|
),
|
|
|
|
models.UniqueConstraint(
|
2023-07-06 20:50:06 -04:00
|
|
|
Lower("mastodon_id"),
|
|
|
|
Lower("mastodon_site"),
|
2023-06-30 23:53:53 -04:00
|
|
|
name="unique_mastodon_id",
|
|
|
|
),
|
2023-07-04 17:21:17 -04:00
|
|
|
models.CheckConstraint(
|
|
|
|
check=(
|
|
|
|
Q(is_active=False)
|
|
|
|
| Q(mastodon_username__isnull=False)
|
|
|
|
| Q(email__isnull=False)
|
|
|
|
),
|
|
|
|
name="at_least_one_login_method",
|
|
|
|
),
|
2020-10-22 21:45:05 +02:00
|
|
|
]
|
2020-05-01 22:46:15 +08:00
|
|
|
|
2023-08-10 11:27:31 -04:00
|
|
|
@staticmethod
|
|
|
|
def register(**param):
|
|
|
|
from .preference import Preference
|
|
|
|
|
|
|
|
new_user = User(**param)
|
|
|
|
new_user.save()
|
|
|
|
Preference.objects.create(user=new_user)
|
|
|
|
return new_user
|
|
|
|
|
2023-07-07 16:54:15 -04:00
|
|
|
@cached_property
|
2023-06-30 23:53:53 -04:00
|
|
|
def mastodon_acct(self):
|
|
|
|
return (
|
|
|
|
f"{self.mastodon_username}@{self.mastodon_site}"
|
|
|
|
if self.mastodon_username
|
|
|
|
else ""
|
|
|
|
)
|
2020-10-30 13:18:31 +01:00
|
|
|
|
2023-07-07 02:02:48 -04:00
|
|
|
@property
|
|
|
|
def locked(self):
|
|
|
|
return self.mastodon_locked
|
|
|
|
|
2022-05-30 19:43:29 -04:00
|
|
|
@property
|
|
|
|
def display_name(self):
|
2023-01-01 23:50:57 -05:00
|
|
|
return (
|
2023-07-12 17:55:48 -04:00
|
|
|
(self.mastodon_account.get("display_name") if self.mastodon_account else "")
|
|
|
|
or self.username
|
|
|
|
or self.mastodon_acct
|
|
|
|
or ""
|
2023-01-01 23:50:57 -05:00
|
|
|
)
|
2022-05-30 19:43:29 -04:00
|
|
|
|
2023-07-05 00:47:54 -04:00
|
|
|
@property
|
|
|
|
def avatar(self):
|
|
|
|
if self.mastodon_account:
|
2023-07-11 12:54:57 -04:00
|
|
|
return self.mastodon_account.get("avatar") or static("img/avatar.svg")
|
2023-07-05 00:47:54 -04:00
|
|
|
if self.email:
|
|
|
|
return (
|
|
|
|
"https://www.gravatar.com/avatar/"
|
|
|
|
+ hashlib.md5(self.email.lower().encode()).hexdigest()
|
|
|
|
)
|
2023-07-11 12:54:57 -04:00
|
|
|
return static("img/avatar.svg")
|
2023-07-05 00:47:54 -04:00
|
|
|
|
2023-06-30 23:53:53 -04:00
|
|
|
@property
|
|
|
|
def handler(self):
|
|
|
|
return self.mastodon_acct or self.username or f"~{self.pk}"
|
|
|
|
|
2022-11-08 02:32:35 +00:00
|
|
|
@property
|
|
|
|
def url(self):
|
2023-06-30 23:53:53 -04:00
|
|
|
return reverse("journal:user_profile", args=[self.handler])
|
2022-11-08 02:32:35 +00:00
|
|
|
|
2021-12-15 00:16:48 -05:00
|
|
|
def __str__(self):
|
2023-06-30 23:53:53 -04:00
|
|
|
return f'{self.pk}:{self.username or ""}:{self.mastodon_acct}'
|
2021-12-15 00:16:48 -05:00
|
|
|
|
2023-07-07 16:54:15 -04:00
|
|
|
@property
|
|
|
|
def ignoring(self):
|
|
|
|
return self.muting + self.rejecting
|
|
|
|
|
2023-07-07 02:02:48 -04:00
|
|
|
def follow(self, target: "User"):
|
|
|
|
if (
|
|
|
|
target is None
|
|
|
|
or target.locked
|
2023-07-07 16:54:15 -04:00
|
|
|
or self.is_following(target)
|
|
|
|
or self.is_blocking(target)
|
|
|
|
or self.is_blocked_by(target)
|
2023-07-07 02:02:48 -04:00
|
|
|
):
|
|
|
|
return False
|
|
|
|
self.local_following.add(target)
|
|
|
|
self.following.append(target.pk)
|
2023-07-07 16:54:15 -04:00
|
|
|
self.save(update_fields=["following"])
|
2023-07-07 02:02:48 -04:00
|
|
|
return True
|
|
|
|
|
|
|
|
def unfollow(self, target: "User"):
|
|
|
|
if target and target in self.local_following.all():
|
|
|
|
self.local_following.remove(target)
|
2023-07-07 16:54:15 -04:00
|
|
|
if (
|
|
|
|
target.pk in self.following
|
|
|
|
and target.mastodon_acct not in self.mastodon_following
|
|
|
|
):
|
2023-07-07 02:02:48 -04:00
|
|
|
self.following.remove(target.pk)
|
2023-07-07 16:54:15 -04:00
|
|
|
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"])
|
2023-07-07 18:04:36 -04:00
|
|
|
if target in self.local_following.all():
|
|
|
|
self.local_following.remove(target)
|
|
|
|
if self in target.local_following.all():
|
|
|
|
target.local_following.remove(self)
|
2023-07-07 16:54:15 -04:00
|
|
|
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)
|
2023-07-07 02:02:48 -04:00
|
|
|
self.save()
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2023-06-30 23:53:53 -04:00
|
|
|
def clear(self):
|
|
|
|
if self.mastodon_site == "removed" and not self.is_active:
|
|
|
|
return
|
2023-07-05 00:47:54 -04:00
|
|
|
self.first_name = self.mastodon_acct or ""
|
|
|
|
self.last_name = self.email or ""
|
2023-06-30 23:53:53 -04:00
|
|
|
self.is_active = False
|
|
|
|
self.email = None
|
|
|
|
# self.username = "~removed~" + str(self.pk)
|
|
|
|
# to get ready for federation, username has to be reserved
|
2023-07-05 00:47:54 -04:00
|
|
|
self.mastodon_username = None
|
2023-06-30 23:53:53 -04:00
|
|
|
self.mastodon_id = None
|
|
|
|
self.mastodon_site = "removed"
|
|
|
|
self.mastodon_token = ""
|
|
|
|
self.mastodon_locked = False
|
|
|
|
self.mastodon_followers = []
|
|
|
|
self.mastodon_following = []
|
|
|
|
self.mastodon_mutes = []
|
|
|
|
self.mastodon_blocks = []
|
|
|
|
self.mastodon_domain_blocks = []
|
|
|
|
self.mastodon_account = {}
|
|
|
|
|
2023-07-07 16:54:15 -04:00
|
|
|
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)
|
|
|
|
|
2021-12-14 16:06:27 -05:00
|
|
|
def refresh_mastodon_data(self):
|
2023-01-01 23:50:57 -05:00
|
|
|
"""Try refresh account data from mastodon server, return true if refreshed successfully, note it will not save to db"""
|
2021-12-14 16:06:27 -05:00
|
|
|
self.mastodon_last_refresh = timezone.now()
|
|
|
|
code, mastodon_account = verify_account(self.mastodon_site, self.mastodon_token)
|
2022-04-01 20:03:37 -04:00
|
|
|
if code == 401 and self.mastodon_refresh_token:
|
2023-01-01 23:50:57 -05:00
|
|
|
self.mastodon_token = refresh_access_token(
|
|
|
|
self.mastodon_site, self.mastodon_refresh_token
|
|
|
|
)
|
2022-04-01 20:03:37 -04:00
|
|
|
if self.mastodon_token:
|
2023-01-01 23:50:57 -05:00
|
|
|
code, mastodon_account = verify_account(
|
|
|
|
self.mastodon_site, self.mastodon_token
|
|
|
|
)
|
2021-12-14 16:06:27 -05:00
|
|
|
updated = False
|
|
|
|
if mastodon_account:
|
|
|
|
self.mastodon_account = mastodon_account
|
2023-01-01 23:50:57 -05:00
|
|
|
self.mastodon_locked = mastodon_account["locked"]
|
2023-06-30 23:53:53 -04:00
|
|
|
if self.mastodon_username != mastodon_account["username"]:
|
2023-07-09 02:12:28 -04:00
|
|
|
logger.warning(
|
2023-06-30 23:53:53 -04:00
|
|
|
f"username changed from {self} to {mastodon_account['username']}"
|
|
|
|
)
|
|
|
|
self.mastodon_username = mastodon_account["username"]
|
2021-12-14 16:06:27 -05:00
|
|
|
# self.mastodon_token = token
|
|
|
|
# user.mastodon_id = mastodon_account['id']
|
2023-01-01 23:50:57 -05:00
|
|
|
self.mastodon_followers = get_related_acct_list(
|
|
|
|
self.mastodon_site,
|
|
|
|
self.mastodon_token,
|
|
|
|
f"/api/v1/accounts/{self.mastodon_id}/followers",
|
|
|
|
)
|
|
|
|
self.mastodon_following = get_related_acct_list(
|
|
|
|
self.mastodon_site,
|
|
|
|
self.mastodon_token,
|
|
|
|
f"/api/v1/accounts/{self.mastodon_id}/following",
|
|
|
|
)
|
|
|
|
self.mastodon_mutes = get_related_acct_list(
|
|
|
|
self.mastodon_site, self.mastodon_token, "/api/v1/mutes"
|
|
|
|
)
|
|
|
|
self.mastodon_blocks = get_related_acct_list(
|
|
|
|
self.mastodon_site, self.mastodon_token, "/api/v1/blocks"
|
|
|
|
)
|
|
|
|
self.mastodon_domain_blocks = get_related_acct_list(
|
|
|
|
self.mastodon_site, self.mastodon_token, "/api/v1/domain_blocks"
|
|
|
|
)
|
2023-07-07 16:54:15 -04:00
|
|
|
self.merge_relationships()
|
2021-12-14 16:06:27 -05:00
|
|
|
updated = True
|
|
|
|
elif code == 401:
|
2023-07-12 23:05:19 -04:00
|
|
|
logger.error(f"Refresh mastodon data error 401 for {self}")
|
2023-01-01 23:50:57 -05:00
|
|
|
self.mastodon_token = ""
|
2021-12-14 16:06:27 -05:00
|
|
|
return updated
|
|
|
|
|
2023-07-07 16:54:15 -04:00
|
|
|
def merged_following_ids(self):
|
2022-05-30 17:54:35 -04:00
|
|
|
fl = []
|
|
|
|
for m in self.mastodon_following:
|
|
|
|
target = User.get(m)
|
2023-01-01 23:50:57 -05:00
|
|
|
if target and (
|
|
|
|
(not target.mastodon_locked)
|
2023-06-30 23:53:53 -04:00
|
|
|
or self.mastodon_acct in target.mastodon_followers
|
2023-01-01 23:50:57 -05:00
|
|
|
):
|
|
|
|
fl.append(target.pk)
|
2023-07-07 02:12:09 -04:00
|
|
|
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)
|
2023-07-07 16:54:15 -04:00
|
|
|
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)
|
2022-05-30 17:54:35 -04:00
|
|
|
|
2021-12-15 00:16:48 -05:00
|
|
|
def is_blocking(self, target):
|
2023-01-01 23:50:57 -05:00
|
|
|
return (
|
|
|
|
(
|
2023-07-07 16:54:15 -04:00
|
|
|
target in self.local_blocking.all()
|
|
|
|
or target.mastodon_acct in self.mastodon_blocks
|
2023-01-01 23:50:57 -05:00
|
|
|
or target.mastodon_site in self.mastodon_domain_blocks
|
|
|
|
)
|
|
|
|
if target.is_authenticated
|
2023-07-12 01:11:15 -04:00
|
|
|
else self.preference.no_anonymous_view
|
2023-01-01 23:50:57 -05:00
|
|
|
)
|
2021-12-15 00:16:48 -05:00
|
|
|
|
|
|
|
def is_blocked_by(self, target):
|
2023-01-01 23:50:57 -05:00
|
|
|
return target.is_authenticated and target.is_blocking(self)
|
2021-12-15 00:16:48 -05:00
|
|
|
|
|
|
|
def is_muting(self, target):
|
2023-07-07 16:54:15 -04:00
|
|
|
return target.pk in self.muting or target.mastodon_acct in self.mastodon_mutes
|
2021-12-15 00:16:48 -05:00
|
|
|
|
|
|
|
def is_following(self, target):
|
2023-01-01 23:50:57 -05:00
|
|
|
return (
|
2023-06-30 23:53:53 -04:00
|
|
|
self.mastodon_acct in target.mastodon_followers
|
2023-07-07 02:02:48 -04:00
|
|
|
if target.locked
|
|
|
|
else target.pk in self.following
|
2023-07-08 09:24:12 -04:00
|
|
|
# or target.mastodon_acct in self.mastodon_following
|
|
|
|
# or self.mastodon_acct in target.mastodon_followers
|
2023-01-01 23:50:57 -05:00
|
|
|
)
|
2021-12-15 00:16:48 -05:00
|
|
|
|
|
|
|
def is_followed_by(self, target):
|
|
|
|
return target.is_following(self)
|
|
|
|
|
2022-04-06 00:59:30 -04:00
|
|
|
def get_mark_for_item(self, item):
|
2023-01-01 23:50:57 -05:00
|
|
|
params = {item.__class__.__name__.lower() + "_id": item.id, "owner": self}
|
2022-04-06 00:59:30 -04:00
|
|
|
mark = item.mark_class.objects.filter(**params).first()
|
|
|
|
return mark
|
|
|
|
|
2022-05-13 00:11:59 -04:00
|
|
|
def get_max_visibility(self, viewer):
|
|
|
|
if not viewer.is_authenticated:
|
|
|
|
return 0
|
|
|
|
elif viewer == self:
|
|
|
|
return 2
|
|
|
|
elif viewer.is_blocked_by(self):
|
|
|
|
return -1
|
|
|
|
elif viewer.is_following(self):
|
|
|
|
return 1
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
2023-04-20 13:36:12 -04:00
|
|
|
@property
|
|
|
|
def unread_announcements(self):
|
|
|
|
unread_announcements = Announcement.objects.filter(
|
|
|
|
pk__gt=self.read_announcement_index
|
|
|
|
).order_by("-pk")
|
|
|
|
return unread_announcements
|
|
|
|
|
2022-05-13 00:11:59 -04:00
|
|
|
@classmethod
|
2023-07-08 00:44:22 -04:00
|
|
|
def get(cls, name, case_sensitive=False):
|
2023-06-30 23:53:53 -04:00
|
|
|
if isinstance(name, str):
|
|
|
|
sp = name.split("@")
|
2023-07-05 00:47:54 -04:00
|
|
|
if name.startswith("~"):
|
|
|
|
try:
|
|
|
|
query_kwargs = {"pk": int(name[1:])}
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
elif len(sp) == 1:
|
2023-07-08 00:44:22 -04:00
|
|
|
query_kwargs = {
|
|
|
|
"username__iexact" if case_sensitive else "username": name
|
|
|
|
}
|
2023-06-30 23:53:53 -04:00
|
|
|
elif len(sp) == 2:
|
2023-07-08 00:44:22 -04:00
|
|
|
query_kwargs = {
|
|
|
|
"mastodon_username__iexact"
|
|
|
|
if case_sensitive
|
|
|
|
else "mastodon_username": sp[0],
|
|
|
|
"mastodon_site__iexact"
|
|
|
|
if case_sensitive
|
|
|
|
else "mastodon_site": sp[1],
|
|
|
|
}
|
2023-06-30 23:53:53 -04:00
|
|
|
else:
|
2022-05-13 00:11:59 -04:00
|
|
|
return None
|
2023-07-05 00:47:54 -04:00
|
|
|
elif isinstance(name, int):
|
|
|
|
query_kwargs = {"pk": name}
|
2022-05-13 00:11:59 -04:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
return User.objects.filter(**query_kwargs).first()
|
|
|
|
|
2023-08-10 11:27:31 -04:00
|
|
|
@property
|
|
|
|
def tags(self):
|
|
|
|
from journal.models import TagManager
|
2020-05-01 22:46:15 +08:00
|
|
|
|
2023-08-10 11:27:31 -04:00
|
|
|
return TagManager.all_tags_for_user(self)
|
2021-02-17 15:08:16 +01:00
|
|
|
|
2023-08-10 11:27:31 -04:00
|
|
|
@cached_property
|
|
|
|
def tag_manager(self):
|
|
|
|
from journal.models import TagManager
|
|
|
|
|
|
|
|
return TagManager.get_manager_for_user(self)
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def shelf_manager(self):
|
|
|
|
from journal.models import ShelfManager
|
|
|
|
|
|
|
|
return ShelfManager.get_manager_for_user(self)
|
2021-02-17 17:36:22 +01:00
|
|
|
|
2023-08-11 01:43:19 -04:00
|
|
|
@cached_property
|
|
|
|
def activity_manager(self):
|
|
|
|
from social.models import ActivityManager
|
|
|
|
|
|
|
|
return ActivityManager.get_manager_for_user(self)
|
|
|
|
|
2021-02-17 15:08:16 +01:00
|
|
|
|
2023-07-07 02:02:48 -04:00
|
|
|
class Follow(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)
|
|
|
|
|
|
|
|
|
2023-07-07 16:54:15 -04:00
|
|
|
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)
|