diff --git a/catalog/templates/_item_comments.html b/catalog/templates/_item_comments.html index 472bd35c..2facef2a 100644 --- a/catalog/templates/_item_comments.html +++ b/catalog/templates/_item_comments.html @@ -58,7 +58,7 @@ {% if comment.rating_grade %}{{ comment.rating_grade|rating_star }}{% endif %} {{ comment.owner.display_name }} + title="@{{ comment.owner.handle }}">{{ comment.owner.display_name }} diff --git a/catalog/templates/_item_comments_by_episode.html b/catalog/templates/_item_comments_by_episode.html index 52018fec..6c4678db 100644 --- a/catalog/templates/_item_comments_by_episode.html +++ b/catalog/templates/_item_comments_by_episode.html @@ -62,7 +62,7 @@ {% if comment.rating_grade %}{{ comment.rating_grade|rating_star }}{% endif %} {{ comment.owner.display_name }} + title="@{{ comment.owner.handle }}">{{ comment.owner.display_name }} diff --git a/catalog/templates/_item_reviews.html b/catalog/templates/_item_reviews.html index b814afd0..346222c9 100644 --- a/catalog/templates/_item_reviews.html +++ b/catalog/templates/_item_reviews.html @@ -22,7 +22,7 @@ {% if review.rating_grade %}{{ review.rating_grade|rating_star }}{% endif %} {{ review.owner.display_name }} + title="@{{ review.owner.handle }}">{{ review.owner.display_name }} {{ review.created_time|date }} diff --git a/catalog/templates/item_mark_list.html b/catalog/templates/item_mark_list.html index 2ea77ec2..de783574 100644 --- a/catalog/templates/item_mark_list.html +++ b/catalog/templates/item_mark_list.html @@ -43,7 +43,7 @@ {{ mark.created_time|date }}
- {{ mark.owner.display_name }} + {{ mark.owner.display_name }} {{ mark.action_label }} {% if mark.rating_grade %}{{ mark.rating_grade|rating_star }}{% endif %} {% if mark.comment.item != item %}{{ mark.comment.item.title }}{% endif %} diff --git a/catalog/templates/item_review_list.html b/catalog/templates/item_review_list.html index f63c4c5e..bb525863 100644 --- a/catalog/templates/item_review_list.html +++ b/catalog/templates/item_review_list.html @@ -45,7 +45,7 @@ {{ review.owner.display_name }} + title="@{{ review.owner.handle }}">{{ review.owner.display_name }}
{{ review.plain_content | truncate:200 }}
diff --git a/common/templates/_sidebar.html b/common/templates/_sidebar.html index e6bb9d2c..c671108c 100644 --- a/common/templates/_sidebar.html +++ b/common/templates/_sidebar.html @@ -196,7 +196,7 @@
{% for t in top_tags %} - {{ t }} + {{ t }} {% empty %}
暂无可见标签
@@ -204,7 +204,7 @@
{% if top_tags %} - ...{% trans '全部' %} + ...{% trans '全部' %} {% endif %} diff --git a/common/utils.py b/common/utils.py index 804ad100..9f1ab07e 100644 --- a/common/utils.py +++ b/common/utils.py @@ -1,9 +1,7 @@ import functools -import re import uuid from typing import TYPE_CHECKING -from django.db.models import query from django.http import Http404, HttpRequest, HttpResponseRedirect, QueryDict from django.utils import timezone from django.utils.baseconv import base62 @@ -55,7 +53,38 @@ def target_identity_required(func): from users.views import render_user_blocked, render_user_not_found try: - target = APIdentity.get_by_handler(user_name) + target = APIdentity.get_by_handle(user_name) + except APIdentity.DoesNotExist: + return render_user_not_found(request) + target_user = target.user + viewer = None + if target_user and not target_user.is_active: + return render_user_not_found(request) + if request.user.is_authenticated: + try: + viewer = APIdentity.objects.get(user=request.user) + except APIdentity.DoesNotExist: + return HttpResponseRedirect("/account/register") + if request.user != target_user: + if target.is_blocking(viewer) or target.is_blocked_by(viewer): + return render_user_blocked(request) + else: + viewer = None + request.target_identity = target + request.identity = viewer + return func(request, user_name, *args, **kwargs) + + return wrapper + + +def profile_identity_required(func): + @functools.wraps(func) + def wrapper(request, user_name, *args, **kwargs): + from users.models import APIdentity + from users.views import render_user_blocked, render_user_not_found + + try: + target = APIdentity.get_by_handle(user_name, match_linked=True) except APIdentity.DoesNotExist: return render_user_not_found(request) target_user = target.user diff --git a/developer/templates/oauth2_provider/authorize.html b/developer/templates/oauth2_provider/authorize.html index 47bb4bc4..9714ac18 100644 --- a/developer/templates/oauth2_provider/authorize.html +++ b/developer/templates/oauth2_provider/authorize.html @@ -10,7 +10,7 @@ {% csrf_token %} {% if not application.is_official %}

