lib.itmens/books/views.py

594 lines
22 KiB
Python
Raw Normal View History

import logging
2020-05-01 22:46:15 +08:00
from django.shortcuts import render, get_object_or_404, redirect, reverse
2020-10-26 00:17:26 +01:00
from django.contrib.auth.decorators import login_required, permission_required
2020-05-01 22:46:15 +08:00
from django.utils.translation import gettext_lazy as _
2020-05-05 23:50:48 +08:00
from django.http import HttpResponseBadRequest, HttpResponseServerError
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import IntegrityError, transaction
2020-07-10 21:28:09 +08:00
from django.db.models import Count
2020-05-05 23:50:48 +08:00
from django.utils import timezone
from django.core.paginator import Paginator
2020-10-22 21:45:05 +02:00
from mastodon import mastodon_request_included
from mastodon.models import MastodonApplication
2021-12-20 22:59:32 -05:00
from mastodon.api import post_toot, TootVisibilityEnum
2020-10-22 21:45:05 +02:00
from mastodon.utils import rating_to_emoji
2020-07-03 15:36:23 +08:00
from common.utils import PageLinksGenerator
2020-12-09 20:09:40 +01:00
from common.views import PAGE_LINK_NUMBER, jump_or_scrape
from common.models import SourceSiteEnum
2020-05-01 22:46:15 +08:00
from .models import *
from .forms import *
2020-05-05 23:50:48 +08:00
from .forms import BookMarkStatusTranslator
from django.conf import settings
from collection.models import CollectionItem
2020-05-01 22:46:15 +08:00
2020-08-28 22:22:07 +08:00
logger = logging.getLogger(__name__)
mastodon_logger = logging.getLogger("django.mastodon")
2020-05-05 23:50:48 +08:00
# how many marks showed on the detail page
MARK_NUMBER = 5
# how many marks at the mark page
MARK_PER_PAGE = 20
# how many reviews showed on the detail page
REVIEW_NUMBER = 5
# how many reviews at the mark page
REVIEW_PER_PAGE = 20
2020-07-10 21:28:09 +08:00
# max tags on detail page
TAG_NUMBER = 10
2020-05-05 23:50:48 +08:00
# public data
###########################
2020-05-01 22:46:15 +08:00
@login_required
def create(request):
if request.method == 'GET':
form = BookForm()
return render(
request,
'books/create_update.html',
{
'form': form,
2020-05-05 23:50:48 +08:00
'title': _('添加书籍'),
'submit_url': reverse("books:create"),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
2020-05-01 22:46:15 +08:00
}
)
elif request.method == 'POST':
2020-05-05 23:50:48 +08:00
if request.user.is_authenticated:
# only local user can alter public data
form = BookForm(request.POST, request.FILES)
if form.is_valid():
form.instance.last_editor = request.user
try:
with transaction.atomic():
form.save()
if form.instance.source_site == SourceSiteEnum.IN_SITE.value:
real_url = form.instance.get_absolute_url()
form.instance.source_url = real_url
form.instance.save()
except IntegrityError as e:
logger.error(e.__str__())
return HttpResponseServerError("integrity error")
2020-05-05 23:50:48 +08:00
return redirect(reverse("books:retrieve", args=[form.instance.id]))
else:
return render(
request,
'books/create_update.html',
{
'form': form,
'title': _('添加书籍'),
'submit_url': reverse("books:create"),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
2020-05-05 23:50:48 +08:00
}
)
else:
return redirect(reverse("users:login"))
else:
return HttpResponseBadRequest()
@login_required
def update(request, id):
if request.method == 'GET':
book = get_object_or_404(Book, pk=id)
form = BookForm(instance=book)
return render(
request,
'books/create_update.html',
{
'form': form,
'title': _('修改书籍'),
'submit_url': reverse("books:update", args=[book.id]),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
2020-05-05 23:50:48 +08:00
}
)
elif request.method == 'POST':
book = get_object_or_404(Book, pk=id)
2020-05-07 06:01:22 +08:00
form = BookForm(request.POST, request.FILES, instance=book)
2020-05-01 22:46:15 +08:00
if form.is_valid():
form.instance.last_editor = request.user
2020-05-05 23:50:48 +08:00
form.instance.edited_time = timezone.now()
try:
with transaction.atomic():
form.save()
if form.instance.source_site == SourceSiteEnum.IN_SITE.value:
real_url = form.instance.get_absolute_url()
form.instance.source_url = real_url
form.instance.save()
except IntegrityError as e:
logger.error(e.__str__())
return HttpResponseServerError("integrity error")
else:
return render(
2020-07-10 21:28:09 +08:00
request,
'books/create_update.html',
{
'form': form,
'title': _('修改书籍'),
'submit_url': reverse("books:update", args=[book.id]),
# provided for frontend js
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
2020-07-10 21:28:09 +08:00
}
)
2020-05-01 22:46:15 +08:00
return redirect(reverse("books:retrieve", args=[form.instance.id]))
2020-05-05 23:50:48 +08:00
2020-05-01 22:46:15 +08:00
else:
2020-07-10 21:28:09 +08:00
return HttpResponseBadRequest()
2020-05-01 22:46:15 +08:00
2020-05-05 23:50:48 +08:00
@mastodon_request_included
2020-07-03 16:12:17 +08:00
# @login_required
2020-05-01 22:46:15 +08:00
def retrieve(request, id):
if request.method == 'GET':
book = get_object_or_404(Book, pk=id)
2020-05-05 23:50:48 +08:00
mark = None
2020-07-10 21:28:09 +08:00
mark_tags = None
2020-05-05 23:50:48 +08:00
review = None
2020-07-10 21:28:09 +08:00
# retreive tags
book_tag_list = book.book_tags.values('content').annotate(
tag_frequency=Count('content')).order_by('-tag_frequency')[:TAG_NUMBER]
# retrieve user mark and initialize mark form
2020-05-05 23:50:48 +08:00
try:
2020-07-03 16:12:17 +08:00
if request.user.is_authenticated:
mark = BookMark.objects.get(owner=request.user, book=book)
2020-05-05 23:50:48 +08:00
except ObjectDoesNotExist:
mark = None
if mark:
2020-10-03 23:27:41 +02:00
mark_tags = mark.bookmark_tags.all()
2020-05-05 23:50:48 +08:00
mark.get_status_display = BookMarkStatusTranslator(mark.status)
2020-07-10 21:28:09 +08:00
mark_form = BookMarkForm(instance=mark, initial={
'tags': mark_tags
})
2020-05-05 23:50:48 +08:00
else:
mark_form = BookMarkForm(initial={
2020-07-10 21:28:09 +08:00
'book': book,
'tags': mark_tags
2020-05-05 23:50:48 +08:00
})
2020-07-10 21:28:09 +08:00
# retrieve user review
2020-05-05 23:50:48 +08:00
try:
2020-07-03 16:12:17 +08:00
if request.user.is_authenticated:
review = BookReview.objects.get(owner=request.user, book=book)
2020-05-05 23:50:48 +08:00
except ObjectDoesNotExist:
review = None
2020-07-03 16:12:17 +08:00
2020-07-10 21:28:09 +08:00
# retrieve other related reviews and marks
2020-07-03 16:12:17 +08:00
if request.user.is_anonymous:
2020-07-10 21:28:09 +08:00
# hide all marks and reviews for anonymous user
2020-07-03 16:12:17 +08:00
mark_list = None
review_list = None
mark_list_more = None
review_list_more = None
else:
2021-12-15 21:54:24 -05:00
mark_list = BookMark.get_available(book, request.user)
review_list = BookReview.get_available(book, request.user)
2020-07-03 16:12:17 +08:00
mark_list_more = True if len(mark_list) > MARK_NUMBER else False
mark_list = mark_list[:MARK_NUMBER]
for m in mark_list:
m.get_status_display = BookMarkStatusTranslator(m.status)
2020-07-10 21:28:09 +08:00
review_list_more = True if len(
review_list) > REVIEW_NUMBER else False
2020-07-03 16:12:17 +08:00
review_list = review_list[:REVIEW_NUMBER]
collection_list = filter(lambda c: c.is_visible_to(request.user), map(lambda i: i.collection, CollectionItem.objects.filter(book=book)))
2020-07-03 16:12:17 +08:00
2020-05-05 23:50:48 +08:00
# def strip_html_tags(text):
# import re
# regex = re.compile('<.*?>')
# return re.sub(regex, '', text)
# for r in review_list:
# r.content = strip_html_tags(r.content)
2020-05-01 22:46:15 +08:00
return render(
request,
'books/detail.html',
{
'book': book,
2020-05-05 23:50:48 +08:00
'mark': mark,
'review': review,
'status_enum': MarkStatusEnum,
'mark_form': mark_form,
'mark_list': mark_list,
'mark_list_more': mark_list_more,
'review_list': review_list,
'review_list_more': review_list_more,
2020-07-10 21:28:09 +08:00
'book_tag_list': book_tag_list,
'mark_tags': mark_tags,
'collection_list': collection_list,
2020-05-05 23:50:48 +08:00
}
)
else:
2020-08-28 22:22:07 +08:00
logger.warning('non-GET method at /book/<id>')
2020-10-22 22:21:29 +02:00
return HttpResponseBadRequest()
2020-05-05 23:50:48 +08:00
2020-10-26 00:17:26 +01:00
@permission_required('books.delete_book')
2020-05-05 23:50:48 +08:00
@login_required
def delete(request, id):
if request.method == 'GET':
book = get_object_or_404(Book, pk=id)
return render(
request,
'books/delete.html',
{
'book': book,
}
)
elif request.method == 'POST':
if request.user.is_staff:
# only staff has right to delete
book = get_object_or_404(Book, pk=id)
book.delete()
2020-10-26 00:18:57 +01:00
return redirect(reverse("common:home"))
2020-05-05 23:50:48 +08:00
else:
raise PermissionDenied()
else:
2020-07-10 21:28:09 +08:00
return HttpResponseBadRequest()
2020-05-05 23:50:48 +08:00
# user owned entites
###########################
@mastodon_request_included
@login_required
def create_update_mark(request):
# check list:
# clean rating if is wish
# transaction on updating book rating
# owner check(guarantee)
if request.method == 'POST':
pk = request.POST.get('id')
old_rating = None
2020-07-10 21:28:09 +08:00
old_tags = None
2020-05-05 23:50:48 +08:00
if pk:
mark = get_object_or_404(BookMark, pk=pk)
2020-10-26 00:17:26 +01:00
if request.user != mark.owner:
return HttpResponseBadRequest()
2020-05-05 23:50:48 +08:00
old_rating = mark.rating
2020-10-03 23:27:41 +02:00
old_tags = mark.bookmark_tags.all()
2020-05-05 23:50:48 +08:00
# update
form = BookMarkForm(request.POST, instance=mark)
else:
# create
form = BookMarkForm(request.POST)
if form.is_valid():
if form.instance.status == MarkStatusEnum.WISH.value or form.instance.rating == 0:
2020-05-05 23:50:48 +08:00
form.instance.rating = None
2020-05-08 12:52:44 +08:00
form.cleaned_data['rating'] = None
2020-05-05 23:50:48 +08:00
form.instance.owner = request.user
form.instance.edited_time = timezone.now()
book = form.instance.book
2020-07-10 21:28:09 +08:00
2020-05-05 23:50:48 +08:00
try:
with transaction.atomic():
# update book rating
book.update_rating(old_rating, form.instance.rating)
form.save()
2020-07-10 21:28:09 +08:00
# update tags
if old_tags:
for tag in old_tags:
tag.delete()
if form.cleaned_data['tags']:
for tag in form.cleaned_data['tags']:
BookTag.objects.create(
content=tag,
book=book,
mark=form.instance
)
2020-05-05 23:50:48 +08:00
except IntegrityError as e:
2020-08-28 22:22:07 +08:00
logger.error(e.__str__())
2020-11-05 12:58:32 +01:00
return HttpResponseServerError("integrity error")
2020-05-05 23:50:48 +08:00
if form.cleaned_data['share_to_mastodon']:
2021-12-20 22:59:32 -05:00
if form.cleaned_data['visibility'] == 2:
visibility = TootVisibilityEnum.DIRECT
elif form.cleaned_data['visibility'] == 1:
2020-05-05 23:50:48 +08:00
visibility = TootVisibilityEnum.PRIVATE
else:
visibility = TootVisibilityEnum.PUBLIC if request.user.preference.mastodon_publish_public else TootVisibilityEnum.UNLISTED
2020-07-10 21:28:09 +08:00
url = "https://" + request.get_host() + reverse("books:retrieve",
args=[book.id])
2020-11-29 13:33:13 +01:00
words = BookMarkStatusTranslator(form.cleaned_data['status']) +\
2020-07-10 21:28:09 +08:00
f"{book.title}" + \
rating_to_emoji(form.cleaned_data['rating'], MastodonApplication.objects.get(domain_name=request.user.mastodon_site).star_mode)
2020-07-29 23:34:06 +08:00
# tags = settings.MASTODON_TAGS % {'category': '书', 'type': '标记'}
2020-08-28 22:22:07 +08:00
tags = ''
2020-07-29 23:34:06 +08:00
content = words + '\n' + url + '\n' + \
form.cleaned_data['text'] + '\n' + tags
2020-10-22 23:27:16 +02:00
response = post_toot(
request.user.mastodon_site, content, visibility, request.session['oauth_token'])
if response.status_code != 200:
2020-08-28 22:22:07 +08:00
mastodon_logger.error(f"CODE:{response.status_code} {response.text}")
return HttpResponseServerError("publishing mastodon status failed")
2020-05-05 23:50:48 +08:00
else:
return HttpResponseBadRequest(f"invalid form data {form.errors}")
2020-05-05 23:50:48 +08:00
return redirect(reverse("books:retrieve", args=[form.instance.book.id]))
else:
2020-07-15 16:58:19 +08:00
return HttpResponseBadRequest("invalid method")
2020-05-05 23:50:48 +08:00
@mastodon_request_included
@login_required
2021-12-15 21:54:24 -05:00
def retrieve_mark_list(request, book_id, following_only=False):
2020-05-05 23:50:48 +08:00
if request.method == 'GET':
book = get_object_or_404(Book, pk=book_id)
2021-12-15 21:54:24 -05:00
queryset = BookMark.get_available(book, request.user, following_only=following_only)
2020-05-05 23:50:48 +08:00
paginator = Paginator(queryset, MARK_PER_PAGE)
page_number = request.GET.get('page', default=1)
marks = paginator.get_page(page_number)
2020-07-10 21:28:09 +08:00
marks.pagination = PageLinksGenerator(
PAGE_LINK_NUMBER, page_number, paginator.num_pages)
2020-05-05 23:50:48 +08:00
for m in marks:
m.get_status_display = BookMarkStatusTranslator(m.status)
return render(
request,
'books/mark_list.html',
{
'marks': marks,
'book': book,
}
)
else:
return HttpResponseBadRequest()
@login_required
def delete_mark(request, id):
if request.method == 'POST':
mark = get_object_or_404(BookMark, pk=id)
2020-10-26 00:17:26 +01:00
if request.user != mark.owner:
return HttpResponseBadRequest()
2020-05-05 23:50:48 +08:00
book_id = mark.book.id
try:
with transaction.atomic():
# update book rating
mark.book.update_rating(mark.rating, None)
mark.delete()
except IntegrityError as e:
return HttpResponseServerError()
return redirect(reverse("books:retrieve", args=[book_id]))
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def create_review(request, book_id):
if request.method == 'GET':
form = BookReviewForm(initial={'book': book_id})
book = get_object_or_404(Book, pk=book_id)
return render(
request,
'books/create_update_review.html',
{
'form': form,
'title': _("添加评论"),
'book': book,
'submit_url': reverse("books:create_review", args=[book_id]),
}
)
elif request.method == 'POST':
form = BookReviewForm(request.POST)
if form.is_valid():
form.instance.owner = request.user
form.save()
if form.cleaned_data['share_to_mastodon']:
2021-12-20 22:59:32 -05:00
if form.cleaned_data['visibility'] == 2:
visibility = TootVisibilityEnum.DIRECT
elif form.cleaned_data['visibility'] == 1:
2020-05-05 23:50:48 +08:00
visibility = TootVisibilityEnum.PRIVATE
else:
visibility = TootVisibilityEnum.PUBLIC if request.user.preference.mastodon_publish_public else TootVisibilityEnum.UNLISTED
2020-07-10 21:28:09 +08:00
url = "https://" + request.get_host() + reverse("books:retrieve_review",
args=[form.instance.id])
2020-05-05 23:50:48 +08:00
words = "发布了关于" + f"{form.instance.book.title}" + "的评论"
# tags = settings.MASTODON_TAGS % {'category': '书', 'type': '评论'}
2020-08-28 22:22:07 +08:00
tags = ''
2020-07-10 21:28:09 +08:00
content = words + '\n' + url + \
2020-07-29 23:34:06 +08:00
'\n' + form.cleaned_data['title'] + '\n' + tags
2020-10-22 23:27:16 +02:00
response = post_toot(
request.user.mastodon_site, content, visibility, request.session['oauth_token'])
if response.status_code != 200:
2020-08-28 22:22:07 +08:00
mastodon_logger.error(
f"CODE:{response.status_code} {response.text}")
return HttpResponseServerError("publishing mastodon status failed")
2020-05-05 23:50:48 +08:00
return redirect(reverse("books:retrieve_review", args=[form.instance.id]))
else:
return HttpResponseBadRequest()
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def update_review(request, id):
if request.method == 'GET':
review = get_object_or_404(BookReview, pk=id)
if request.user != review.owner:
2020-07-10 21:28:09 +08:00
return HttpResponseBadRequest()
2020-05-05 23:50:48 +08:00
form = BookReviewForm(instance=review)
book = review.book
return render(
request,
'books/create_update_review.html',
{
'form': form,
'title': _("编辑评论"),
'book': book,
'submit_url': reverse("books:update_review", args=[review.id]),
}
)
elif request.method == 'POST':
review = get_object_or_404(BookReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
form = BookReviewForm(request.POST, instance=review)
if form.is_valid():
form.instance.edited_time = timezone.now()
form.save()
if form.cleaned_data['share_to_mastodon']:
2021-12-20 22:59:32 -05:00
if form.cleaned_data['visibility'] == 2:
visibility = TootVisibilityEnum.DIRECT
elif form.cleaned_data['visibility'] == 1:
2020-05-05 23:50:48 +08:00
visibility = TootVisibilityEnum.PRIVATE
else:
visibility = TootVisibilityEnum.PUBLIC if request.user.preference.mastodon_publish_public else TootVisibilityEnum.UNLISTED
2020-07-10 21:28:09 +08:00
url = "https://" + request.get_host() + reverse("books:retrieve_review",
args=[form.instance.id])
2020-05-05 23:50:48 +08:00
words = "发布了关于" + f"{form.instance.book.title}" + "的评论"
# tags = settings.MASTODON_TAGS % {'category': '书', 'type': '评论'}
2020-08-28 22:22:07 +08:00
tags = ''
2020-07-10 21:28:09 +08:00
content = words + '\n' + url + \
2020-07-29 23:34:06 +08:00
'\n' + form.cleaned_data['title'] + '\n' + tags
2020-10-22 23:27:16 +02:00
response = post_toot(
request.user.mastodon_site, content, visibility, request.session['oauth_token'])
if response.status_code != 200:
2020-08-28 22:22:07 +08:00
mastodon_logger.error(f"CODE:{response.status_code} {response.text}")
return HttpResponseServerError("publishing mastodon status failed")
2020-05-05 23:50:48 +08:00
return redirect(reverse("books:retrieve_review", args=[form.instance.id]))
else:
return HttpResponseBadRequest()
else:
2020-07-10 21:28:09 +08:00
return HttpResponseBadRequest()
2020-05-05 23:50:48 +08:00
@login_required
def delete_review(request, id):
if request.method == 'GET':
review = get_object_or_404(BookReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
review_form = BookReviewForm(instance=review)
return render(
request,
'books/delete_review.html',
{
'form': review_form,
'review': review,
}
)
elif request.method == 'POST':
review = get_object_or_404(BookReview, pk=id)
if request.user != review.owner:
return HttpResponseBadRequest()
book_id = review.book.id
review.delete()
return redirect(reverse("books:retrieve", args=[book_id]))
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def retrieve_review(request, id):
if request.method == 'GET':
review = get_object_or_404(BookReview, pk=id)
2021-12-20 22:59:32 -05:00
if not review.is_visible_to(request.user):
msg = _("你没有访问这个页面的权限😥")
return render(
request,
'common/error.html',
{
'msg': msg,
}
)
2020-05-05 23:50:48 +08:00
review_form = BookReviewForm(instance=review)
book = review.book
try:
mark = BookMark.objects.get(owner=review.owner, book=book)
mark.get_status_display = BookMarkStatusTranslator(mark.status)
except ObjectDoesNotExist:
mark = None
return render(
request,
'books/review_detail.html',
{
'form': review_form,
'review': review,
'book': book,
'mark': mark,
}
)
else:
return HttpResponseBadRequest()
@mastodon_request_included
@login_required
def retrieve_review_list(request, book_id):
if request.method == 'GET':
book = get_object_or_404(Book, pk=book_id)
2021-12-15 21:54:24 -05:00
queryset = BookReview.get_available(book, request.user)
2020-05-05 23:50:48 +08:00
paginator = Paginator(queryset, REVIEW_PER_PAGE)
page_number = request.GET.get('page', default=1)
reviews = paginator.get_page(page_number)
2020-07-10 21:28:09 +08:00
reviews.pagination = PageLinksGenerator(
PAGE_LINK_NUMBER, page_number, paginator.num_pages)
2020-05-05 23:50:48 +08:00
return render(
request,
'books/review_list.html',
{
'reviews': reviews,
'book': book,
}
)
else:
return HttpResponseBadRequest()
@login_required
def scrape(request):
if request.method == 'GET':
2020-05-07 19:30:16 +08:00
keywords = request.GET.get('q')
2020-05-05 23:50:48 +08:00
form = BookForm()
return render(
request,
'books/scrape.html',
{
2020-05-07 19:30:16 +08:00
'q': keywords,
2020-05-05 23:50:48 +08:00
'form': form,
2020-05-01 22:46:15 +08:00
}
)
2020-05-12 14:05:12 +08:00
else:
return HttpResponseBadRequest()
@login_required
def click_to_scrape(request):
if request.method == "POST":
url = request.POST.get("url")
if url:
2020-12-09 20:09:40 +01:00
return jump_or_scrape(request, url)
2020-05-12 14:05:12 +08:00
else:
return HttpResponseBadRequest()
2020-05-01 22:46:15 +08:00
else:
2020-07-10 21:28:09 +08:00
return HttpResponseBadRequest()