From f1010531b0380be7a465ebe06cd877eb6378a59e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 7 May 2022 17:00:52 -0400 Subject: [PATCH] simplify user register flow to avoid inconsistent state --- users/account.py | 254 ++++++++++++++++++++ users/data.py | 132 +++++++++++ users/{export.py => tasks.py} | 10 + users/templates/users/register.html | 3 +- users/urls.py | 1 - users/views.py | 356 +--------------------------- 6 files changed, 406 insertions(+), 350 deletions(-) create mode 100644 users/account.py create mode 100644 users/data.py rename users/{export.py => tasks.py} (95%) diff --git a/users/account.py b/users/account.py new file mode 100644 index 00000000..b8e47552 --- /dev/null +++ b/users/account.py @@ -0,0 +1,254 @@ +from django.shortcuts import reverse, redirect, render, get_object_or_404 +from django.http import HttpResponseBadRequest, HttpResponse +from django.contrib.auth.decorators import login_required +from django.contrib import auth +from django.contrib.auth import authenticate +from django.core.paginator import Paginator +from django.utils.translation import gettext_lazy as _ +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Count +from .models import User, Report, Preference +from .forms import ReportForm +from mastodon.api import * +from mastodon import mastodon_request_included +from common.config import * +from common.models import MarkStatusEnum +from common.utils import PageLinksGenerator +from management.models import Announcement +from books.models import * +from movies.models import * +from music.models import * +from games.models import * +from books.forms import BookMarkStatusTranslator +from movies.forms import MovieMarkStatusTranslator +from music.forms import MusicMarkStatusTranslator +from games.forms import GameMarkStatusTranslator +from mastodon.models import MastodonApplication +from mastodon.api import verify_account +from django.conf import settings +from urllib.parse import quote +import django_rq +from .account import * +from .tasks import * +from datetime import timedelta +from django.utils import timezone +import json +from django.contrib import messages +from books.models import BookMark, BookReview +from movies.models import MovieMark, MovieReview +from games.models import GameMark, GameReview +from music.models import AlbumMark, SongMark, AlbumReview, SongReview +from collection.models import Collection +from common.importers.goodreads import GoodreadsImporter +from common.importers.douban import DoubanImporter + + +# the 'login' page that user can see +def login(request): + if request.method == 'GET': + selected_site = request.GET.get('site', default='') + + sites = MastodonApplication.objects.all().order_by("domain_name") + + # store redirect url in the cookie + if request.GET.get('next'): + request.session['next_url'] = request.GET.get('next') + + return render( + request, + 'users/login.html', + { + 'sites': sites, + 'scope': quote(settings.MASTODON_CLIENT_SCOPE), + 'selected_site': selected_site, + 'allow_any_site': settings.MASTODON_ALLOW_ANY_SITE, + } + ) + else: + return HttpResponseBadRequest() + + +# connect will redirect to mastodon server +def connect(request): + if not settings.MASTODON_ALLOW_ANY_SITE: + return redirect(reverse("users:login")) + login_domain = request.session['swap_domain'] if request.session.get('swap_login') else request.GET.get('domain') + if not login_domain: + return render(request, 'common/error.html', {'msg': '未指定实例域名', 'secondary_msg': "", }) + login_domain = login_domain.strip().lower().split('//')[-1].split('/')[0].split('@')[-1] + domain, version = get_instance_info(login_domain) + app, error_msg = get_mastodon_application(domain) + if app is None: + return render(request, 'common/error.html', {'msg': error_msg, 'secondary_msg': "", }) + else: + login_url = get_mastodon_login_url(app, login_domain, version, request) + resp = redirect(login_url) + resp.set_cookie('mastodon_domain', domain) + return resp + + +# mastodon server redirect back to here +@mastodon_request_included +def OAuth2_login(request): + if request.method != 'GET': + return HttpResponseBadRequest() + + code = request.GET.get('code') + site = request.COOKIES.get('mastodon_domain') + try: + token, refresh_token = obtain_token(site, request, code) + except ObjectDoesNotExist: + return HttpResponseBadRequest("Mastodon site not registered") + if not token: + return render( + request, + 'common/error.html', + { + 'msg': _("认证失败😫") + } + ) + + if request.session.get('swap_login', False) and request.user.is_authenticated: # swap login for existing user + return swap_login(request, token, site, refresh_token) + + user = authenticate(request, token=token, site=site) + if user: # existing user + user.mastodon_token = token + user.mastodon_refresh_token = refresh_token + user.save(update_fields=['mastodon_token', 'mastodon_refresh_token']) + auth_login(request, user) + if request.session.get('next_url') is not None: + response = redirect(request.session.get('next_url')) + del request.session['next_url'] + else: + response = redirect(reverse('common:home')) + return response + else: # newly registered user + code, user_data = verify_account(site, token) + if code != 200 or user_data is None: + return render( + request, + 'common/error.html', + { + 'msg': _("联邦网络访问失败😫") + } + ) + new_user = User( + username=user_data['username'], + mastodon_id=user_data['id'], + mastodon_site=site, + mastodon_token=token, + mastodon_refresh_token=refresh_token, + mastodon_account=user_data, + ) + new_user.save() + request.session['new_user'] = True + auth_login(request, new_user) + return redirect(reverse('users:register')) + + +@mastodon_request_included +@login_required +def logout(request): + if request.method == 'GET': + # revoke_token(request.user.mastodon_site, request.user.mastodon_token) + auth_logout(request) + return redirect(reverse("users:login")) + else: + return HttpResponseBadRequest() + + +@mastodon_request_included +@login_required +def reconnect(request): + if request.method == 'POST': + request.session['swap_login'] = True + request.session['swap_domain'] = request.POST['domain'] + return connect(request) + else: + return HttpResponseBadRequest() + + +@mastodon_request_included +def register(request): + if request.session.get('new_user'): + del request.session['new_user'] + return render(request, 'users/register.html') + else: + return redirect(reverse('common:home')) + + +def swap_login(request, token, site, refresh_token): + del request.session['swap_login'] + del request.session['swap_domain'] + code, data = verify_account(site, token) + current_user = request.user + if code == 200 and data is not None: + username = data['username'] + if username == current_user.username and site == current_user.mastodon_site: + messages.add_message(request, messages.ERROR, _(f'该身份 {username}@{site} 与当前账号相同。')) + else: + try: + existing_user = User.objects.get(username=username, mastodon_site=site) + messages.add_message(request, messages.ERROR, _(f'该身份 {username}@{site} 已被用于其它账号。')) + except ObjectDoesNotExist: + current_user.username = username + current_user.mastodon_id = data['id'] + current_user.mastodon_site = site + current_user.mastodon_token = token + current_user.mastodon_refresh_token = refresh_token + current_user.mastodon_account = data + current_user.save(update_fields=['username', 'mastodon_id', 'mastodon_site', 'mastodon_token', 'mastodon_refresh_token', 'mastodon_account']) + django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, current_user, token) + messages.add_message(request, messages.INFO, _(f'账号身份已更新为 {username}@{site}。')) + else: + messages.add_message(request, messages.ERROR, _('连接联邦网络获取身份信息失败。')) + return redirect(reverse('users:data')) + + +def auth_login(request, user): + """ Decorates django ``login()``. Attach token to session.""" + auth.login(request, user) + if user.mastodon_last_refresh < timezone.now() - timedelta(hours=1) or user.mastodon_account == {}: + django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, user) + + +def auth_logout(request): + """ Decorates django ``logout()``. Release token in session.""" + auth.logout(request) + + +@login_required +def clear_data(request): + if request.method == 'POST': + if request.POST.get('verification') == request.user.mastodon_username: + BookMark.objects.filter(owner=request.user).delete() + MovieMark.objects.filter(owner=request.user).delete() + GameMark.objects.filter(owner=request.user).delete() + AlbumMark.objects.filter(owner=request.user).delete() + SongMark.objects.filter(owner=request.user).delete() + BookReview.objects.filter(owner=request.user).delete() + MovieReview.objects.filter(owner=request.user).delete() + GameReview.objects.filter(owner=request.user).delete() + AlbumReview.objects.filter(owner=request.user).delete() + SongReview.objects.filter(owner=request.user).delete() + request.user.first_name = request.user.username + request.user.last_name = request.user.mastodon_site + request.user.is_active = False + request.user.username = 'removed_' + str(request.user.id) + request.user.mastodon_id = 0 + request.user.mastodon_site = 'removed' + request.user.mastodon_token = '' + request.user.mastodon_locked = False + request.user.mastodon_followers = [] + request.user.mastodon_following = [] + request.user.mastodon_mutes = [] + request.user.mastodon_blocks = [] + request.user.mastodon_domain_blocks = [] + request.user.mastodon_account = {} + request.user.save() + auth_logout(request) + return redirect(reverse("users:login")) + else: + messages.add_message(request, messages.ERROR, _('验证信息不符。')) + return redirect(reverse("users:data")) diff --git a/users/data.py b/users/data.py new file mode 100644 index 00000000..22a270c3 --- /dev/null +++ b/users/data.py @@ -0,0 +1,132 @@ +from django.shortcuts import reverse, redirect, render, get_object_or_404 +from django.http import HttpResponseBadRequest, HttpResponse +from django.contrib.auth.decorators import login_required +from django.contrib import auth +from django.contrib.auth import authenticate +from django.core.paginator import Paginator +from django.utils.translation import gettext_lazy as _ +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Count +from .models import User, Report, Preference +from .forms import ReportForm +from mastodon.api import * +from mastodon import mastodon_request_included +from common.config import * +from common.models import MarkStatusEnum +from common.utils import PageLinksGenerator +from management.models import Announcement +from books.models import * +from movies.models import * +from music.models import * +from games.models import * +from books.forms import BookMarkStatusTranslator +from movies.forms import MovieMarkStatusTranslator +from music.forms import MusicMarkStatusTranslator +from games.forms import GameMarkStatusTranslator +from mastodon.models import MastodonApplication +from mastodon.api import verify_account +from django.conf import settings +from urllib.parse import quote +import django_rq +from .account import * +from .tasks import * +from datetime import timedelta +from django.utils import timezone +import json +from django.contrib import messages +from books.models import BookMark, BookReview +from movies.models import MovieMark, MovieReview +from games.models import GameMark, GameReview +from music.models import AlbumMark, SongMark, AlbumReview, SongReview +from collection.models import Collection +from common.importers.goodreads import GoodreadsImporter +from common.importers.douban import DoubanImporter + + +@mastodon_request_included +@login_required +def preferences(request): + if request.method == 'POST': + request.user.preference.mastodon_publish_public = bool(request.POST.get('mastodon_publish_public')) + request.user.preference.mastodon_append_tag = request.POST.get('mastodon_append_tag', '').strip() + request.user.preference.save() + return render(request, 'users/preferences.html') + + +@mastodon_request_included +@login_required +def data(request): + return render(request, 'users/data.html', { + 'latest_task': request.user.user_synctasks.order_by("-id").first(), + 'import_status': request.user.preference.import_status, + 'export_status': request.user.preference.export_status + }) + + +@mastodon_request_included +@login_required +def export_reviews(request): + if request.method != 'POST': + return redirect(reverse("users:data")) + return render(request, 'users/data.html') + + +@mastodon_request_included +@login_required +def export_marks(request): + if request.method == 'POST': + if not request.user.preference.export_status.get('marks_pending'): + django_rq.get_queue('export').enqueue(export_marks_task, request.user) + request.user.preference.export_status['marks_pending'] = True + request.user.preference.save() + messages.add_message(request, messages.INFO, _('导出已开始。')) + return redirect(reverse("users:data")) + else: + with open(request.user.preference.export_status['marks_file'], 'rb') as fh: + response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel") + response['Content-Disposition'] = 'attachment;filename="marks.xlsx"' + return response + + +@login_required +def sync_mastodon(request): + if request.method == 'POST': + django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, request.user) + messages.add_message(request, messages.INFO, _('同步已开始。')) + return redirect(reverse("users:data")) + + +@login_required +def reset_visibility(request): + if request.method == 'POST': + visibility = int(request.POST.get('visibility')) + visibility = visibility if visibility >= 0 and visibility <= 2 else 0 + BookMark.objects.filter(owner=request.user).update(visibility=visibility) + MovieMark.objects.filter(owner=request.user).update(visibility=visibility) + GameMark.objects.filter(owner=request.user).update(visibility=visibility) + AlbumMark.objects.filter(owner=request.user).update(visibility=visibility) + SongMark.objects.filter(owner=request.user).update(visibility=visibility) + messages.add_message(request, messages.INFO, _('已重置。')) + return redirect(reverse("users:data")) + + +@login_required +def import_goodreads(request): + if request.method == 'POST': + raw_url = request.POST.get('url') + if GoodreadsImporter.import_from_url(raw_url, request.user): + messages.add_message(request, messages.INFO, _('链接已保存,等待后台导入。')) + else: + messages.add_message(request, messages.ERROR, _('无法识别链接。')) + return redirect(reverse("users:data")) + + +@login_required +def import_douban(request): + if request.method == 'POST': + importer = DoubanImporter(request.user, request.POST.get('visibility')) + if importer.import_from_file(request.FILES['file']): + messages.add_message(request, messages.INFO, _('文件上传成功,等待后台导入。')) + else: + messages.add_message(request, messages.ERROR, _('无法识别文件。')) + return redirect(reverse("users:data")) diff --git a/users/export.py b/users/tasks.py similarity index 95% rename from users/export.py rename to users/tasks.py index 372285d5..040b33aa 100644 --- a/users/export.py +++ b/users/tasks.py @@ -32,6 +32,16 @@ from datetime import datetime import os +def refresh_mastodon_data_task(user, token=None): + if token: + user.mastodon_token = token + if user.refresh_mastodon_data(): + user.save() + print(f"{user} mastodon data refreshed") + else: + print(f"{user} mastodon data refresh failed") + + def export_marks_task(user): user.preference.export_status['marks_pending'] = True user.preference.save() diff --git a/users/templates/users/register.html b/users/templates/users/register.html index 90ec7412..498cf639 100644 --- a/users/templates/users/register.html +++ b/users/templates/users/register.html @@ -30,8 +30,7 @@ 此外,{{ site_name }}现处于测试阶段,疏漏在所难免,请妥善备份您的数据。 使用过程中遇到的问题或者错误欢迎向维护者提出。感谢理解和支持!