- {{ application.name }} 是由 @{{ application.user.identity.handler }} 创建和维护的应用程序。 + {{ application.name }} 是由 @{{ application.user.identity.handle }} 创建和维护的应用程序。 {{ site_name }}无法保证其安全性和有效性,请自行验证确认后再授权。

{% endif %} diff --git a/journal/templates/_list_item.html b/journal/templates/_list_item.html index 0d032ae0..2a5a94ab 100644 --- a/journal/templates/_list_item.html +++ b/journal/templates/_list_item.html @@ -49,7 +49,7 @@ {{ mark.created_time|date }}
- {% comment %} {{ mark.owner.display_name }} {% endcomment %} + {% comment %} {{ mark.owner.display_name }} {% endcomment %} {{ mark.action_label }} {% if mark.rating_grade %}{{ mark.rating_grade|rating_star }}{% endif %}
diff --git a/journal/templates/collection.html b/journal/templates/collection.html index 0064591a..624e3d16 100644 --- a/journal/templates/collection.html +++ b/journal/templates/collection.html @@ -62,7 +62,7 @@

{{ collection.owner.mastodon_account.display_name }} - @{{ collection.owner.handler }} + @{{ collection.owner.handle }}

{% for cat, count in collection.get_summary.items %} diff --git a/journal/templates/profile.html b/journal/templates/profile.html index 0fef8cc4..ea5400e6 100644 --- a/journal/templates/profile.html +++ b/journal/templates/profile.html @@ -10,21 +10,21 @@ - {% if identity.user == request.user %} + {% if me %} {{ site_name }} - {% trans '我的个人主页' %} {% else %} {{ site_name }} - {{ identity.display_name }} {% endif %} - + content="{{ site_name }} - @{{ identity.handle }}"> + {% if not identity.anonymous_viewable %}{% endif %} + title="{{ site_name }} - @{{ identity.handle }}的评论" + href="{{ identity.url }}/feed/reviews/"> {% include "common_libs.html" %}

