2023-06-30 23:53:53 -04:00
|
|
|
import re
|
2023-11-24 09:49:23 -05:00
|
|
|
from datetime import timedelta
|
2023-08-10 11:27:31 -04:00
|
|
|
from functools import cached_property
|
2023-09-16 11:09:57 -04:00
|
|
|
from typing import TYPE_CHECKING, ClassVar
|
2023-08-10 11:27:31 -04:00
|
|
|
|
2024-07-05 18:15:10 -04:00
|
|
|
import django_rq
|
2024-02-20 20:20:43 -05:00
|
|
|
import httpx
|
2024-04-03 23:10:21 -04:00
|
|
|
from django.conf import settings
|
2023-08-14 08:15:55 -04:00
|
|
|
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
2023-08-11 01:43:19 -04:00
|
|
|
from django.contrib.auth.validators import UnicodeUsernameValidator
|
2023-07-04 17:21:17 -04:00
|
|
|
from django.core.exceptions import ValidationError
|
2024-02-20 20:20:43 -05:00
|
|
|
from django.core.files.base import ContentFile
|
2024-07-01 17:29:38 -04:00
|
|
|
from django.db import models, transaction
|
2024-07-03 00:07:07 -04:00
|
|
|
from django.db.models.functions import Lower
|
2023-08-10 11:27:31 -04:00
|
|
|
from django.urls import reverse
|
2024-06-03 09:00:40 -04:00
|
|
|
from django.utils import timezone, translation
|
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
|
|
|
|
|
2024-07-03 00:07:07 -04:00
|
|
|
from mastodon.models import (
|
|
|
|
BlueskyAccount,
|
|
|
|
EmailAccount,
|
|
|
|
MastodonAccount,
|
|
|
|
SocialAccount,
|
|
|
|
ThreadsAccount,
|
|
|
|
)
|
2023-07-20 21:59:49 -04:00
|
|
|
from takahe.utils import Takahe
|
2023-08-10 11:27:31 -04:00
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
2023-07-20 21:59:49 -04:00
|
|
|
from .apidentity import APIdentity
|
2023-08-10 11:27:31 -04:00
|
|
|
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",
|
|
|
|
"__",
|
|
|
|
"admin",
|
2024-04-12 20:42:36 -04:00
|
|
|
"administrator",
|
2024-04-19 20:24:34 -04:00
|
|
|
"service",
|
|
|
|
"support",
|
2024-04-12 20:42:36 -04:00
|
|
|
"system",
|
|
|
|
"user",
|
|
|
|
"users",
|
2023-07-04 17:21:17 -04:00
|
|
|
"api",
|
2024-04-19 20:24:34 -04:00
|
|
|
"bot",
|
2023-07-04 17:21:17 -04:00
|
|
|
"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
|
|
|
|
2023-08-14 08:15:55 -04:00
|
|
|
class UserManager(BaseUserManager):
|
|
|
|
def create_user(self, username, email, password=None):
|
2024-07-03 00:07:07 -04:00
|
|
|
from mastodon.models import Email
|
|
|
|
|
2023-08-14 08:15:55 -04:00
|
|
|
Takahe.get_domain() # ensure configuration is complete
|
2024-07-03 00:07:07 -04:00
|
|
|
|
|
|
|
user = User.register(username=username)
|
|
|
|
e = Email.new_account(email)
|
|
|
|
if not e:
|
|
|
|
raise ValueError("Invalid Email")
|
|
|
|
e.user = user
|
|
|
|
e.save()
|
2023-08-14 08:15:55 -04:00
|
|
|
return user
|
|
|
|
|
|
|
|
def create_superuser(self, username, email, password=None):
|
2024-07-03 00:07:07 -04:00
|
|
|
from mastodon.models import Email
|
2023-08-14 08:15:55 -04:00
|
|
|
from takahe.models import User as TakaheUser
|
|
|
|
|
|
|
|
Takahe.get_domain() # ensure configuration is complete
|
2024-07-03 00:07:07 -04:00
|
|
|
user = User.register(username=username, is_superuser=True)
|
|
|
|
e = Email.new_account(email)
|
|
|
|
if not e:
|
|
|
|
raise ValueError("Invalid Email")
|
|
|
|
e.user = user
|
|
|
|
e.save()
|
2023-08-14 08:15:55 -04:00
|
|
|
tu = TakaheUser.objects.get(pk=user.pk, email="@" + username)
|
|
|
|
tu.admin = True
|
|
|
|
tu.save()
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
2020-05-01 22:46:15 +08:00
|
|
|
class User(AbstractUser):
|
2023-07-20 21:59:49 -04:00
|
|
|
identity: "APIdentity"
|
2023-07-12 01:11:15 -04:00
|
|
|
preference: "Preference"
|
2024-07-01 17:29:38 -04:00
|
|
|
social_accounts: "models.QuerySet[SocialAccount]"
|
|
|
|
objects: ClassVar[UserManager] = UserManager()
|
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."),
|
|
|
|
},
|
|
|
|
)
|
2024-07-01 17:29:38 -04:00
|
|
|
language = models.CharField(
|
|
|
|
_("language"),
|
|
|
|
max_length=10,
|
|
|
|
choices=settings.LANGUAGES,
|
|
|
|
null=False,
|
|
|
|
default="en",
|
|
|
|
)
|
|
|
|
|
|
|
|
# remove the following
|
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)
|
2023-11-11 00:53:03 -05:00
|
|
|
mastodon_last_reachable = 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",
|
|
|
|
),
|
2023-12-25 16:47:49 -05:00
|
|
|
]
|
2024-07-03 00:07:07 -04:00
|
|
|
indexes = [models.Index("is_active", name="index_user_is_active")]
|
2020-05-01 22:46:15 +08:00
|
|
|
|
2024-07-01 17:29:38 -04:00
|
|
|
@cached_property
|
|
|
|
def mastodon(self) -> "MastodonAccount | None":
|
|
|
|
return MastodonAccount.objects.filter(user=self).first()
|
|
|
|
|
2024-07-03 00:07:07 -04:00
|
|
|
@cached_property
|
|
|
|
def threads(self) -> "ThreadsAccount | None":
|
|
|
|
return ThreadsAccount.objects.filter(user=self).first()
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def bluesky(self) -> "BlueskyAccount | None":
|
|
|
|
return BlueskyAccount.objects.filter(user=self).first()
|
|
|
|
|
2024-07-01 17:29:38 -04:00
|
|
|
@cached_property
|
|
|
|
def email_account(self) -> "EmailAccount | None":
|
|
|
|
return EmailAccount.objects.filter(user=self).first()
|
|
|
|
|
2023-07-07 16:54:15 -04:00
|
|
|
@cached_property
|
2023-06-30 23:53:53 -04:00
|
|
|
def mastodon_acct(self):
|
2024-07-01 17:29:38 -04:00
|
|
|
return self.mastodon.handle if self.mastodon else ""
|
2020-10-30 13:18:31 +01:00
|
|
|
|
2024-07-01 17:29:38 -04:00
|
|
|
@cached_property
|
2023-07-07 02:02:48 -04:00
|
|
|
def locked(self):
|
2024-07-01 17:29:38 -04:00
|
|
|
return self.identity.locked
|
2023-07-07 02:02:48 -04:00
|
|
|
|
2022-05-30 19:43:29 -04:00
|
|
|
@property
|
|
|
|
def display_name(self):
|
2024-07-01 17:29:38 -04:00
|
|
|
return self.identity.display_name
|
2022-05-30 19:43:29 -04:00
|
|
|
|
2023-07-05 00:47:54 -04:00
|
|
|
@property
|
|
|
|
def avatar(self):
|
2023-08-24 05:48:14 +00:00
|
|
|
return (
|
|
|
|
self.identity.avatar if self.identity else settings.SITE_INFO["user_icon"]
|
|
|
|
)
|
2023-07-05 00:47:54 -04:00
|
|
|
|
2022-11-08 02:32:35 +00:00
|
|
|
@property
|
|
|
|
def url(self):
|
2024-07-01 17:29:38 -04:00
|
|
|
return reverse("journal:user_profile", args=[self.username])
|
2022-11-08 02:32:35 +00:00
|
|
|
|
2023-12-23 00:57:00 -05:00
|
|
|
@property
|
|
|
|
def absolute_url(self):
|
|
|
|
return settings.SITE_INFO["site_url"] + self.url
|
|
|
|
|
2021-12-15 00:16:48 -05:00
|
|
|
def __str__(self):
|
2024-07-05 18:15:10 -04:00
|
|
|
return f'{self.pk}:{self.username or "<missing>"}'
|
2021-12-15 00:16:48 -05:00
|
|
|
|
2023-09-03 20:11:46 +00:00
|
|
|
@property
|
|
|
|
def registration_complete(self):
|
|
|
|
return self.username is not None
|
|
|
|
|
2024-01-14 13:15:30 -05:00
|
|
|
@property
|
|
|
|
def last_usage(self):
|
2024-01-14 22:16:53 -05:00
|
|
|
from journal.models import ShelfMember
|
2024-01-14 13:15:30 -05:00
|
|
|
|
2024-01-14 22:16:53 -05:00
|
|
|
p = (
|
|
|
|
ShelfMember.objects.filter(owner=self.identity)
|
|
|
|
.order_by("-edited_time")
|
|
|
|
.first()
|
|
|
|
)
|
2024-01-14 13:15:30 -05:00
|
|
|
return p.edited_time if p else None
|
|
|
|
|
2023-06-30 23:53:53 -04:00
|
|
|
def clear(self):
|
2024-07-03 00:07:07 -04:00
|
|
|
if not self.is_active:
|
2023-06-30 23:53:53 -04:00
|
|
|
return
|
2024-07-01 17:29:38 -04:00
|
|
|
with transaction.atomic():
|
2024-07-03 00:07:07 -04:00
|
|
|
accts = [str(a) for a in self.social_accounts.all()]
|
|
|
|
self.first_name = (";").join(accts)
|
|
|
|
self.last_name = self.username
|
2024-07-01 17:29:38 -04:00
|
|
|
self.is_active = False
|
|
|
|
# self.username = "~removed~" + str(self.pk)
|
|
|
|
# to get ready for federation, username has to be reserved
|
|
|
|
self.save()
|
|
|
|
self.identity.deleted = timezone.now()
|
|
|
|
self.identity.save()
|
2024-07-03 00:07:07 -04:00
|
|
|
self.social_accounts.all().delete()
|
2023-06-30 23:53:53 -04:00
|
|
|
|
2023-11-08 09:54:36 -05:00
|
|
|
def sync_relationship(self):
|
2024-07-01 17:29:38 -04:00
|
|
|
def get_identity_ids(accts: list):
|
|
|
|
return set(
|
|
|
|
MastodonAccount.objects.filter(handle__in=accts).values_list(
|
|
|
|
"user__identity", flat=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def get_identity_ids_in_domains(domains: list):
|
|
|
|
return set(
|
|
|
|
MastodonAccount.objects.filter(domain__in=domains).values_list(
|
|
|
|
"user__identity", flat=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
me = self.identity.pk
|
|
|
|
if not self.mastodon:
|
|
|
|
return
|
|
|
|
for target_identity in get_identity_ids(self.mastodon.following):
|
|
|
|
if not Takahe.get_is_following(me, target_identity):
|
|
|
|
Takahe.follow(me, target_identity, True)
|
|
|
|
|
|
|
|
for target_identity in get_identity_ids(self.mastodon.blocks):
|
|
|
|
if not Takahe.get_is_blocking(me, target_identity):
|
|
|
|
Takahe.block(me, target_identity)
|
|
|
|
|
|
|
|
for target_identity in get_identity_ids_in_domains(self.mastodon.domain_blocks):
|
|
|
|
if not Takahe.get_is_blocking(me, target_identity):
|
|
|
|
Takahe.block(me, target_identity)
|
|
|
|
|
|
|
|
for target_identity in get_identity_ids(self.mastodon.mutes):
|
|
|
|
if not Takahe.get_is_muting(me, target_identity):
|
|
|
|
Takahe.mute(me, target_identity)
|
2023-11-08 09:54:36 -05:00
|
|
|
|
|
|
|
def sync_identity(self):
|
2024-07-03 00:07:07 -04:00
|
|
|
"""sync display name, bio, and avatar from available sources"""
|
2023-11-08 09:54:36 -05:00
|
|
|
identity = self.identity.takahe_identity
|
2024-01-12 08:33:38 -05:00
|
|
|
if identity.deleted:
|
|
|
|
logger.error(f"Identity {identity} is deleted, skip sync")
|
|
|
|
return
|
2024-07-01 17:29:38 -04:00
|
|
|
mastodon = self.mastodon
|
2024-07-03 00:07:07 -04:00
|
|
|
threads = self.threads
|
2024-07-05 10:53:43 -04:00
|
|
|
bluesky = self.bluesky
|
2024-07-03 00:07:07 -04:00
|
|
|
changed = False
|
|
|
|
name = (
|
|
|
|
(mastodon.display_name if mastodon else "")
|
|
|
|
or (threads.username if threads else "")
|
2024-07-05 10:53:43 -04:00
|
|
|
or (bluesky.display_name if bluesky else "")
|
2024-07-03 00:07:07 -04:00
|
|
|
or identity.name
|
|
|
|
or identity.username
|
|
|
|
)
|
|
|
|
if identity.name != name:
|
|
|
|
identity.name = name
|
|
|
|
changed = True
|
|
|
|
summary = (
|
|
|
|
(mastodon.note if mastodon else "")
|
|
|
|
or (threads.threads_biography if threads else "")
|
2024-07-05 10:53:43 -04:00
|
|
|
or (bluesky.description if bluesky else "")
|
2024-07-03 00:07:07 -04:00
|
|
|
or identity.summary
|
|
|
|
)
|
|
|
|
if identity.summary != summary:
|
|
|
|
identity.summary = summary
|
|
|
|
changed = True
|
|
|
|
identity.manually_approves_followers = (
|
|
|
|
mastodon.locked if mastodon else identity.manually_approves_followers
|
|
|
|
)
|
|
|
|
# it's tedious to update avatar repeatedly, so only sync it once
|
|
|
|
if not identity.icon:
|
|
|
|
url = None
|
|
|
|
if mastodon and mastodon.avatar:
|
|
|
|
url = mastodon.avatar
|
|
|
|
elif threads and threads.threads_profile_picture_url:
|
|
|
|
url = threads.threads_profile_picture_url
|
2024-07-05 10:53:43 -04:00
|
|
|
elif bluesky and bluesky.avatar:
|
|
|
|
url = bluesky.avatar
|
2024-07-03 00:07:07 -04:00
|
|
|
if url:
|
2024-02-20 20:20:43 -05:00
|
|
|
try:
|
2024-07-03 00:07:07 -04:00
|
|
|
r = httpx.get(url)
|
2024-02-20 20:20:43 -05:00
|
|
|
except Exception as e:
|
2024-05-25 23:38:11 -04:00
|
|
|
logger.error(
|
2024-07-05 18:15:10 -04:00
|
|
|
f"fetch icon failed: {identity} {url}",
|
2024-05-25 23:38:11 -04:00
|
|
|
extra={"exception": e},
|
|
|
|
)
|
2024-07-05 18:15:10 -04:00
|
|
|
r = None
|
|
|
|
if r:
|
|
|
|
name = str(self.pk) + "-" + url.split("/")[-1].split("?")[0][-100:]
|
|
|
|
f = ContentFile(r.content, name=name)
|
|
|
|
identity.icon.save(name, f, save=False)
|
|
|
|
changed = True
|
2024-07-03 00:07:07 -04:00
|
|
|
if changed:
|
|
|
|
identity.save()
|
|
|
|
Takahe.update_state(identity, "outdated")
|
2023-07-07 16:54:15 -04:00
|
|
|
|
2024-07-05 18:15:10 -04:00
|
|
|
def sync_accounts(self, skip_graph=False, sleep_hours=0):
|
|
|
|
"""Try refresh account data from 3p server"""
|
|
|
|
for account in self.social_accounts.all():
|
|
|
|
account.sync(skip_graph=skip_graph, sleep_hours=sleep_hours)
|
2024-02-10 23:39:49 -05:00
|
|
|
if not self.preference.mastodon_skip_userinfo:
|
|
|
|
self.sync_identity()
|
2024-07-05 18:15:10 -04:00
|
|
|
if skip_graph:
|
|
|
|
return
|
2024-02-10 23:39:49 -05:00
|
|
|
if not self.preference.mastodon_skip_relationship:
|
|
|
|
self.sync_relationship()
|
2024-07-05 18:15:10 -04:00
|
|
|
return
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def sync_accounts_task(user_id):
|
|
|
|
user = User.objects.get(pk=user_id)
|
|
|
|
logger.info(f"{user} accounts sync start")
|
|
|
|
if user.sync_accounts():
|
|
|
|
logger.info(f"{user} accounts sync done")
|
|
|
|
else:
|
|
|
|
logger.warning(f"{user} accounts sync failed")
|
|
|
|
|
|
|
|
def sync_accounts_later(self):
|
|
|
|
django_rq.get_queue("mastodon").enqueue(User.sync_accounts_task, self.pk)
|
2021-12-14 16:06:27 -05:00
|
|
|
|
2024-04-17 00:00:40 -04:00
|
|
|
@cached_property
|
2023-04-20 13:36:12 -04:00
|
|
|
def unread_announcements(self):
|
2024-04-17 00:00:40 -04:00
|
|
|
from takahe.utils import Takahe
|
|
|
|
|
|
|
|
return Takahe.get_announcements_for_user(self)
|
2023-04-20 13:36:12 -04:00
|
|
|
|
2023-07-20 21:59:49 -04:00
|
|
|
@property
|
|
|
|
def activity_manager(self):
|
|
|
|
if not self.identity:
|
|
|
|
raise ValueError("User has no identity")
|
|
|
|
return self.identity.activity_manager
|
|
|
|
|
|
|
|
@property
|
|
|
|
def shelf_manager(self):
|
|
|
|
if not self.identity:
|
|
|
|
raise ValueError("User has no identity")
|
|
|
|
return self.identity.shelf_manager
|
|
|
|
|
|
|
|
@property
|
|
|
|
def tag_manager(self):
|
|
|
|
if not self.identity:
|
|
|
|
raise ValueError("User has no identity")
|
|
|
|
return self.identity.tag_manager
|
|
|
|
|
2022-05-13 00:11:59 -04:00
|
|
|
@classmethod
|
2024-07-01 17:29:38 -04:00
|
|
|
def register(cls, **param) -> "User":
|
2023-07-20 21:59:49 -04:00
|
|
|
from .preference import Preference
|
2023-08-10 11:27:31 -04:00
|
|
|
|
2024-07-01 17:29:38 -04:00
|
|
|
account = param.pop("account", None)
|
|
|
|
with transaction.atomic():
|
|
|
|
new_user = cls(**param)
|
|
|
|
if not new_user.username:
|
|
|
|
raise ValueError("username is not set")
|
|
|
|
if "language" not in param:
|
|
|
|
new_user.language = translation.get_language()
|
2024-07-03 00:07:07 -04:00
|
|
|
new_user.set_unusable_password()
|
2024-07-01 17:29:38 -04:00
|
|
|
new_user.save()
|
|
|
|
Preference.objects.create(user=new_user)
|
|
|
|
if account:
|
|
|
|
account.user = new_user
|
|
|
|
account.save()
|
|
|
|
Takahe.init_identity_for_local_user(new_user)
|
|
|
|
new_user.identity.shelf_manager
|
|
|
|
return new_user
|
2021-02-17 17:36:22 +01:00
|
|
|
|
2024-07-03 00:07:07 -04:00
|
|
|
def reconnect_account(self, account: SocialAccount):
|
|
|
|
with transaction.atomic():
|
|
|
|
SocialAccount.objects.filter(user=self, type=account.type).delete()
|
|
|
|
account.user = self
|
|
|
|
account.save()
|
|
|
|
|
2023-08-11 01:43:19 -04:00
|
|
|
|
2023-07-20 21:59:49 -04:00
|
|
|
# TODO the following models should be deprecated soon
|
2023-08-11 01:43:19 -04:00
|
|
|
|
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)
|