diff --git a/common/static/img/avatar.svg b/common/static/img/avatar.svg new file mode 100644 index 00000000..1b9bc733 --- /dev/null +++ b/common/static/img/avatar.svg @@ -0,0 +1,3 @@ + + + diff --git a/common/templates/_header.html b/common/templates/_header.html index c9eae5aa..7d4ed445 100644 --- a/common/templates/_header.html +++ b/common/templates/_header.html @@ -59,19 +59,19 @@ + src="{% if request.user.is_authenticated %}{{ request.user.avatar }}{% else %}{% static 'img/avatar.svg' %}{% endif %}" /> +{% comment %} + {% if request.user.is_authenticated and not request.user.username %} + + {% endif %} +{% endcomment %} {% if messages %} -
- -
+ {% endif %} diff --git a/common/templates/_sidebar.html b/common/templates/_sidebar.html index 42c76bb8..6af24765 100644 --- a/common/templates/_sidebar.html +++ b/common/templates/_sidebar.html @@ -40,7 +40,7 @@
{% comment %} onclick to workaround webkit issue with in {% endcomment %} - +
diff --git a/journal/templates/collection.html b/journal/templates/collection.html index 8d7edda2..94a2f9f8 100644 --- a/journal/templates/collection.html +++ b/journal/templates/collection.html @@ -55,7 +55,7 @@
- {{ collection.owner.display_name }}
diff --git a/journal/templates/profile.html b/journal/templates/profile.html index 3d55a7f6..ccfd5448 100644 --- a/journal/templates/profile.html +++ b/journal/templates/profile.html @@ -17,7 +17,7 @@ {% endif %} - + {% if user.preference.no_anonymous_view %}{% endif %}
- {{ review.owner.display_name }} + {{ review.owner.display_name }}
diff --git a/journal/urls.py b/journal/urls.py index 18628a69..ee2e0a18 100644 --- a/journal/urls.py +++ b/journal/urls.py @@ -82,7 +82,7 @@ urlpatterns = [ name="collection_remove_featured", ), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/(?P" + r"^users/(?P[~A-Za-z0-9_\-.@]+)/(?P" + _get_all_shelf_types() + ")/(?P" + _get_all_categories() @@ -91,14 +91,14 @@ urlpatterns = [ name="user_mark_list", ), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/reviews/(?P" + r"^users/(?P[~A-Za-z0-9_\-.@]+)/reviews/(?P" + _get_all_categories() + ")/$", user_review_list, name="user_review_list", ), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/tags/(?P.+)/$", + r"^users/(?P[~A-Za-z0-9_\-.@]+)/tags/(?P.+)/$", user_tag_member_list, name="user_tag_member_list", ), @@ -108,23 +108,25 @@ urlpatterns = [ name="user_tag_edit", ), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/collections/$", + r"^users/(?P[~A-Za-z0-9_\-.@]+)/collections/$", user_collection_list, name="user_collection_list", ), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/like/collections/$", + r"^users/(?P[~A-Za-z0-9_\-.@]+)/like/collections/$", user_liked_collection_list, name="user_liked_collection_list", ), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/tags/$", + r"^users/(?P[~A-Za-z0-9_\-.@]+)/tags/$", user_tag_list, name="user_tag_list", ), - re_path(r"^users/(?P[A-Za-z0-9_\-.@]+)/$", profile, name="user_profile"), re_path( - r"^users/(?P[A-Za-z0-9_\-.@]+)/calendar_data$", + r"^users/(?P[~A-Za-z0-9_\-.@]+)/$", profile, name="user_profile" + ), + re_path( + r"^users/(?P[~A-Za-z0-9_\-.@]+)/calendar_data$", user_calendar_data, name="user_calendar_data", ), diff --git a/social/templates/feed_data.html b/social/templates/feed_data.html index a2c2511c..94dfedb2 100644 --- a/social/templates/feed_data.html +++ b/social/templates/feed_data.html @@ -12,7 +12,7 @@ {% for activity in activities %}
- cover + cover
diff --git a/users/account.py b/users/account.py index 1984eba9..58b1728d 100644 --- a/users/account.py +++ b/users/account.py @@ -180,7 +180,9 @@ def OAuth2_login(request): return render(request, "common/error.html", {"msg": _("联邦宇宙访问失败😫")}) return register_new_user( request, - username=None, + username=None + if settings.MASTODON_ALLOW_ANY_SITE + else user_data["username"], mastodon_username=user_data["username"], mastodon_id=user_data["id"], mastodon_site=site, @@ -251,7 +253,6 @@ class RegistrationForm(forms.ModelForm): def send_verification_link(user_id, action, email): s = {"i": user_id, "e": email, "a": action} v = TimestampSigner().sign_object(s) # type: ignore - site = settings.SITE_INFO["site_name"] if action == "verify": subject = f'{settings.SITE_INFO["site_name"]} - {_("验证电子邮件地址")}' url = settings.SITE_INFO["site_url"] + "/account/verify_email?c=" + v @@ -263,15 +264,16 @@ def send_verification_link(user_id, action, email): elif action == "register": subject = f'{settings.SITE_INFO["site_name"]} - {_("注册新账号")}' url = settings.SITE_INFO["site_url"] + "/account/register_email?c=" + v - msg = f"你好,\n{site}还没有与{email}关联的账号。你希望注册一个新账号吗?\n" - msg += f"如果你已经注册过{site}或联邦宇宙(长毛象),不必重新注册,只要用联邦宇宙身份登录{site},再关联这个电子邮件地址,未来就可以通过邮件登录。\n" + msg = f"你好,\n本站没有与{email}关联的账号。你希望注册一个新账号吗?\n" + msg += f"如果你已经注册过本站或联邦宇宙(长毛象),不必重新注册,只要用联邦宇宙身份登录本站,再关联这个电子邮件地址,未来就可以通过邮件登录。\n" msg += f"\n如果你还没有联邦宇宙身份,可以访问这里选择实例并创建一个: https://joinmastodon.org/zh/servers\n" if settings.ALLOW_EMAIL_ONLY_ACCOUNT: - msg += f"\n如果你不便使用联邦宇宙身份,可以点击以下链接注册新的{site}账号,以后再关联到联邦宇宙。\n{url}\n" + msg += f"\n如果你不便使用联邦宇宙身份,可以点击以下链接注册新的本站账号,以后再关联到联邦宇宙。\n{url}\n" msg += f"\n如果你没有打算用此电子邮件地址注册或登录本站,请忽略此邮件。" else: raise ValueError("Invalid action") try: + logger.info(f"Sending email to {email} with subject {subject}") send_mail( subject=subject, message=msg, @@ -287,6 +289,13 @@ def verify_email(request): error = "" try: s = TimestampSigner().unsign_object(request.GET.get("c"), max_age=60 * 15) # type: ignore + except Exception as e: + logger.error(e) + error = _("链接无效或已过期") + return render( + request, "users/verify_email.html", {"success": False, "error": error} + ) + try: email = s["e"] action = s["a"] if action == "verify": @@ -314,7 +323,8 @@ def verify_email(request): else: return register_new_user(request, username=None, email=email) except Exception as e: - error = _("链接已失效") + logger.error(e) + error = _("无法完成验证") return render( request, "users/verify_email.html", {"success": False, "error": error} ) @@ -426,7 +436,7 @@ def swap_login(request, token, site, refresh_token): ] ) django_rq.get_queue("mastodon").enqueue( - refresh_mastodon_data_task, current_user, token + refresh_mastodon_data_task, current_user.pk, token ) messages.add_message( request, messages.INFO, _(f"账号身份已更新为 {username}@{site}。") @@ -443,7 +453,7 @@ def auth_login(request, user): user.mastodon_last_refresh < timezone.now() - timedelta(hours=1) or user.mastodon_account == {} ): - django_rq.get_queue("mastodon").enqueue(refresh_mastodon_data_task, user) + django_rq.get_queue("mastodon").enqueue(refresh_mastodon_data_task, user.pk) def auth_logout(request): @@ -465,7 +475,8 @@ def clear_data(request): if request.META.get("HTTP_AUTHORIZATION"): raise BadRequest("Only for web login") if request.method == "POST": - if request.POST.get("verification") == request.user.mastodon_acct: + v = request.POST.get("verification") + if v and (v == request.user.mastodon_acct or v == request.user.email): django_rq.get_queue("mastodon").enqueue(clear_data_task, request.user.id) auth_logout(request) return redirect(reverse("users:login")) diff --git a/users/data.py b/users/data.py index 8fbec52c..c4ad7b53 100644 --- a/users/data.py +++ b/users/data.py @@ -118,9 +118,9 @@ def export_marks(request): @login_required def sync_mastodon(request): - if request.method == "POST": + if request.method == "POST" and request.user.mastodon_username: django_rq.get_queue("mastodon").enqueue( - refresh_mastodon_data_task, request.user + refresh_mastodon_data_task, request.user.pk ) messages.add_message(request, messages.INFO, _("同步已开始。")) return redirect(reverse("users:data")) diff --git a/users/migrations/0005_add_dedicated_username.py b/users/migrations/0005_add_dedicated_username.py index 5b2a6644..5b23f2d9 100644 --- a/users/migrations/0005_add_dedicated_username.py +++ b/users/migrations/0005_add_dedicated_username.py @@ -3,6 +3,7 @@ import django.contrib.auth.validators from django.db import migrations, models import users.models +from django.conf import settings def move_username(apps, schema_editor): @@ -16,7 +17,7 @@ def move_username(apps, schema_editor): def clear_username(apps, schema_editor): User = apps.get_model("users", "User") for u in User.objects.all(): - u.username = None + u.username = None if settings.ALLOW_ANY_SITE else u.mastodon_username u.save() diff --git a/users/models.py b/users/models.py index 54805247..7638b011 100644 --- a/users/models.py +++ b/users/models.py @@ -13,6 +13,8 @@ from management.models import Announcement from mastodon.api import * from django.urls import reverse from django.db.models import Q +from django.templatetags.static import static +import hashlib RESERVED_USERNAMES = [ @@ -122,6 +124,19 @@ class User(AbstractUser): else (self.username or self.mastodon_acct or "") ) + @property + def avatar(self): + if self.mastodon_account: + return self.mastodon_account.get("avatar") or static( + "static/img/avatar.svg" + ) + if self.email: + return ( + "https://www.gravatar.com/avatar/" + + hashlib.md5(self.email.lower().encode()).hexdigest() + ) + return static("static/img/avatar.svg") + @property def handler(self): return self.mastodon_acct or self.username or f"~{self.pk}" @@ -142,13 +157,13 @@ class User(AbstractUser): def clear(self): if self.mastodon_site == "removed" and not self.is_active: return - self.first_name = self.mastodon_username - self.last_name = self.mastodon_site + self.first_name = self.mastodon_acct or "" + self.last_name = self.email or "" self.is_active = False self.email = None # self.username = "~removed~" + str(self.pk) # to get ready for federation, username has to be reserved - self.mastodon_username = "~removed~" + str(self.pk) + self.mastodon_username = None self.mastodon_id = None self.mastodon_site = "removed" self.mastodon_token = "" @@ -275,14 +290,19 @@ class User(AbstractUser): def get(cls, name): if isinstance(name, str): sp = name.split("@") - if len(sp) == 1: + if name.startswith("~"): + try: + query_kwargs = {"pk": int(name[1:])} + except: + return None + elif len(sp) == 1: query_kwargs = {"username": name} elif len(sp) == 2: query_kwargs = {"mastodon_username": sp[0], "mastodon_site": sp[1]} else: return None - elif isinstance(id, int): - query_kwargs = {"pk": id} + elif isinstance(name, int): + query_kwargs = {"pk": name} else: return None return User.objects.filter(**query_kwargs).first() diff --git a/users/tasks.py b/users/tasks.py index 93f2df4c..c3f4abfc 100644 --- a/users/tasks.py +++ b/users/tasks.py @@ -1,11 +1,17 @@ from django.conf import settings +from .models import User +from loguru import logger -def refresh_mastodon_data_task(user, token=None): +def refresh_mastodon_data_task(user_id, token=None): + user = User.objects.get(pk=user_id) + if not user.mastodon_username: + logger.info(f"{user} mastodon data refresh skipped") + return if token: user.mastodon_token = token if user.refresh_mastodon_data(): user.save() - print(f"{user} mastodon data refreshed") + logger.info(f"{user} mastodon data refreshed") else: - print(f"{user} mastodon data refresh failed") + logger.error(f"{user} mastodon data refresh failed") diff --git a/users/templates/users/account.html b/users/templates/users/account.html index 87deabd7..e29ae127 100644 --- a/users/templates/users/account.html +++ b/users/templates/users/account.html @@ -82,8 +82,12 @@ method="post" enctype="multipart/form-data"> {% csrf_token %} - - 上次更新时间 {{ user.mastodon_last_refresh }} + + + {% if user.mastodon_last_refresh %}上次更新时间 {{ user.mastodon_last_refresh }}{% endif %} +
为了正确高效的展示短评和评论,{{ site_name }}会缓存你在联邦宇宙的关注、屏蔽和静音列表。如果你刚刚更新过帐户的上锁状态、增减过关注、静音或屏蔽,希望立即生效,可以点击这里立刻更新;这类信息也会每天自动同步。
@@ -98,11 +102,12 @@ onsubmit="return confirm('账号数据一旦删除后将无法恢复。确认删除吗?');"> {% csrf_token %}
- 输入完整的 用户名@实例名 以确认删除 - 用户名@实例名 或 电子邮件地址 以确认删除 + {% else %}

+ {{ error }} 链接无效或已过期,点击这里重新登录

{% endif %}