@@ -94,7 +94,7 @@
{{ shelf.title }} - {{ shelf.count }} + {{ shelf.count }}
    @@ -133,7 +133,7 @@
    {% trans '创建的收藏单' %} - {{ collections_count }} + {{ collections_count }} {% if identity.user == request.user %} @@ -173,7 +173,7 @@
    {% trans '喜欢的收藏单' %} - {{ liked_collections_count }} + {{ liked_collections_count }}
      diff --git a/journal/templates/review.html b/journal/templates/review.html index f407de60..ced529e2 100644 --- a/journal/templates/review.html +++ b/journal/templates/review.html @@ -50,7 +50,7 @@
      {{ review.owner.display_name }} - @{{ review.owner.handler }} + @{{ review.owner.handle }}
      评论 {{ review.item.title }} diff --git a/journal/templates/user_collection_list.html b/journal/templates/user_collection_list.html index 12ad7a0f..b41e94ba 100644 --- a/journal/templates/user_collection_list.html +++ b/journal/templates/user_collection_list.html @@ -40,7 +40,7 @@ {% if liked %} - {{ collection.owner.display_name }} + title="@{{ collection.owner.handle }}">{{ collection.owner.display_name }} {% endif %}

      {% empty %} diff --git a/journal/templates/user_tag_list.html b/journal/templates/user_tag_list.html index 24ffd141..19afbf89 100644 --- a/journal/templates/user_tag_list.html +++ b/journal/templates/user_tag_list.html @@ -25,7 +25,7 @@ {% for v in tags %} - {{ v.title }} + {{ v.title }} ({{ v.total }}) diff --git a/journal/views/common.py b/journal/views/common.py index 46ec9f6c..cf870af9 100644 --- a/journal/views/common.py +++ b/journal/views/common.py @@ -13,6 +13,7 @@ from common.utils import ( AuthedHttpRequest, PageLinksGenerator, get_uuid_or_404, + profile_identity_required, target_identity_required, ) diff --git a/journal/views/profile.py b/journal/views/profile.py index 7d163b12..a96f9a5a 100644 --- a/journal/views/profile.py +++ b/journal/views/profile.py @@ -1,32 +1,42 @@ import datetime from django.contrib.auth.decorators import login_required -from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied -from django.http import Http404, HttpResponse, HttpResponseRedirect -from django.shortcuts import get_object_or_404, redirect, render +from django.shortcuts import render from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_http_methods -from user_messages import api as msg from catalog.models import * from common.utils import AuthedHttpRequest -from users.models import APIdentity, User -from users.views import render_user_blocked, render_user_not_found from ..forms import * from ..models import * -from .common import render_list, target_identity_required +from .common import profile_identity_required, target_identity_required @require_http_methods(["GET"]) -@target_identity_required +@profile_identity_required def profile(request: AuthedHttpRequest, user_name): target = request.target_identity - # if user.mastodon_acct != user_name and user.username != user_name: - # return redirect(user.url) - if not request.user.is_authenticated and not target.anonymous_viewable: - return render(request, "users/home_anonymous.html", {"user": target.user}) - me = target.user == request.user + + if not request.user.is_authenticated and ( + not target.local or not target.anonymous_viewable + ): + return render( + request, + "users/home_anonymous.html", + {"identity": target, "redir": f"/account/login?next={target.url}"}, + ) + + if (target.local and user_name != target.handle) or ( + not target.local and user_name != f"@{target.handle}" + ): + return render( + request, + "users/home_anonymous.html", + {"identity": target, "redir": target.url}, + ) + + me = target.local and target.user == request.user qv = q_owned_piece_visible_to_user(request.user, target) shelf_list = {} @@ -91,6 +101,7 @@ def profile(request: AuthedHttpRequest, user_name): { "user": target.user, "identity": target, + "me": me, "top_tags": top_tags, "shelf_list": shelf_list, "collections": collections[:10], diff --git a/journal/views/review.py b/journal/views/review.py index 5833072a..5e6814d3 100644 --- a/journal/views/review.py +++ b/journal/views/review.py @@ -3,8 +3,8 @@ import mimetypes from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.syndication.views import Feed -from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied -from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.core.exceptions import BadRequest, PermissionDenied +from django.http import Http404 from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone @@ -106,7 +106,7 @@ MAX_ITEM_PER_TYPE = 10 class ReviewFeed(Feed): def get_object(self, request, *args, **kwargs): - return APIdentity.get_by_handler(kwargs["username"]) + return APIdentity.get_by_handle(kwargs["username"]) def title(self, owner): return "%s的评论" % owner.display_name if owner else "无效链接" diff --git a/users/models/apidentity.py b/users/models/apidentity.py index 33a50353..6a630b20 100644 --- a/users/models/apidentity.py +++ b/users/models/apidentity.py @@ -90,7 +90,7 @@ class APIdentity(models.Model): @property def url(self): - return f"/users/{self.handler}/" + return f"/users/{self.handle}/" if self.local else f"/users/@{self.handle}/" @property def preference(self): @@ -101,11 +101,11 @@ class APIdentity(models.Model): return f"{self.username}@{self.domain_name}" @property - def handler(self): + def handle(self): if self.local: return self.username else: - return f"@{self.username}@{self.domain_name}" + return f"{self.username}@{self.domain_name}" @property def following(self): @@ -214,11 +214,25 @@ class APIdentity(models.Model): return Takahe.get_is_follow_requesting(target.pk, self.pk) @classmethod - def get_by_handler(cls, handler: str) -> "APIdentity": + def get_remote(cls, username, domain): + i = cls.objects.filter( + username__iexact=username, domain_name__iexact=domain, deleted__isnull=True + ).first() + if i: + return i + if domain != settings.SITE_INFO["site_domain"].lower(): + identity = Takahe.get_identity_by_handler(username, domain) + if identity: + return Takahe.get_or_create_remote_apidentity(identity) + + @classmethod + def get_by_handle(cls, handler: str, match_linked=False) -> "APIdentity": """ Handler format 'id' - local identity with username 'id' - 'id@site' - local identity with linked mastodon id == 'id@site' + 'id@site' + match_linked = True - local identity with linked mastodon id == 'id@site' (for backward compatibility) + match_linked = False - remote activitypub identity 'id@site' '@id' - local identity with username 'id' '@id@site' - remote activitypub identity 'id@site' """ @@ -231,22 +245,22 @@ class APIdentity(models.Model): deleted__isnull=True, ) elif l == 2: - return cls.objects.get( - user__mastodon_username__iexact=s[0], - user__mastodon_site__iexact=s[1], - deleted__isnull=True, - ) + if match_linked: + return cls.objects.get( + user__mastodon_username__iexact=s[0], + user__mastodon_site__iexact=s[1], + deleted__isnull=True, + ) + else: + i = cls.get_remote(s[0], s[1]) + if i: + return i + raise cls.DoesNotExist(f"Identity not found @{handler}") elif l == 3 and s[0] == "": - i = cls.objects.filter( - username__iexact=s[1], domain_name__iexact=s[2], deleted__isnull=True - ).first() + i = cls.get_remote(s[1], s[2]) if i: return i - if s[2].lower() != settings.SITE_INFO["site_domain"].lower(): - identity = Takahe.get_identity_by_handler(s[1], s[2]) - if identity: - return Takahe.get_or_create_remote_apidentity(identity) - raise cls.DoesNotExist(f"Identity not exist {handler}") + raise cls.DoesNotExist(f"Identity not found {handler}") else: raise cls.DoesNotExist(f"Identity handler invalid {handler}") diff --git a/users/templates/users/home_anonymous.html b/users/templates/users/home_anonymous.html index acba19e5..3d9982c7 100644 --- a/users/templates/users/home_anonymous.html +++ b/users/templates/users/home_anonymous.html @@ -4,21 +4,16 @@ - - {{ site_name }} - {{ identity.handler }} - + + {{ site_name }} - {{ identity.handle }} + content="{{ site_name }} - {{ identity.handle }}的主页"> - + - {% if identity.user.mastodon_account.url %} + {% if identity.local and identity.user.mastodon_account.url %} Mastodon verification diff --git a/users/templates/users/login.html b/users/templates/users/login.html index ae7ba899..91c7fdc5 100644 --- a/users/templates/users/login.html +++ b/users/templates/users/login.html @@ -47,7 +47,7 @@
      {% if request.user.is_authenticated %} - {% trans '前往首页' %} + {% trans '前往首页' %} {% else %}
      {% csrf_token %} diff --git a/users/templates/users/profile_actions.html b/users/templates/users/profile_actions.html index d01a63d6..792d261c 100644 --- a/users/templates/users/profile_actions.html +++ b/users/templates/users/profile_actions.html @@ -4,7 +4,7 @@ 已屏蔽 @@ -46,7 +46,7 @@ @@ -55,7 +55,7 @@ @@ -66,7 +66,7 @@ @@ -76,7 +76,7 @@ @@ -85,7 +85,7 @@ {% else %} @@ -95,7 +95,7 @@ {% if not relationship.muting %} @@ -105,7 +105,7 @@ @@ -115,11 +115,10 @@ - {% comment %} {% trans '投诉用户' %} {% endcomment %} {% endif %} diff --git a/users/templates/users/relationship_list.html b/users/templates/users/relationship_list.html index 1c0b1c4c..8cde8a11 100644 --- a/users/templates/users/relationship_list.html +++ b/users/templates/users/relationship_list.html @@ -4,7 +4,7 @@ {{ identity.handler }} + onclick="navigator.clipboard.writeText(this.innerText);$(this).data('tooltip','copied');">@{{ identity.handle }}

      {% empty %}

      无数据

      diff --git a/users/tests.py b/users/tests.py index 3e0a555e..48dff2bc 100644 --- a/users/tests.py +++ b/users/tests.py @@ -19,17 +19,17 @@ class UserTest(TestCase): self.domain = settings.SITE_INFO.get("site_domain") def test_handle(self): - self.assertEqual(APIdentity.get_by_handler("Alice"), self.alice) - self.assertEqual(APIdentity.get_by_handler("@alice"), self.alice) - self.assertEqual(APIdentity.get_by_handler("Alice@MySpace"), self.alice) - self.assertEqual(APIdentity.get_by_handler("alice@myspace"), self.alice) - self.assertEqual(APIdentity.get_by_handler("@alice@" + self.domain), self.alice) - self.assertEqual(APIdentity.get_by_handler("@Alice@" + self.domain), self.alice) + self.assertEqual(APIdentity.get_by_handle("Alice"), self.alice) + self.assertEqual(APIdentity.get_by_handle("@alice"), self.alice) + self.assertEqual(APIdentity.get_by_handle("Alice@MySpace", True), self.alice) + self.assertEqual(APIdentity.get_by_handle("alice@myspace", True), self.alice) + self.assertEqual(APIdentity.get_by_handle("@alice@" + self.domain), self.alice) + self.assertEqual(APIdentity.get_by_handle("@Alice@" + self.domain), self.alice) self.assertRaises( - APIdentity.DoesNotExist, APIdentity.get_by_handler, "@Alice@MySpace" + APIdentity.DoesNotExist, APIdentity.get_by_handle, "@Alice@MySpace" ) self.assertRaises( - APIdentity.DoesNotExist, APIdentity.get_by_handler, "@alice@KKCity" + APIdentity.DoesNotExist, APIdentity.get_by_handle, "@alice@KKCity" ) def test_fetch(self): diff --git a/users/views.py b/users/views.py index 4a77750a..b4c254bc 100644 --- a/users/views.py +++ b/users/views.py @@ -62,7 +62,7 @@ def render_user_noanonymous(request): def query_identity(request, handle): try: - i = APIdentity.get_by_handler(handle) + i = APIdentity.get_by_handle(handle) return redirect(i.url) except APIdentity.DoesNotExist: if len(handle.split("@")) == 3: @@ -77,7 +77,7 @@ def query_identity(request, handle): def fetch_refresh(request): handle = request.GET.get("handle", "") try: - i = APIdentity.get_by_handler(handle) + i = APIdentity.get_by_handle(handle) return HTTPResponseHXRedirect(i.url) except: retry = int(request.GET.get("retry", 0)) + 1 @@ -161,7 +161,7 @@ def unblock(request: AuthedHttpRequest, user_name): if request.method != "POST": raise BadRequest() try: - target = APIdentity.get_by_handler(user_name) + target = APIdentity.get_by_handle(user_name) except APIdentity.DoesNotExist: return render_user_not_found(request) target_user = target.user