-
- {% csrf_token %} +
diff --git a/users/urls.py b/users/urls.py index 7e7279e2..7c5f57ff 100644 --- a/users/urls.py +++ b/users/urls.py @@ -17,7 +17,6 @@ urlpatterns = [ path('data/clear_data', clear_data, name='clear_data'), path('preferences/', preferences, name='preferences'), path('logout/', logout, name='logout'), - path('delete/', delete, name='delete'), path('layout/', set_layout, name='set_layout'), path('OAuth2_login/', OAuth2_login, name='OAuth2_login'), path('/', home_redirect, name='home_redirect'), diff --git a/users/views.py b/users/views.py index a4776f3a..76cb2828 100644 --- a/users/views.py +++ b/users/views.py @@ -28,7 +28,8 @@ from mastodon.api import verify_account from django.conf import settings from urllib.parse import quote import django_rq -from .export import * +from .account import * +from .data import * from datetime import timedelta from django.utils import timezone import json @@ -42,196 +43,6 @@ from common.importers.goodreads import GoodreadsImporter from common.importers.douban import DoubanImporter -# Views -######################################## -def swap_login(request, token, site, refresh_token): - del request.session['swap_login'] - del request.session['swap_domain'] - code, data = verify_account(site, token) - current_user = request.user - if code == 200 and data is not None: - username = data['username'] - if username == current_user.username and site == current_user.mastodon_site: - messages.add_message(request, messages.ERROR, _(f'该身份 {username}@{site} 与当前账号相同。')) - else: - try: - existing_user = User.objects.get(username=username, mastodon_site=site) - messages.add_message(request, messages.ERROR, _(f'该身份 {username}@{site} 已被用于其它账号。')) - except ObjectDoesNotExist: - current_user.username = username - current_user.mastodon_id = data['id'] - current_user.mastodon_site = site - current_user.mastodon_token = token - current_user.mastodon_refresh_token = refresh_token - current_user.mastodon_account = data - current_user.save(update_fields=['username', 'mastodon_id', 'mastodon_site', 'mastodon_token', 'mastodon_refresh_token', 'mastodon_account']) - django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, current_user, token) - messages.add_message(request, messages.INFO, _(f'账号身份已更新为 {username}@{site}。')) - else: - messages.add_message(request, messages.ERROR, _('连接联邦网络获取身份信息失败。')) - return redirect(reverse('users:data')) - - -# no page rendered -@mastodon_request_included -def OAuth2_login(request): - """ oauth authentication and logging user into django system """ - if request.method == 'GET': - code = request.GET.get('code') - site = request.COOKIES.get('mastodon_domain') - - # Network IO - try: - token, refresh_token = obtain_token(site, request, code) - except ObjectDoesNotExist: - return HttpResponseBadRequest("Mastodon site not registered") - if token: - if request.session.get('swap_login', False) and request.user.is_authenticated: # swap login for existing user - return swap_login(request, token, site, refresh_token) - user = authenticate(request, token=token, site=site) - if user: - user.mastodon_token = token - user.mastodon_refresh_token = refresh_token - user.save(update_fields=['mastodon_token', 'mastodon_refresh_token']) - auth_login(request, user) - if request.session.get('next_url') is not None: - response = redirect(request.session.get('next_url')) - del request.session['next_url'] - else: - response = redirect(reverse('common:home')) - - response.delete_cookie('mastodon_domain') - return response - else: - # will be passed to register page - request.session['new_user_token'] = token - request.session['new_user_refresh_token'] = refresh_token - return redirect(reverse('users:register')) - else: - return render( - request, - 'common/error.html', - { - 'msg': _("认证失败😫") - } - ) - else: - return HttpResponseBadRequest() - - -# the 'login' page that user can see -def login(request): - if request.method == 'GET': - selected_site = request.GET.get('site', default='') - - sites = MastodonApplication.objects.all().order_by("domain_name") - - # store redirect url in the cookie - if request.GET.get('next'): - request.session['next_url'] = request.GET.get('next') - - return render( - request, - 'users/login.html', - { - 'sites': sites, - 'scope': quote(settings.MASTODON_CLIENT_SCOPE), - 'selected_site': selected_site, - 'allow_any_site': settings.MASTODON_ALLOW_ANY_SITE, - } - ) - else: - return HttpResponseBadRequest() - - -def connect(request): - if not settings.MASTODON_ALLOW_ANY_SITE: - return redirect(reverse("users:login")) - login_domain = request.session['swap_domain'] if request.session.get('swap_login') else request.GET.get('domain') - if not login_domain: - return render(request, 'common/error.html', {'msg': '未指定实例域名', 'secondary_msg': "", }) - login_domain = login_domain.strip().lower().split('//')[-1].split('/')[0].split('@')[-1] - domain, version = get_instance_info(login_domain) - app, error_msg = get_mastodon_application(domain) - if app is None: - return render(request, 'common/error.html', {'msg': error_msg, 'secondary_msg': "", }) - else: - login_url = get_mastodon_login_url(app, login_domain, version, request) - resp = redirect(login_url) - resp.set_cookie("mastodon_domain", domain) - return resp - - -@mastodon_request_included -@login_required -def reconnect(request): - if request.method == 'POST': - request.session['swap_login'] = True - request.session['swap_domain'] = request.POST['domain'] - return connect(request) - else: - return HttpResponseBadRequest() - - -@mastodon_request_included -@login_required -def logout(request): - if request.method == 'GET': - # revoke_token(request.user.mastodon_site, request.user.mastodon_token) - auth_logout(request) - return redirect(reverse("users:login")) - else: - return HttpResponseBadRequest() - - -@mastodon_request_included -def register(request): - """ register confirm page """ - if request.method == 'GET': - if request.user.is_authenticated: - return redirect(reverse('common:home')) - elif request.session.get('new_user_token'): - return render( - request, - 'users/register.html' - ) - else: - return HttpResponseBadRequest() - elif request.method == 'POST': - token = request.session['new_user_token'] - refresh_token = request.session['new_user_refresh_token'] - code, user_data = verify_account(request.COOKIES['mastodon_domain'], token) - if code != 200 or user_data is None: - return render( - request, - 'common/error.html', - { - 'msg': _("联邦网络访问失败😫") - } - ) - new_user = User( - username=user_data['username'], - mastodon_id=user_data['id'], - mastodon_site=request.COOKIES['mastodon_domain'], - mastodon_token=token, - mastodon_refresh_token=refresh_token, - mastodon_account=user_data, - ) - new_user.save() - del request.session['new_user_token'] - del request.session['new_user_refresh_token'] - auth_login(request, new_user) - response = redirect(reverse('common:home')) - response.delete_cookie('mastodon_domain') - return response - else: - return HttpResponseBadRequest() - - -def delete(request): - raise NotImplementedError - - def home_redirect(request, id): try: query_kwargs = {'pk': id} @@ -247,10 +58,10 @@ def home_anonymous(request, id): username = id.split('@')[0] site = id.split('@')[1] return render(request, 'users/home_anonymous.html', { - 'login_url': login_url, - 'username': username, - 'site': site, - }) + 'login_url': login_url, + 'username': username, + 'site': site, + }) except Exception: return redirect(login_url) @@ -349,7 +160,7 @@ def home(request, id): # movie marks filtered_movie_marks = filter_marks(movie_marks, MOVIES_PER_SET, 'movie') - movie_marks_count= count_marks(movie_marks, "movie") + movie_marks_count = count_marks(movie_marks, "movie") # game marks filtered_game_marks = filter_marks(game_marks, GAMES_PER_SET, 'game') @@ -800,7 +611,7 @@ def game_list(request, id, status): if status == 'reviewed': queryset = GameReview.objects.filter(owner=user).order_by("-edited_time") elif status == 'tagged': - queryset = GameTag.objects.filter(content=tag, mark__owner=user).order_by("-mark__edited_time") + queryset = GameTag.objects.filter(content=tag, mark__owner=user).order_by("-mark__edited_time") else: queryset = GameMark.objects.filter( owner=user, status=MarkStatusEnum[status.upper()]).order_by("-edited_time") @@ -816,7 +627,7 @@ def game_list(request, id, status): elif status == 'tagged': list_title = str(_(f"标记为「{tag}」的游戏")) else: - list_title = str(GameMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的游戏")) + list_title = str(GameMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的游戏")) return render( request, 'users/item_list.html', @@ -1020,152 +831,3 @@ def collection_list(request, id): } ) return list(request, user.id) - - -# Utils -######################################## -def refresh_mastodon_data_task(user, token=None): - if token: - user.mastodon_token = token - if user.refresh_mastodon_data(): - user.save() - print(f"{user} mastodon data refreshed") - else: - print(f"{user} mastodon data refresh failed") - - -def auth_login(request, user): - """ Decorates django ``login()``. Attach token to session.""" - auth.login(request, user) - if user.mastodon_last_refresh < timezone.now() - timedelta(hours=1) or user.mastodon_account == {}: - django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, user) - - -def auth_logout(request): - """ Decorates django ``logout()``. Release token in session.""" - auth.logout(request) - - -@mastodon_request_included -@login_required -def preferences(request): - if request.method == 'POST': - request.user.preference.mastodon_publish_public = bool(request.POST.get('mastodon_publish_public')) - request.user.preference.mastodon_append_tag = request.POST.get('mastodon_append_tag', '').strip() - request.user.preference.save() - return render(request, 'users/preferences.html') - - -@mastodon_request_included -@login_required -def data(request): - return render(request, 'users/data.html', { - 'latest_task': request.user.user_synctasks.order_by("-id").first(), - 'import_status': request.user.preference.import_status, - 'export_status': request.user.preference.export_status - }) - - -@mastodon_request_included -@login_required -def export_reviews(request): - if request.method != 'POST': - return redirect(reverse("users:data")) - return render(request, 'users/data.html') - - -@mastodon_request_included -@login_required -def export_marks(request): - if request.method == 'POST': - if not request.user.preference.export_status.get('marks_pending'): - django_rq.get_queue('export').enqueue(export_marks_task, request.user) - request.user.preference.export_status['marks_pending'] = True - request.user.preference.save() - messages.add_message(request, messages.INFO, _('导出已开始。')) - return redirect(reverse("users:data")) - else: - with open(request.user.preference.export_status['marks_file'], 'rb') as fh: - response = HttpResponse(fh.read(), content_type="application/vnd.ms-excel") - response['Content-Disposition'] = 'attachment;filename="marks.xlsx"' - return response - - -@login_required -def sync_mastodon(request): - if request.method == 'POST': - django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, request.user) - messages.add_message(request, messages.INFO, _('同步已开始。')) - return redirect(reverse("users:data")) - - -@login_required -def reset_visibility(request): - if request.method == 'POST': - visibility = int(request.POST.get('visibility')) - visibility = visibility if visibility >= 0 and visibility <= 2 else 0 - BookMark.objects.filter(owner=request.user).update(visibility=visibility) - MovieMark.objects.filter(owner=request.user).update(visibility=visibility) - GameMark.objects.filter(owner=request.user).update(visibility=visibility) - AlbumMark.objects.filter(owner=request.user).update(visibility=visibility) - SongMark.objects.filter(owner=request.user).update(visibility=visibility) - messages.add_message(request, messages.INFO, _('已重置。')) - return redirect(reverse("users:data")) - - -@login_required -def clear_data(request): - if request.method == 'POST': - if request.POST.get('verification') == request.user.mastodon_username: - BookMark.objects.filter(owner=request.user).delete() - MovieMark.objects.filter(owner=request.user).delete() - GameMark.objects.filter(owner=request.user).delete() - AlbumMark.objects.filter(owner=request.user).delete() - SongMark.objects.filter(owner=request.user).delete() - BookReview.objects.filter(owner=request.user).delete() - MovieReview.objects.filter(owner=request.user).delete() - GameReview.objects.filter(owner=request.user).delete() - AlbumReview.objects.filter(owner=request.user).delete() - SongReview.objects.filter(owner=request.user).delete() - request.user.first_name = request.user.username - request.user.last_name = request.user.mastodon_site - request.user.is_active = False - request.user.username = 'removed_' + str(request.user.id) - request.user.mastodon_id = 0 - request.user.mastodon_site = 'removed' - request.user.mastodon_token = '' - request.user.mastodon_locked = False - request.user.mastodon_followers = [] - request.user.mastodon_following = [] - request.user.mastodon_mutes = [] - request.user.mastodon_blocks = [] - request.user.mastodon_domain_blocks = [] - request.user.mastodon_account = {} - request.user.save() - auth_logout(request) - return redirect(reverse("users:login")) - else: - messages.add_message(request, messages.ERROR, _('验证信息不符。')) - return redirect(reverse("users:data")) - - -@login_required -def import_goodreads(request): - if request.method == 'POST': - raw_url = request.POST.get('url') - if GoodreadsImporter.import_from_url(raw_url, request.user): - messages.add_message(request, messages.INFO, _('链接已保存,等待后台导入。')) - else: - messages.add_message(request, messages.ERROR, _('无法识别链接。')) - return redirect(reverse("users:data")) - - -@login_required -def import_douban(request): - if request.method == 'POST': - importer = DoubanImporter(request.user, request.POST.get('visibility')) - if importer.import_from_file(request.FILES['file']): - messages.add_message(request, messages.INFO, _('文件上传成功,等待后台导入。')) - else: - messages.add_message(request, messages.ERROR, _('无法识别文件。')) - return redirect(reverse("users:data"))