lib.itmens/journal/views.py
2023-06-03 01:26:52 -04:00

907 lines
31 KiB
Python

import logging
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.utils.translation import gettext_lazy as _
from django.http import Http404, HttpResponse
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
from django.db.models import Count
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from django.core.paginator import Paginator
from .models import *
from django.conf import settings
from django.http import HttpResponseRedirect
from management.models import Announcement
from .forms import *
from mastodon.api import (
get_spoiler_text,
share_review,
share_collection,
get_status_id_by_url,
post_toot,
get_visibility,
)
from users.views import render_user_blocked, render_user_not_found
from users.models import User, Report, Preference
from common.utils import PageLinksGenerator, get_uuid_or_404
from user_messages import api as msg
from datetime import datetime
_logger = logging.getLogger(__name__)
PAGE_SIZE = 10
_checkmark = "✔️".encode("utf-8")
@login_required
def wish(request, item_uuid):
if request.method != "POST":
raise BadRequest()
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
if not item:
raise Http404()
request.user.shelf_manager.move_item(item, ShelfType.WISHLIST)
if request.GET.get("back"):
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
return HttpResponse(_checkmark)
@login_required
def like(request, piece_uuid):
if request.method != "POST":
raise BadRequest()
piece = get_object_or_404(Piece, uid=get_uuid_or_404(piece_uuid))
if not piece:
raise Http404()
Like.user_like_piece(request.user, piece)
if request.GET.get("back"):
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
elif request.GET.get("stats"):
return render(
request,
"like_stats.html",
{
"piece": piece,
"liked": True,
"label": request.GET.get("label"),
"icon": request.GET.get("icon"),
},
)
return HttpResponse(_checkmark)
@login_required
def unlike(request, piece_uuid):
if request.method != "POST":
raise BadRequest()
piece = get_object_or_404(Piece, uid=get_uuid_or_404(piece_uuid))
if not piece:
raise Http404()
Like.user_unlike_piece(request.user, piece)
if request.GET.get("back"):
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
elif request.GET.get("stats"):
return render(
request,
"like_stats.html",
{
"piece": piece,
"liked": False,
"label": request.GET.get("label"),
"icon": request.GET.get("icon"),
},
)
return HttpResponse(_checkmark)
@login_required
def add_to_collection(request, item_uuid):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
if request.method == "GET":
collections = Collection.objects.filter(owner=request.user)
return render(
request,
"add_to_collection.html",
{
"item": item,
"collections": collections,
},
)
else:
cid = int(request.POST.get("collection_id", default=0))
if not cid:
cid = Collection.objects.create(
owner=request.user, title=f"{request.user.username}的收藏单"
).id
collection = Collection.objects.get(owner=request.user, id=cid)
collection.append_item(item, note=request.POST.get("note"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
def render_relogin(request):
return render(
request,
"common/error.html",
{
"url": reverse("users:connect") + "?domain=" + request.user.mastodon_site,
"msg": _("信息已保存,但是未能分享到联邦网络"),
"secondary_msg": _(
"可能是你在联邦网络(Mastodon/Pleroma/...)的登录状态过期了,正在跳转到联邦网络重新登录😼"
),
},
)
@login_required
def mark(request, item_uuid):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
mark = Mark(request.user, item)
if request.method == "GET":
tags = TagManager.get_item_tags_by_user(item, request.user)
shelf_types = [
(n[1], n[2]) for n in iter(ShelfTypeNames) if n[0] == item.category
]
shelf_type = request.GET.get("shelf_type", mark.shelf_type)
return render(
request,
"mark2.html" if request.GET.get("new", 0) else "mark.html",
{
"item": item,
"mark": mark,
"shelf_type": shelf_type,
"tags": ",".join(tags),
"shelf_types": shelf_types,
"date_today": timezone.localdate().isoformat(),
},
)
elif request.method == "POST":
if request.POST.get("delete", default=False):
mark.delete()
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
else:
visibility = int(request.POST.get("visibility", default=0))
rating_grade = request.POST.get("rating_grade", default=0)
rating_grade = int(rating_grade) if rating_grade else None
status = ShelfType(request.POST.get("status"))
text = request.POST.get("text")
tags = request.POST.get("tags")
tags = tags.split(",") if tags else []
share_to_mastodon = bool(
request.POST.get("share_to_mastodon", default=False)
)
mark_date = None
if request.POST.get("mark_anotherday"):
dt = parse_datetime(request.POST.get("mark_date", "") + " 20:00:00")
mark_date = timezone.get_current_timezone().localize(dt) if dt else None
if mark_date and mark_date >= timezone.now():
mark_date = None
TagManager.tag_item_by_user(item, request.user, tags, visibility)
try:
mark.update(
status,
text,
rating_grade,
visibility,
share_to_mastodon=share_to_mastodon,
created_time=mark_date,
)
except ValueError as e:
_logger.warn(f"post to mastodon error {e}")
return render_relogin(request)
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
raise BadRequest()
@login_required
def comment(request, item_uuid, focus_item_uuid):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
focus_item = get_object_or_404(Item, uid=get_uuid_or_404(focus_item_uuid))
if focus_item.parent_item != item:
raise Http404()
comment = Comment.objects.filter(
owner=request.user, item=item, focus_item=focus_item
).first()
if request.method == "GET":
return render(
request,
"comment.html",
{
"item": item,
"focus_item": focus_item,
"comment": comment,
},
)
elif request.method == "POST":
if request.POST.get("delete", default=False):
if not comment:
raise Http404()
comment.delete()
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
visibility = int(request.POST.get("visibility", default=0))
text = request.POST.get("text")
position = (
request.POST.get("position")
if request.POST.get("share_position")
else "0:0:0"
)
try:
pos = datetime.strptime(position, "%H:%M:%S")
position = pos.hour * 3600 + pos.minute * 60 + pos.second
except:
if settings.DEBUG:
raise
position = None
share_to_mastodon = bool(request.POST.get("share_to_mastodon", default=False))
shared_link = None
post_error = False
if share_to_mastodon:
shared_link = comment.metadata.get("shared_link") if comment else None
status_id = get_status_id_by_url(shared_link)
link = focus_item.get_absolute_url_with_position(position or None)
status = f"分享{ItemCategory(item.category).label}{item.title} - {focus_item.title}\n{link}\n\n{text}"
spoiler, status = get_spoiler_text(status, item)
try:
response = post_toot(
request.user.mastodon_site,
status,
get_visibility(visibility, request.user),
request.user.mastodon_token,
False,
status_id,
spoiler,
)
if response and response.status_code in [200, 201]:
j = response.json()
if "url" in j:
shared_link = j["url"]
except Exception as e:
if settings.DEBUG:
raise
post_error = True
if comment:
comment.visibility = visibility
comment.text = text
comment.metadata["position"] = position
if shared_link:
comment.metadata["shared_link"] = shared_link
comment.save()
else:
comment = Comment.objects.create(
owner=request.user,
item=item,
focus_item=focus_item,
text=text,
visibility=visibility,
metadata={"shared_link": shared_link, "position": position},
)
if post_error:
return render_relogin(request)
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
raise BadRequest()
def collection_retrieve(request, collection_uuid):
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_visible_to(request.user):
raise PermissionDenied()
follower_count = collection.likes.all().count()
following = (
Like.user_liked_piece(request.user, collection)
if request.user.is_authenticated
else False
)
featured_since = (
collection.featured_by_user_since(request.user)
if request.user.is_authenticated
else None
)
available_as_featured = (
request.user.is_authenticated
and (following or request.user == collection.owner)
and not featured_since
and collection.members.all().exists()
)
stats = {}
if featured_since:
stats = collection.get_stats_for_user(request.user)
stats["wishlist_deg"] = (
stats["wishlist"] / stats["total"] * 360 if stats["total"] else 0
)
stats["progress_deg"] = (
stats["progress"] / stats["total"] * 360 if stats["total"] else 0
)
stats["complete_deg"] = (
stats["complete"] / stats["total"] * 360 if stats["total"] else 0
)
return render(
request,
"collection.html",
{
"collection": collection,
"follower_count": follower_count,
"following": following,
"stats": stats,
"available_as_featured": available_as_featured,
"featured_since": featured_since,
},
)
@login_required
def collection_add_featured(request, collection_uuid):
if request.method != "POST":
raise BadRequest()
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_visible_to(request.user):
raise PermissionDenied()
FeaturedCollection.objects.update_or_create(owner=request.user, target=collection)
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
@login_required
def collection_remove_featured(request, collection_uuid):
if request.method != "POST":
raise BadRequest()
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_visible_to(request.user):
raise PermissionDenied()
fc = FeaturedCollection.objects.filter(
owner=request.user, target=collection
).first()
if fc:
fc.delete()
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
@login_required
def collection_share(request, collection_uuid):
collection = (
get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if collection_uuid
else None
)
if collection and not collection.is_visible_to(request.user):
raise PermissionDenied()
if request.method == "GET":
return render(request, "collection_share.html", {"collection": collection})
elif request.method == "POST":
visibility = int(request.POST.get("visibility", default=0))
comment = request.POST.get("comment")
if share_collection(collection, comment, request.user, visibility):
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
else:
return render_relogin(request)
else:
raise BadRequest()
def collection_retrieve_items(request, collection_uuid, edit=False, msg=None):
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_visible_to(request.user):
raise PermissionDenied()
form = CollectionForm(instance=collection)
return render(
request,
"collection_items.html",
{
"collection": collection,
"form": form,
"collection_edit": edit or request.GET.get("edit"),
"msg": msg,
},
)
@login_required
def collection_append_item(request, collection_uuid):
if request.method != "POST":
raise BadRequest()
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_editable_by(request.user):
raise PermissionDenied()
url = request.POST.get("url")
note = request.POST.get("note")
item = Item.get_by_url(url)
if item:
collection.append_item(item, note=note)
collection.save()
msg = None
else:
msg = _("条目链接无法识别,请输入本站已有条目的链接。")
return collection_retrieve_items(request, collection_uuid, True, msg)
@login_required
def collection_remove_item(request, collection_uuid, item_uuid):
if request.method != "POST":
raise BadRequest()
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
if not collection.is_editable_by(request.user):
raise PermissionDenied()
collection.remove_item(item)
return collection_retrieve_items(request, collection_uuid, True)
@login_required
def collection_move_item(request, direction, collection_uuid, item_uuid):
if request.method != "POST":
raise BadRequest()
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_editable_by(request.user):
raise PermissionDenied()
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
if direction == "up":
collection.move_up_item(item)
else:
collection.move_down_item(item)
return collection_retrieve_items(request, collection_uuid, True)
@login_required
def collection_update_member_order(request, collection_uuid):
if request.method != "POST":
raise BadRequest()
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_editable_by(request.user):
raise PermissionDenied()
ids = request.POST.get("member_ids", "").strip()
if not ids:
raise BadRequest()
ordered_member_ids = [int(i) for i in ids.split(",")]
collection.update_member_order(ordered_member_ids)
return collection_retrieve_items(request, collection_uuid, True)
@login_required
def collection_update_item_note(request, collection_uuid, item_uuid):
collection = get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if not collection.is_editable_by(request.user):
raise PermissionDenied()
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
if not collection.is_editable_by(request.user):
raise PermissionDenied()
if request.method == "POST":
collection.update_item_metadata(
item, {"note": request.POST.get("note", default="")}
)
return collection_retrieve_items(request, collection_uuid, True)
elif request.method == "GET":
member = collection.get_member_for_item(item)
return render(
request,
"collection_update_item_note.html",
{"collection": collection, "item": item, "note": member.note},
)
else:
raise BadRequest()
@login_required
def collection_edit(request, collection_uuid=None):
collection = (
get_object_or_404(Collection, uid=get_uuid_or_404(collection_uuid))
if collection_uuid
else None
)
if collection and not collection.is_editable_by(request.user):
raise PermissionDenied()
if request.method == "GET":
form = CollectionForm(instance=collection) if collection else CollectionForm()
if request.GET.get("title"):
form.instance.title = request.GET.get("title")
return render(
request,
"collection_edit.html",
{
"form": form,
"collection": collection,
"user": collection.owner if collection else request.user,
},
)
elif request.method == "POST":
form = (
CollectionForm(request.POST, request.FILES, instance=collection)
if collection
else CollectionForm(request.POST)
)
if form.is_valid():
if not collection:
form.instance.owner = request.user
form.instance.edited_time = timezone.now()
form.save()
return redirect(
reverse("journal:collection_retrieve", args=[form.instance.uuid])
)
else:
raise BadRequest()
else:
raise BadRequest()
def review_retrieve(request, review_uuid):
# piece = get_object_or_404(Review, uid=get_uuid_or_404(review_uuid))
piece = Review.get_by_url(review_uuid)
if piece is None:
raise Http404()
if not piece.is_visible_to(request.user):
raise PermissionDenied()
return render(request, "review.html", {"review": piece})
@login_required
def review_edit(request, item_uuid, review_uuid=None):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
review = (
get_object_or_404(Review, uid=get_uuid_or_404(review_uuid))
if review_uuid
else None
)
if review and not review.is_editable_by(request.user):
raise PermissionDenied()
if request.method == "GET":
form = (
ReviewForm(instance=review)
if review
else ReviewForm(initial={"item": item.id, "share_to_mastodon": True})
)
return render(
request,
"review_edit.html",
{
"form": form,
"item": item,
"date_today": timezone.localdate().isoformat(),
},
)
elif request.method == "POST":
form = (
ReviewForm(request.POST, instance=review)
if review
else ReviewForm(request.POST)
)
if form.is_valid():
mark_date = None
if request.POST.get("mark_anotherday"):
mark_date = timezone.get_current_timezone().localize(
parse_datetime(request.POST.get("mark_date") + " 20:00:00")
)
body = form.instance.body
if request.POST.get("leading_space"):
body = re.sub(
r"^(\u2003*)( +)",
lambda s: "\u2003" * ((len(s[2]) + 1) // 2 + len(s[1])),
body,
flags=re.MULTILINE,
)
review = Review.review_item_by_user(
item,
request.user,
form.cleaned_data["title"],
body,
form.cleaned_data["visibility"],
mark_date,
form.cleaned_data["share_to_mastodon"],
)
return redirect(reverse("journal:review_retrieve", args=[review.uuid]))
else:
raise BadRequest()
else:
raise BadRequest()
@login_required
def piece_delete(request, piece_uuid):
piece = get_object_or_404(Piece, uid=get_uuid_or_404(piece_uuid))
return_url = request.GET.get("return_url", None) or "/"
if not piece.is_editable_by(request.user):
raise PermissionDenied()
if request.method == "GET":
return render(
request, "piece_delete.html", {"piece": piece, "return_url": return_url}
)
elif request.method == "POST":
piece.delete()
return redirect(return_url)
else:
raise BadRequest()
def render_list_not_fount(request):
msg = _("相关列表不存在")
return render(
request,
"common/error.html",
{
"msg": msg,
},
)
def _render_list(
request, user_name, type, shelf_type=None, item_category=None, tag_title=None
):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (
request.user.is_blocked_by(user) or request.user.is_blocking(user)
):
return render_user_blocked(request)
tag = None
if type == "mark":
queryset = user.shelf_manager.get_latest_members(shelf_type, item_category)
elif type == "tagmember":
tag = Tag.objects.filter(owner=user, title=tag_title).first()
if not tag:
return render_list_not_fount(request)
if tag.visibility != 0 and user != request.user:
return render_list_not_fount(request)
queryset = TagMember.objects.filter(parent=tag)
elif type == "review":
queryset = Review.objects.filter(owner=user)
queryset = queryset.filter(query_item_category(item_category))
else:
raise BadRequest()
queryset = queryset.filter(q_visible_to(request.user, user)).order_by(
"-created_time"
)
paginator = Paginator(queryset, PAGE_SIZE)
page_number = request.GET.get("page", default=1)
members = paginator.get_page(page_number)
members.pagination = PageLinksGenerator(PAGE_SIZE, page_number, paginator.num_pages)
return render(
request,
f"user_{type}_list.html",
{"user": user, "members": members, "tag": tag},
)
@login_required
def user_mark_list(request, user_name, shelf_type, item_category):
return _render_list(
request, user_name, "mark", shelf_type=shelf_type, item_category=item_category
)
@login_required
def user_tag_member_list(request, user_name, tag_title):
return _render_list(request, user_name, "tagmember", tag_title=tag_title)
@login_required
def user_tag_edit(request):
if request.method == "GET":
tag_title = Tag.cleanup_title(request.GET.get("tag", ""), replace=False)
if not tag_title:
raise Http404()
tag = Tag.objects.filter(owner=request.user, title=tag_title).first()
if not tag:
raise Http404()
return render(request, "tag_edit.html", {"tag": tag})
elif request.method == "POST":
tag_title = Tag.cleanup_title(request.POST.get("title", ""), replace=False)
tag_id = request.POST.get("id")
tag = (
Tag.objects.filter(owner=request.user, id=tag_id).first()
if tag_id
else None
)
if not tag or not tag_title:
msg.error(request.user, _("无效标签"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
if request.POST.get("delete"):
tag.delete()
msg.info(request.user, _("标签已删除"))
return redirect(
reverse("journal:user_tag_list", args=[request.user.mastodon_username])
)
elif (
tag_title != tag.title
and Tag.objects.filter(owner=request.user, title=tag_title).exists()
):
msg.error(request.user, _("标签已存在"))
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
tag.title = tag_title
tag.visibility = int(request.POST.get("visibility", 0))
tag.visibility = 0 if tag.visibility == 0 else 2
tag.save()
msg.info(request.user, _("标签已修改"))
return redirect(
reverse(
"journal:user_tag_member_list",
args=[request.user.mastodon_username, tag.title],
)
)
raise BadRequest()
@login_required
def user_review_list(request, user_name, item_category):
return _render_list(request, user_name, "review", item_category=item_category)
@login_required
def user_tag_list(request, user_name):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (
request.user.is_blocked_by(user) or request.user.is_blocking(user)
):
return render_user_blocked(request)
tags = Tag.objects.filter(owner=user)
if user != request.user:
tags = tags.filter(visibility=0)
tags = tags.values("title").annotate(total=Count("members")).order_by("-total")
return render(
request,
"user_tag_list.html",
{
"user": user,
"tags": tags,
},
)
@login_required
def user_collection_list(request, user_name):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (
request.user.is_blocked_by(user) or request.user.is_blocking(user)
):
return render_user_blocked(request)
collections = Collection.objects.filter(owner=user)
if user != request.user:
if request.user.is_following(user):
collections = collections.filter(visibility__in=[0, 1])
else:
collections = collections.filter(visibility=0)
return render(
request,
"user_collection_list.html",
{
"user": user,
"collections": collections,
},
)
@login_required
def user_liked_collection_list(request, user_name):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (
request.user.is_blocked_by(user) or request.user.is_blocking(user)
):
return render_user_blocked(request)
collections = Collection.objects.filter(likes__owner=user)
if user != request.user:
collections = collections.filter(query_visible(request.user))
return render(
request,
"user_collection_list.html",
{
"user": user,
"collections": collections,
"liked": True,
},
)
def profile_anonymous(request, id):
login_url = settings.LOGIN_URL + "?next=" + request.get_full_path()
try:
username = id.split("@")[0]
site = id.split("@")[1]
return render(
request,
"users/home_anonymous.html",
{
"login_url": login_url,
"username": username,
"site": site,
},
)
except Exception:
return redirect(login_url)
def profile(request, user_name):
if request.method != "GET":
raise BadRequest()
user = User.get(user_name)
if user is None or not user.is_active:
return render_user_not_found(request)
if not request.user.is_authenticated and user.get_preference().no_anonymous_view:
return profile_anonymous(request, user_name)
# access one's own home page
if user != request.user and (
user.is_blocked_by(request.user) or user.is_blocking(request.user)
):
return render_user_blocked(request)
qv = q_visible_to(request.user, user)
shelf_list = {}
visbile_categories = [
ItemCategory.Book,
ItemCategory.Movie,
ItemCategory.TV,
ItemCategory.Music,
ItemCategory.Podcast,
ItemCategory.Game,
]
for category in visbile_categories:
shelf_list[category] = {}
for shelf_type in ShelfType:
members = user.shelf_manager.get_latest_members(
shelf_type, category
).filter(qv)
shelf_list[category][shelf_type] = {
"title": user.shelf_manager.get_label(shelf_type, category),
"count": members.count(),
"members": members[:10].prefetch_related("item"),
}
reviews = (
Review.objects.filter(owner=user)
.filter(qv)
.filter(query_item_category(category))
.order_by("-created_time")
)
shelf_list[category]["reviewed"] = {
"title": "评论过的" + category.label,
"count": reviews.count(),
"members": reviews[:10].prefetch_related("item"),
}
collections = (
Collection.objects.filter(owner=user).filter(qv).order_by("-created_time")
)
liked_collections = (
Like.user_likes_by_class(user, Collection)
.order_by("-edited_time")
.values_list("target_id", flat=True)
)
if user != request.user:
liked_collections = liked_collections.filter(query_visible(request.user))
top_tags = user.tag_manager.public_tags[:10]
else:
top_tags = user.tag_manager.all_tags[:10]
return render(
request,
"profile.html",
{
"user": user,
"top_tags": top_tags,
"shelf_list": shelf_list,
"collections": collections[:10],
"collections_count": collections.count(),
"liked_collections": [
Collection.objects.get(id=i)
for i in liked_collections.order_by("-edited_time")[:10]
],
"liked_collections_count": liked_collections.count(),
"layout": user.get_preference().profile_layout,
},
)
def user_calendar_data(request, user_name):
if request.method != "GET":
raise BadRequest()
user = User.get(user_name)
if user is None or not request.user.is_authenticated:
return HttpResponse("")
max_visiblity = max_visiblity_to(request.user, user)
calendar_data = user.shelf_manager.get_calendar_data(max_visiblity)
return render(
request,
"calendar_data.html",
{
"calendar_data": calendar_data,
},
)