reply; emoji
This commit is contained in:
parent
bd3f7f4c94
commit
5df23582af
19 changed files with 446 additions and 19 deletions
|
@ -219,6 +219,7 @@ SILENCED_SYSTEM_CHECKS = [
|
|||
"fields.W344", # Required by takahe: identical table name in different database
|
||||
]
|
||||
|
||||
TAKAHE_MEDIA_PREFIX = "/media/"
|
||||
MEDIA_URL = "/m/"
|
||||
MEDIA_ROOT = os.environ.get("NEODB_MEDIA_ROOT", os.path.join(BASE_DIR, "media/"))
|
||||
|
||||
|
|
|
@ -46,15 +46,11 @@
|
|||
data-uuid="{{ comment.item.uuid }}"><i class="fa-regular fa-circle-play"></i></a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
{% liked_piece comment as liked %}
|
||||
{% include 'like_stats.html' with liked=liked piece=comment %}
|
||||
</span>
|
||||
<span>
|
||||
<a target="_blank"
|
||||
rel="noopener"
|
||||
{% if comment.shared_link %} href="{{ comment.shared_link }}" title="打开联邦宇宙分享链接" {% else %} class="disabled" {% endif %}><i class="fa-solid {% if comment.visibility > 0 %} fa-lock {% else %} fa-globe {% endif %}"></i></a>
|
||||
</span>
|
||||
{% if comment.post %}
|
||||
{% include "action_reply_post.html" with post=comment.post %}
|
||||
{% include "action_like_post.html" with post=comment.post %}
|
||||
{% include "action_open_post.html" with post=comment.post %}
|
||||
{% endif %}
|
||||
</span>
|
||||
<span>
|
||||
{% if comment.rating_grade %}{{ comment.rating_grade|rating_star }}{% endif %}
|
||||
|
@ -70,6 +66,7 @@
|
|||
</span>
|
||||
{% if comment.item != item %}<a href="{{ comment.item_url }}">{{ comment.item.title }}</a>{% endif %}
|
||||
<div>{{ comment.html|safe }}</div>
|
||||
{% if comment.post_id %}<div id="replies_{{ comment.post_id }}"></div>{% endif %}
|
||||
</section>
|
||||
{% else %}
|
||||
<a hx-get="{% url 'catalog:comments' comment.item.url_path comment.item.uuid %}?last={{ comment.created_time|date:'Y-m-d H:i:s.uO'|urlencode }}"
|
||||
|
|
29
common/static/scss/_post.scss
Normal file
29
common/static/scss/_post.scss
Normal file
|
@ -0,0 +1,29 @@
|
|||
section.replies {
|
||||
border-left: 1px solid var(--pico-muted-color);
|
||||
margin-left: var(--pico-spacing);
|
||||
padding-left: var(--pico-spacing);
|
||||
margin-bottom: 0 !important;
|
||||
>div {
|
||||
margin-bottom: calc(var(--pico-spacing));
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
details {
|
||||
summary {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
form {
|
||||
margin-bottom: 0;
|
||||
select {
|
||||
width: min-content;
|
||||
}
|
||||
button{
|
||||
height: calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)
|
||||
}
|
||||
details.dropdown > summary::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,3 +18,4 @@
|
|||
@import '_common.scss';
|
||||
@import '_login.scss';
|
||||
@import '_form.scss';
|
||||
@import '_post.scss';
|
||||
|
|
|
@ -62,6 +62,16 @@
|
|||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if identity.user.mastodon_account %}
|
||||
<span>
|
||||
<a href="{{ identity.user.mastodon_account.url }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="@{{ identity.user.mastodon_acct }}">
|
||||
<i class="fa-brands fa-mastodon"></i>
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% elif request.user.is_authenticated %}
|
||||
{% include 'users/profile_actions.html' %}
|
||||
{% endif %}
|
||||
|
|
|
@ -140,6 +140,10 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
def api_url(self):
|
||||
return f"/api/{self.url}" if self.url_path else None
|
||||
|
||||
@property
|
||||
def post(self):
|
||||
return Takahe.get_post(self.post_id) if self.post_id else None
|
||||
|
||||
@property
|
||||
def shared_link(self):
|
||||
return Takahe.get_post_url(self.post_id) if self.post_id else None
|
||||
|
@ -153,6 +157,17 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
def is_liked_by(self, user):
|
||||
return self.post_id and Takahe.post_liked_by(self.post_id, user)
|
||||
|
||||
@property
|
||||
def reply_count(self):
|
||||
return (
|
||||
Takahe.get_post_stats(self.post_id).get("replies", 0) if self.post_id else 0
|
||||
)
|
||||
|
||||
def get_replies(self, viewing_identity):
|
||||
return Takahe.get_post_replies(
|
||||
self.post_id, viewing_identity.pk if viewing_identity else None
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_by_url(cls, url_or_b62):
|
||||
b62 = url_or_b62.strip().split("/")[-1]
|
||||
|
|
|
@ -133,12 +133,13 @@ class Mark:
|
|||
shelf_type != self.shelf_type
|
||||
or comment_text != self.comment_text
|
||||
or rating_grade != self.rating_grade
|
||||
or visibility != self.visibility
|
||||
)
|
||||
if shelf_type is None:
|
||||
if shelf_type is None or visibility != self.visibility:
|
||||
Takahe.delete_mark(self)
|
||||
if created_time and created_time >= timezone.now():
|
||||
created_time = None
|
||||
post_as_new = shelf_type != self.shelf_type
|
||||
post_as_new = shelf_type != self.shelf_type or visibility != self.visibility
|
||||
original_visibility = self.visibility
|
||||
if shelf_type != self.shelf_type or visibility != original_visibility:
|
||||
self.shelfmember = self.owner.shelf_manager.move_item(
|
||||
|
|
15
journal/templates/action_like_post.html
Normal file
15
journal/templates/action_like_post.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% load user_actions %}
|
||||
<span hx-target="this" hx-swap="outerHTML">
|
||||
{% liked_post post as liked %}
|
||||
{% if liked %}
|
||||
<a class="activated" hx-post="{% url 'journal:post_unlike' post.pk %}">
|
||||
<i class="fa-solid fa-thumbs-up }}"></i>
|
||||
<span>{{ post.stats.likes }}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a hx-post="{% url 'journal:post_like' post.pk %}">
|
||||
<i class="fa-regular fa-thumbs-up }}"></i>
|
||||
{% if post.stats.likes %}<span>{{ post.stats.likes }}</span>{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
18
journal/templates/action_open_post.html
Normal file
18
journal/templates/action_open_post.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<span>
|
||||
<a target="_blank"
|
||||
rel="noopener"
|
||||
href="{{ post.object_uri }}"
|
||||
class="disabled">
|
||||
{% if post.visibility == 1 %}
|
||||
<i class="fa-solid fa-lock-open"></i>
|
||||
{% elif post.visibility == 2 %}
|
||||
<i class="fa-solid fa-lock"></i>
|
||||
{% elif post.visibility == 3 %}
|
||||
<i class="fa-solid fa-at"></i>
|
||||
{% elif post.visibility == 4 %}
|
||||
<i class="fa-solid fa-igloo"></i>
|
||||
{% else %}
|
||||
<i class="fa-solid fa-globe"></i>
|
||||
{% endif %}
|
||||
</a>
|
||||
</span>
|
9
journal/templates/action_reply_post.html
Normal file
9
journal/templates/action_reply_post.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<span>
|
||||
<a hx-get="{% url 'journal:post_replies' post.pk %}"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="click once"
|
||||
hx-target="#replies_{{ post.pk }}">
|
||||
<i class="fa-solid fa-reply"></i>
|
||||
{% if post.stats.replies %}<span>{{ post.stats.replies }}</span>{% endif %}
|
||||
</a>
|
||||
</span>
|
91
journal/templates/replies.html
Normal file
91
journal/templates/replies.html
Normal file
|
@ -0,0 +1,91 @@
|
|||
<section class="replies" hx-target="this" hx-swap="outerHTML">
|
||||
{% for post in replies %}
|
||||
<div>
|
||||
<span class="action">
|
||||
{% include "action_reply_post.html" %}
|
||||
{% include "action_like_post.html" %}
|
||||
{% include "action_open_post.html" %}
|
||||
</span>
|
||||
<span>
|
||||
<a href="{{ post.author.url }}"
|
||||
class="nickname"
|
||||
title="@{{ post.author.handle }}">{{ post.author.name|default:post.author.username }}</a>
|
||||
</span>
|
||||
<span class="action inline">
|
||||
<span class="timestamp">
|
||||
{% if post.edited %}
|
||||
{{ post.edited | date }}
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
{% elif post.published %}
|
||||
{{ post.published | date }}
|
||||
{% else %}
|
||||
{{ post.created | date }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
{% if post.summary %}
|
||||
<details>
|
||||
<summary>{{ post.summary }}</summary>
|
||||
{{ post.safe_content_local }}
|
||||
</details>
|
||||
{% else %}
|
||||
{{ post.safe_content_local }}
|
||||
{% endif %}
|
||||
<div id="replies_{{ post.pk }}"></div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="empty">暂无回应</div>
|
||||
{% endfor %}
|
||||
<form class="reply"
|
||||
role="group"
|
||||
hx-post="{% url 'journal:post_reply' post.pk %}"
|
||||
hx-trigger="submit once">
|
||||
<input name="content" type="text" placeholder="Type your reply" />
|
||||
<details class="dropdown">
|
||||
<summary>
|
||||
<i class="fa-solid fa-users-gear"></i>
|
||||
</summary>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="0"
|
||||
{% if post.visibility == 0 %}checked{% endif %} />
|
||||
<i class="fa-solid fa-globe"></i>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="1"
|
||||
{% if post.visibility == 1 %}checked{% endif %} />
|
||||
<i class="fa-solid fa-lock-open"></i>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="2"
|
||||
{% if post.visibility == 2 %}checked{% endif %} />
|
||||
<i class="fa-solid fa-lock"></i>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="3"
|
||||
{% if post.visibility == 3 %}checked{% endif %} />
|
||||
<i class="fa-solid fa-at"></i>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<button class="secondary">
|
||||
<i class="fa-solid fa-reply"></i>
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
|
@ -38,5 +38,15 @@ def liked_piece(context, piece):
|
|||
user
|
||||
and user.is_authenticated
|
||||
and piece.post_id
|
||||
and Takahe.get_user_interaction(piece.post_id, user, "like")
|
||||
and Takahe.get_user_interaction(piece.post_id, user.identity.pk, "like")
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def liked_post(context, post):
|
||||
user = context["request"].user
|
||||
return (
|
||||
user
|
||||
and user.is_authenticated
|
||||
and Takahe.post_liked_by(post.pk, user.identity.pk)
|
||||
)
|
||||
|
|
|
@ -23,6 +23,11 @@ urlpatterns = [
|
|||
path("unlike/<str:piece_uuid>", unlike, name="unlike"),
|
||||
path("mark/<str:item_uuid>", mark, name="mark"),
|
||||
path("comment/<str:item_uuid>", comment, name="comment"),
|
||||
path("piece/<str:piece_uuid>/replies", piece_replies, name="piece_replies"),
|
||||
path("post/<int:post_id>/replies", post_replies, name="post_replies"),
|
||||
path("post/<int:post_id>/reply", post_reply, name="post_reply"),
|
||||
path("post/<int:post_id>/like", post_like, name="post_like"),
|
||||
path("post/<int:post_id>/unlike", post_unlike, name="post_unlike"),
|
||||
path("mark_log/<str:item_uuid>/<int:log_id>", mark_log, name="mark_log"),
|
||||
path(
|
||||
"add_to_collection/<str:item_uuid>", add_to_collection, name="add_to_collection"
|
||||
|
|
|
@ -25,6 +25,7 @@ from .mark import (
|
|||
user_mark_list,
|
||||
wish,
|
||||
)
|
||||
from .post import piece_replies, post_like, post_replies, post_reply, post_unlike
|
||||
from .profile import profile, user_calendar_data
|
||||
from .review import ReviewFeed, review_edit, review_retrieve, user_review_list
|
||||
from .tag import user_tag_edit, user_tag_list, user_tag_member_list
|
||||
|
|
64
journal/views/post.py
Normal file
64
journal/views/post.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from httpx import request
|
||||
|
||||
from catalog.models import *
|
||||
from common.utils import (
|
||||
AuthedHttpRequest,
|
||||
PageLinksGenerator,
|
||||
get_uuid_or_404,
|
||||
target_identity_required,
|
||||
)
|
||||
from takahe.utils import Takahe
|
||||
|
||||
from ..forms import *
|
||||
from ..models import *
|
||||
|
||||
|
||||
@login_required
|
||||
def piece_replies(request: AuthedHttpRequest, piece_uuid: str):
|
||||
piece = get_object_or_404(Piece, uid=get_uuid_or_404(piece_uuid))
|
||||
if not piece.is_visible_to(request.user):
|
||||
raise PermissionDenied()
|
||||
replies = piece.get_replies(request.user.identity)
|
||||
return render(request, "replies.html", {"post": piece.post, "replies": replies})
|
||||
|
||||
|
||||
@login_required
|
||||
def post_replies(request: AuthedHttpRequest, post_id: int):
|
||||
replies = Takahe.get_post_replies(post_id, request.user.identity.pk)
|
||||
return render(
|
||||
request, "replies.html", {"post": Takahe.get_post(post_id), "replies": replies}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def post_reply(request: AuthedHttpRequest, post_id: int):
|
||||
content = request.POST.get("content", "").strip()
|
||||
visibility = Takahe.Visibilities(int(request.POST.get("visibility", -1)))
|
||||
if request.method != "POST" or not content:
|
||||
raise BadRequest()
|
||||
Takahe.reply_post(post_id, request.user.identity.pk, content, visibility)
|
||||
replies = Takahe.get_post_replies(post_id, request.user.identity.pk)
|
||||
return render(
|
||||
request, "replies.html", {"post": Takahe.get_post(post_id), "replies": replies}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def post_like(request: AuthedHttpRequest, post_id: int):
|
||||
if request.method != "POST":
|
||||
raise BadRequest()
|
||||
Takahe.like_post(post_id, request.user.identity.pk)
|
||||
return render(request, "action_like_post.html", {"post": Takahe.get_post(post_id)})
|
||||
|
||||
|
||||
@login_required
|
||||
def post_unlike(request: AuthedHttpRequest, post_id: int):
|
||||
if request.method != "POST":
|
||||
raise BadRequest()
|
||||
Takahe.unlike_post(post_id, request.user.identity.pk)
|
||||
return render(request, "action_like_post.html", {"post": Takahe.get_post(post_id)})
|
114
takahe/models.py
114
takahe/models.py
|
@ -22,7 +22,7 @@ from django.utils.safestring import mark_safe
|
|||
from loguru import logger
|
||||
from lxml import etree
|
||||
|
||||
from .html import FediverseHtmlParser
|
||||
from .html import ContentRenderer, FediverseHtmlParser
|
||||
from .uris import *
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -419,6 +419,14 @@ class Identity(models.Model):
|
|||
return f"{self.username}@{self.domain_id}"
|
||||
return f"{self.username}@(unknown server)"
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return (
|
||||
f"/users/{self.username}/"
|
||||
if self.local
|
||||
else f"/users/@{self.username}@{self.domain_id}/"
|
||||
)
|
||||
|
||||
@property
|
||||
def user_pk(self):
|
||||
user = self.users.first()
|
||||
|
@ -630,6 +638,101 @@ class Follow(models.Model):
|
|||
return f"#{self.id}: {self.source} → {self.target}"
|
||||
|
||||
|
||||
class PostQuerySet(models.QuerySet):
|
||||
def not_hidden(self):
|
||||
query = self.exclude(state__in=["deleted", "deleted_fanned_out"])
|
||||
return query
|
||||
|
||||
def public(self, include_replies: bool = False):
|
||||
query = self.filter(
|
||||
visibility__in=[
|
||||
Post.Visibilities.public,
|
||||
Post.Visibilities.local_only,
|
||||
],
|
||||
)
|
||||
if not include_replies:
|
||||
return query.filter(in_reply_to__isnull=True)
|
||||
return query
|
||||
|
||||
def local_public(self, include_replies: bool = False):
|
||||
query = self.filter(
|
||||
visibility__in=[
|
||||
Post.Visibilities.public,
|
||||
Post.Visibilities.local_only,
|
||||
],
|
||||
local=True,
|
||||
)
|
||||
if not include_replies:
|
||||
return query.filter(in_reply_to__isnull=True)
|
||||
return query
|
||||
|
||||
def unlisted(self, include_replies: bool = False):
|
||||
query = self.filter(
|
||||
visibility__in=[
|
||||
Post.Visibilities.public,
|
||||
Post.Visibilities.local_only,
|
||||
Post.Visibilities.unlisted,
|
||||
],
|
||||
)
|
||||
if not include_replies:
|
||||
return query.filter(in_reply_to__isnull=True)
|
||||
return query
|
||||
|
||||
def visible_to(self, identity: Identity | None, include_replies: bool = False):
|
||||
if identity is None:
|
||||
return self.unlisted(include_replies=include_replies)
|
||||
query = self.filter(
|
||||
models.Q(
|
||||
visibility__in=[
|
||||
Post.Visibilities.public,
|
||||
Post.Visibilities.local_only,
|
||||
Post.Visibilities.unlisted,
|
||||
]
|
||||
)
|
||||
| models.Q(
|
||||
visibility=Post.Visibilities.followers,
|
||||
author__inbound_follows__source=identity,
|
||||
)
|
||||
| models.Q(
|
||||
mentions=identity,
|
||||
)
|
||||
| models.Q(author=identity)
|
||||
).distinct()
|
||||
if not include_replies:
|
||||
return query.filter(in_reply_to__isnull=True)
|
||||
return query
|
||||
|
||||
# def tagged_with(self, hashtag: str | Hashtag):
|
||||
# if isinstance(hashtag, str):
|
||||
# tag_q = models.Q(hashtags__contains=hashtag)
|
||||
# else:
|
||||
# tag_q = models.Q(hashtags__contains=hashtag.hashtag)
|
||||
# if hashtag.aliases:
|
||||
# for alias in hashtag.aliases:
|
||||
# tag_q |= models.Q(hashtags__contains=alias)
|
||||
# return self.filter(tag_q)
|
||||
|
||||
|
||||
class PostManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return PostQuerySet(self.model, using=self._db)
|
||||
|
||||
def not_hidden(self):
|
||||
return self.get_queryset().not_hidden()
|
||||
|
||||
def public(self, include_replies: bool = False):
|
||||
return self.get_queryset().public(include_replies=include_replies)
|
||||
|
||||
def local_public(self, include_replies: bool = False):
|
||||
return self.get_queryset().local_public(include_replies=include_replies)
|
||||
|
||||
def unlisted(self, include_replies: bool = False):
|
||||
return self.get_queryset().unlisted(include_replies=include_replies)
|
||||
|
||||
# def tagged_with(self, hashtag: str | Hashtag):
|
||||
# return self.get_queryset().tagged_with(hashtag=hashtag)
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
"""
|
||||
A post (status, toot) that is either local or remote.
|
||||
|
@ -739,6 +842,7 @@ class Post(models.Model):
|
|||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
objects = PostManager()
|
||||
|
||||
class Meta:
|
||||
# managed = False
|
||||
|
@ -810,7 +914,6 @@ class Post(models.Model):
|
|||
with transaction.atomic():
|
||||
# Find mentions in this post
|
||||
mentions = cls.mentions_from_content(content, author)
|
||||
# mentions = set()
|
||||
if reply_to:
|
||||
mentions.add(reply_to.author)
|
||||
# Maintain local-only for replies
|
||||
|
@ -955,6 +1058,10 @@ class Post(models.Model):
|
|||
if save:
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def safe_content_local(self):
|
||||
return ContentRenderer(local=True).render_post(self.content, self)
|
||||
|
||||
|
||||
class EmojiQuerySet(models.QuerySet):
|
||||
def usable(self, domain: Domain | None = None):
|
||||
|
@ -1070,7 +1177,8 @@ class Emoji(models.Model):
|
|||
def full_url(self, always_show=False) -> RelativeAbsoluteUrl:
|
||||
if self.is_usable or always_show:
|
||||
if self.file:
|
||||
return AutoAbsoluteUrl(self.file.url)
|
||||
return AutoAbsoluteUrl(settings.TAKAHE_MEDIA_PREFIX + self.file.name)
|
||||
# return AutoAbsoluteUrl(self.file.url)
|
||||
elif self.remote_url:
|
||||
return ProxyAbsoluteUrl(
|
||||
f"/proxy/emoji/{self.pk}/",
|
||||
|
|
|
@ -342,6 +342,7 @@ class Takahe:
|
|||
content: str,
|
||||
visibility: Visibilities,
|
||||
data: dict | None = None,
|
||||
reply_to_pk: int | None = None,
|
||||
post_pk: int | None = None,
|
||||
post_time: datetime.datetime | None = None,
|
||||
) -> int | None:
|
||||
|
@ -351,6 +352,13 @@ class Takahe:
|
|||
if post_pk
|
||||
else None
|
||||
)
|
||||
if post_pk and not post:
|
||||
raise ValueError(f"Cannot find post to edit: {post_pk}")
|
||||
reply_to_post = (
|
||||
Post.objects.filter(pk=reply_to_pk).first() if reply_to_pk else None
|
||||
)
|
||||
if reply_to_pk and not reply_to_post:
|
||||
raise ValueError(f"Cannot find post to reply: {reply_to_pk}")
|
||||
if post:
|
||||
post.edit_local(
|
||||
pre_conetent, content, visibility=visibility, type_data=data
|
||||
|
@ -363,9 +371,14 @@ class Takahe:
|
|||
visibility=visibility,
|
||||
type_data=data,
|
||||
published=post_time,
|
||||
reply_to=reply_to_post,
|
||||
)
|
||||
return post.pk if post else None
|
||||
|
||||
@staticmethod
|
||||
def get_post(post_pk: int) -> str | None:
|
||||
return Post.objects.filter(pk=post_pk).first()
|
||||
|
||||
@staticmethod
|
||||
def get_post_url(post_pk: int) -> str | None:
|
||||
post = Post.objects.filter(pk=post_pk).first() if post_pk else None
|
||||
|
@ -465,6 +478,12 @@ class Takahe:
|
|||
interaction.save()
|
||||
post.calculate_stats()
|
||||
|
||||
@staticmethod
|
||||
def reply_post(
|
||||
post_pk: int, identity_pk: int, content: str, visibility: Visibilities
|
||||
):
|
||||
return Takahe.post(identity_pk, "", content, visibility, reply_to_pk=post_pk)
|
||||
|
||||
@staticmethod
|
||||
def like_post(post_pk: int, identity_pk: int):
|
||||
return Takahe.interact_post(post_pk, identity_pk, "like")
|
||||
|
@ -497,3 +516,33 @@ class Takahe:
|
|||
logger.warning(f"Cannot find post {post_pk}")
|
||||
return {}
|
||||
return post.stats or {}
|
||||
|
||||
@staticmethod
|
||||
def get_post_replies(post_pk: int, identity_pk: int | None):
|
||||
node = Post.objects.filter(pk=post_pk).first()
|
||||
if not node:
|
||||
return Post.objects.none()
|
||||
identity = (
|
||||
Identity.objects.filter(pk=identity_pk).first() if identity_pk else None
|
||||
)
|
||||
child_queryset = (
|
||||
Post.objects.not_hidden()
|
||||
.prefetch_related(
|
||||
# "attachments",
|
||||
"mentions",
|
||||
"emojis",
|
||||
)
|
||||
.select_related(
|
||||
"author",
|
||||
"author__domain",
|
||||
)
|
||||
.filter(in_reply_to=node.object_uri)
|
||||
.order_by("published")
|
||||
)
|
||||
if identity:
|
||||
child_queryset = child_queryset.visible_to(
|
||||
identity=identity, include_replies=True
|
||||
)
|
||||
else:
|
||||
child_queryset = child_queryset.unlisted(include_replies=True)
|
||||
return child_queryset
|
||||
|
|
|
@ -52,7 +52,9 @@ def init_identity(apps, schema_editor):
|
|||
domain_name=domain,
|
||||
deleted=None if user.is_active else user.updated,
|
||||
)
|
||||
takahe_user = TakaheUser.objects.create(pk=user.pk, email=handler)
|
||||
takahe_user = TakaheUser.objects.create(
|
||||
pk=user.pk, email=handler, admin=user.is_superuser
|
||||
)
|
||||
takahe_identity = TakaheIdentity.objects.create(
|
||||
pk=user.pk,
|
||||
actor_uri=f"https://{service_domain or domain}/@{username}@{domain}/",
|
||||
|
|
|
@ -66,17 +66,18 @@ class APIdentity(models.Model):
|
|||
def profile_uri(self):
|
||||
return self.takahe_identity.profile_uri
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def display_name(self):
|
||||
return self.takahe_identity.name or self.username
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def summary(self):
|
||||
return self.takahe_identity.summary or ""
|
||||
|
||||
@property
|
||||
def avatar(self):
|
||||
return self.takahe_identity.icon_uri or static("img/avatar.svg") # fixme
|
||||
# return self.takahe_identity.icon_uri or static("img/avatar.svg") # fixme
|
||||
return f"/proxy/identity_icon/{self.pk}/"
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
|
|
Loading…
Add table
Reference in a new issue