new data model: mark and review pages

This commit is contained in:
Your Name 2022-12-25 13:45:24 -05:00
parent 86b13bf08f
commit de2fde302a
16 changed files with 402 additions and 197 deletions

45
catalog/static/catalog.js Normal file
View file

@ -0,0 +1,45 @@
function catalog_init(context) {
// readonly star rating of detail display section
let ratingLabels = $("#main .rating-star", context);
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
});
});
// readonly star rating at aside section
ratingLabels = $("#aside .rating-star"), context;
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
starSize: 15,
});
});
// hide long text
$(".entity-desc__content", context).each(function() {
let copy = $(this).clone()
.addClass('entity-desc__content--folded')
.css("visibility", "hidden");
$(this).after(copy);
if ($(this).height() > copy.height()) {
$(this).addClass('entity-desc__content--folded');
$(this).siblings(".entity-desc__unfold-button").removeClass("entity-desc__unfold-button--hidden");
}
copy.remove();
});
// expand hidden long text
$(".entity-desc__unfold-button a", context).on('click', function() {
$(this).parent().siblings(".entity-desc__content").removeClass('entity-desc__content--folded');
$(this).parent(".entity-desc__unfold-button").remove();
});
}
$(function() {
document.body.addEventListener('htmx:load', function(evt) {
catalog_init(evt.detail.elt);
});
});

View file

@ -0,0 +1,25 @@
{% load static %}
{% if sentry_dsn %}
<script src="https://browser.sentry-cdn.com/7.7.0/bundle.min.js"></script>
<script>
if (window.Sentry) Sentry.init({
dsn: "{{ sentry_dsn }}",
release: "{{ version_hash }}",
environment: "{{ settings_module }}",
tracesSampleRate: 1.0,
});
</script>
{% endif %}
{% if jquery %}
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
{% else %}
<script src="https://cdn.staticfile.org/cash/8.1.1/cash.min.js"></script>
{% endif %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.8.4/htmx.min.js"></script>
<script src="{% static 'lib/js/hyperscript-0.9.7.min.js' %}"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/boofilsic.css' %}">
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'lib/css/collection.css' %}">
<link rel="search"type="application/opensearchdescription+xml" title="{{ site_name }}" href="{% static 'opensearch.xml' %}">
<script src="{% static 'catalog.js' %}"></script>

View file

@ -10,12 +10,21 @@
{% load strip_scheme %}
{% load thumb %}
{% block opengraph %}
{% if item.author %}
<meta property="og:book:author" content="{% for author in item.author %}{{ author }}{% if not forloop.last %},{% endif %}{% endfor %}">
{% endif %}
{% if item.isbn %}
<meta property="og:book:isbn" content="{{ item.isbn }}">
{% endif %}
{% endblock %}
{% block details %}
<div class="entity-detail__fields">
<div class="entity-detail__rating">
{% if item.rating %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:"0" }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating }} </span>
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:0 }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating | floatformat:1 }} </span>
<small>({{ item.rating_count }}人评分)</small>
{% else %}
<span> {% trans '评分:评分人数不足' %}</span>

View file

@ -16,23 +16,15 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:title" content="{{ site_name }}{% trans item.category.label %} - {{ item.title }}">
<meta property="og:type" content="book">
<meta property="og:type" content="{{ item.category }}">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ item.cover.url }}">
<meta property="og:site_name" content="{{ site_name }}">
<meta property="og:description" content="{{ item.brief }}">
{% if item.author %}
<meta property="og:book:author" content="{% for author in item.author %}{{ author }}{% if not forloop.last %},{% endif %}{% endfor %}">
{% endif %}
{% if item.isbn %}
<meta property="og:book:isbn" content="{{ item.isbn }}">
{% endif %}
{% block opengraph %}
{% endblock %}
<title>{{ site_name }} - {% trans item.category.label %} | {{ item.title }}</title>
{% include "partial/_common_libs.html" with jquery=1 %}
<script src="{% static 'lib/js/rating-star.js' %}"></script>
{% include "common_libs.html" with jquery=1 %}
<script src="{% static 'lib/js/tag-input.js' %}"></script>
<link href="{% static 'lib/css/tag-input.css' %}" type="text/css" media="all" rel="stylesheet">
</head>
@ -204,9 +196,10 @@
{% endif %}
<span class="mark-panel__actions">
<a href="#" hx-get="{% url 'journal:mark' item.uuid %}" class="edit" hx-target="body" hx-swap="beforeend">{% trans '修改' %}</a>
<form action="{% url 'books:delete_mark' mark.id %}" method="post">
<form id="mark_delete" action="{% url 'journal:mark' item.uuid %}" method="post">
{% csrf_token %}
<a href="" class="delete">{% trans '删除' %}</a>
<input type="hidden" name="delete" value="1">
<a href="javascript:$('#mark_delete').submit()" class="delete">{% trans '删除' %}</a>
</form>
</span>
<div class="mark-panel__clear"></div>
@ -234,26 +227,24 @@
<div class="action-panel" id="addMarkPanel">
<div class="action-panel__label">{% trans '标记' %}{% trans item.demonstrative %}</div>
<div class="action-panel__button-group">
<button class="action-panel__button" data-status="{{ status_enum.WISH.value }}" id="wishButton">{% trans '想读' %}</button>
<button class="action-panel__button" data-status="{{ status_enum.DO.value }}">{% trans '在读' %}</button>
<button class="action-panel__button" data-status="{{ status_enum.COLLECT.value }}">{% trans '读过' %}</button>
{% for k, v in shelf_types %}
<button class="action-panel__button" data-status="{{ k }}" hx-get="{% url 'journal:mark' item.uuid %}?shelf_type={{ k }}" class="edit" hx-target="body" hx-swap="beforeend">{% trans v %}</button>
{% endfor %}
</div>
</div>
{% endif %}
</div>
<div class="aside-section-wrapper">
{% if review %}
<div class="review-panel">
<div class="review-panel action-panel">
{% if review %}
<span class="review-panel__label">{% trans '我的评论' %}</span>
{% if review.visibility > 0 %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="review-panel__actions">
<a href="{% url 'books:update_review' review.id %}">{% trans '编辑' %}</a>
<a href="{% url 'books:delete_review' review.id %}">{% trans '删除' %}</a>
<a href="{% url 'journal:review_edit' item.uuid review.uuid %}">{% trans '编辑' %}</a>
<a href="{% url 'journal:review_delete' review.uuid %}">{% trans '删除' %}</a>
</span>
<div class="review-panel__time">{{ review.edited_time }}</div>
@ -261,20 +252,14 @@
<a href="{% url 'journal:review_retrieve' review.uuid %}" class="review-panel__review-title">
{{ review.title }}
</a>
</div>
{% else %}
<div class="action-panel">
<div class="action-panel__label">{% trans '我的评论' %}</div>
{% else %}
<div class="action-panel__button-group action-panel__button-group--center">
<a href="{% url 'books:create_review' item.id %}">
<button class="action-panel__button">{% trans '去写评论' %}</button>
<a href="{% url 'journal:review_create' item.uuid %}">
<button class="action-panel__button">{% trans '撰写评论' %}</button>
</a>
</div>
{% endif %}
</div>
{% endif %}
</div>
{% endblock %}
@ -308,44 +293,5 @@
</div>
{% include "partial/_footer.html" %}
</div>
<script>
// readonly star rating of detail display section
let ratingLabels = $("#main .rating-star");
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
});
});
// readonly star rating at aside section
ratingLabels = $("#aside .rating-star");
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
starSize: 15,
});
});
// hide long text
$(".entity-desc__content").each(function() {
let copy = $(this).clone()
.addClass('entity-desc__content--folded')
.css("visibility", "hidden");
$(this).after(copy);
if ($(this).height() > copy.height()) {
$(this).addClass('entity-desc__content--folded');
$(this).siblings(".entity-desc__unfold-button").removeClass("entity-desc__unfold-button--hidden");
}
copy.remove();
});
// expand hidden long text
$(".entity-desc__unfold-button a").on('click', function() {
$(this).parent().siblings(".entity-desc__content").removeClass('entity-desc__content--folded');
$(this).parent(".entity-desc__unfold-button").remove();
});
</script>
</body>
</html>

View file

@ -13,11 +13,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }} - {{ item.title }}{% trans '的标记' %}</title>
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
{% include "common_libs.html" with jquery=1 %}
</head>
<body>
@ -105,47 +101,5 @@
</div>
{% include "partial/_footer.html" %}
</div>
<script>
// readonly star rating of detail display section
let ratingLabels = $("#main .rating-star");
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
});
});
// readonly star rating at aside section
ratingLabels = $("#aside .rating-star");
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
starSize: 15,
});
});
// hide long text
$(".entity-desc__content").each(function() {
let copy = $(this).clone()
.addClass('entity-desc__content--folded')
.css("visibility", "hidden");
$(this).after(copy);
if ($(this).height() > copy.height()) {
$(this).addClass('entity-desc__content--folded');
$(this).siblings(".entity-desc__unfold-button").removeClass("entity-desc__unfold-button--hidden");
}
copy.remove();
});
// expand hidden long text
$(".entity-desc__unfold-button a").on('click', function() {
$(this).parent().siblings(".entity-desc__content").removeClass('entity-desc__content--folded');
$(this).parent(".entity-desc__unfold-button").remove();
});
</script>
</body>
</html>

View file

@ -13,11 +13,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }} - {{ item.title }}{% trans '的评论' %}</title>
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
{% include "common_libs.html" with jquery=1 %}
</head>
<body>
@ -30,22 +26,18 @@
<div class="main-section-wrapper">
<div class="entity-reviews">
<h5 class="entity-reviews__title entity-reviews__title--stand-alone">
<a href="{% url 'books:retrieve' item.id %}">{{ item.title }}</a>{% trans ' 的评论' %}
<a href="{{ item.url }}">{{ item.title }}</a>{% trans ' 的评论' %}
</h5>
<ul class="entity-reviews__review-list">
{% for review in reviews %}
<li class="entity-reviews__review entity-reviews__review--wider">
<a href="{% url 'users:home' review.owner.mastodon_username %}" class="entity-reviews__owner-link">{{ review.owner.username }}</a>
{% if review.visibility > 0 %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
{% endif %}
<span class="entity-reviews__review-time">{{ review.edited_time }}</span>
<span href="{% url 'books:retrieve_review' review.id %}" class="entity-reviews__review-title"><a href="{% url 'books:retrieve_review' review.id %}">{{ review.title }}</a></span>
<span class="entity-reviews__review-title"><a href="{% url 'journal:review_retrieve' review.uuid %}">{{ review.title }}</a></span>
</li>
{% empty %}
<div>{% trans '无结果' %}</div>

View file

@ -19,6 +19,7 @@ from journal.models import Mark, ShelfMember, Review
from journal.models import query_visible, query_following
from common.utils import PageLinksGenerator
from common.views import PAGE_LINK_NUMBER
from journal.models import ShelfTypeNames
_logger = logging.getLogger(__name__)
@ -43,6 +44,7 @@ def retrieve(request, item_path, item_uuid):
mark_list = None
review_list = None
collection_list = []
shelf_types = [(n[1], n[2]) for n in iter(ShelfTypeNames) if n[0] == item.category]
if request.user.is_authenticated:
visible = query_visible(request.user)
mark = Mark(request.user, item)
@ -60,6 +62,7 @@ def retrieve(request, item_path, item_uuid):
'mark_list': mark_list,
'review_list': review_list,
'collection_list': collection_list,
'shelf_types': shelf_types,
}
)
else:

35
journal/forms.py Normal file
View file

@ -0,0 +1,35 @@
from django import forms
from markdownx.fields import MarkdownxFormField
import django.contrib.postgres.forms as postgres
from django.utils import formats
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
import json
from .models import *
class ReviewForm(forms.ModelForm):
class Meta:
model = Review
fields = [
'id',
'item',
'title',
'body',
'visibility'
]
widgets = {
'item': forms.TextInput(attrs={"hidden": ""}),
}
title = forms.CharField(label=_("评论标题"))
body = MarkdownxFormField(label=_("评论正文 (Markdown)"))
share_to_mastodon = forms.BooleanField(
label=_("分享到联邦网络"), initial=True, required=False)
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
visibility = forms.TypedChoiceField(
label=_("可见性"),
initial=0,
coerce=int,
choices=VisibilityType.choices,
widget=forms.RadioSelect
)

View file

@ -23,6 +23,12 @@ from django.db.models import Q
import mistune
class VisibilityType(models.IntegerChoices):
Public = 0, _('公开')
Follower_Only = 1, _('仅关注者')
Private = 2, _('仅自己')
def query_visible(user):
return Q(visibility=0) | Q(owner_id__in=user.following, visibility__lt=2) | Q(owner_id=user.id)
@ -47,7 +53,7 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
@property
def url(self):
return f'/{self.url_path}/{self.uuid}/' if self.url_path else None
return f'/{self.url_path}/{self.uuid}' if self.url_path else None
@property
def absolute_url(self):
@ -62,7 +68,7 @@ class Content(Piece):
item = models.ForeignKey(Item, on_delete=models.PROTECT)
def __str__(self):
return f"{self.id}({self.item})"
return f"{self.uuid}@{self.item}"
class Meta:
abstract = True
@ -644,3 +650,6 @@ class Mark:
if self.shelfmember.metadata.get('shared_link') != self.shared_link:
self.shelfmember.metadata['shared_link'] = self.shared_link
self.shelfmember.save()
def delete(self):
self.update(None, None, None, 0)

View file

@ -31,17 +31,17 @@
<div id="statusSelection" class="mark-modal__status-radio float-right">
<ul id="id_status">
{% for k, v in shelf_types %}
<li><label for="id_status_{{ k }}"><input type="radio" name="status" value="{{ k }}" required="" id="id_status_{{ k }}" {% if mark.shelf_type == k %}checked=""{% endif %}> {{ v }}</label></li>
<li><label for="id_status_{{ k }}"><input type="radio" name="status" value="{{ k }}" required="" id="id_status_{{ k }}" {% if shelf_type == k %}checked=""{% endif %}> {{ v }}</label></li>
{% endfor %}
</ul>
</div>
<div class="mark-modal__rating-star rating-star-edit"></div>
<input type="hidden" name="rating" id="id_rating" value="{{ mark.rating }}">
<input type="hidden" name="rating" id="id_rating" value="{{ mark.rating|floatformat:0 }}">
<div class="mark-modal__clear"></div>
<textarea name="text" cols="40" rows="10" placeholder="超过360字部分实例可能无法显示" maxlength="360" id="id_text">{{ mark.text }}</textarea>
<textarea name="text" cols="40" rows="10" placeholder="超过360字部分实例可能无法显示" maxlength="360" id="id_text">{% if mark.text %}{{ mark.text }}{% endif %}</textarea>
<div class="mark-modal__tag">
<label>标签</label>
@ -100,19 +100,19 @@
// hide rating star when select wish
const WISH_CODE = "wishlist";
if ($("#statusSelection input[type='radio']:checked").val() == WISH_CODE) {
$("#model .rating-star-edit").hide();
$("#modal .rating-star-edit").hide();
}
$("#statusSelection input[type='radio']").on('click', function() {
if ($(this).val() == WISH_CODE) {
$("#model .rating-star-edit").hide();
$("#modal .rating-star-edit").hide();
} else {
$("#model .rating-star-edit").show();
$("#modal .rating-star-edit").show();
}
});
// show confirm modal
$("#model a.delete").on('click', function(e) {
$("#modal a.delete").on('click', function(e) {
e.preventDefault();
$(".confirm-modal").show();
$(".bg-mask").show();
@ -121,7 +121,7 @@
// confirm modal
$(".confirm-modal input[type='submit']").on('click', function(e) {
e.preventDefault();
$("#model form").submit();
$("#modal form").submit();
});
})();
</script>

View file

@ -11,18 +11,13 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta property="og:title" content="{{ site_name }}书评 - {{ review.title }}">
<meta property="og:title" content="{{ site_name }} - {{ review.title }} ({{ review.item.title }} {% trans '评论' %})">
<meta property="og:type" content="article">
<meta property="og:article:author" content="{{ review.owner.username }}">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{{ review.item.cover|thumb:'normal' }}">
<title>{{ site_name }}{% trans '评论' %} - {{ review.title }}</title>
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
<link rel="stylesheet" href="{% static 'lib/css/collection.css' %}">
{% include "common_libs.html" with jquery=1 %}
</head>
<body>
@ -62,8 +57,8 @@
</div>
<div class="review-head__actions">
{% if request.user == review.owner %}
<a class="review-head__action-link" href="{% url 'journal:update_review' review.id %}">{% trans '编辑' %}</a>
<a class="review-head__action-link" href="{% url 'journal:delete_review' review.id %}">{% trans '删除' %}</a>
<a class="review-head__action-link" href="{% url 'journal:review_edit' review.item.uuid review.uuid %}">{% trans '编辑' %}</a>
<a class="review-head__action-link" href="{% url 'journal:review_delete' review.uuid %}">{% trans '删除' %}</a>
{% endif %}
</div>
</div>

View file

@ -0,0 +1,93 @@
{% load static %}
{% load i18n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }} - {% trans '删除评论' %}</title>
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="single-section-wrapper" id="main">
<h5>{% trans '确认删除这篇评论吗?' %}</h5>
<div class="dividing-line"></div>
<div class="review-head">
<h5 class="review-head__title">
{{ review.title }}
</h5>
{% if review.visibility > 0 %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"><svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20">
<path
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
</svg></span>
{% endif %}
<div class="review-head__body">
<div class="review-head__info">
<a href="{% url 'users:home' review.owner.mastodon_username %}"
class="review-head__owner-link">{{ review.owner.username }}</a>
<span class="review-head__time">{{ review.edited_time }}</span>
</div>
</div>
</div>
<div id="rawContent" class="delete-preview">
{{ form.body }}
</div>
{{ form.media }}
<div class="dividing-line"></div>
<div class="clearfix">
<form action="{% url 'journal:review_delete' review.uuid %}" method="post" class="float-right">
{% csrf_token %}
<input class="button" type="submit" value="{% trans '确认' %}">
</form>
<button onclick="history.back()"
class="button button-clear float-right">{% trans '返回' %}</button>
</div>
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
<script>
$(".markdownx textarea").hide();
$(".markdownx .markdownx-preview").show();
</script>
</body>
</html>

View file

@ -0,0 +1,73 @@
{% load static %}
{% load i18n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }} - {{ item.title }} - {% trans '评论' %}</title>
{% include "common_libs.html" with jquery=1 %}
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="grid__main" id="main">
<div class="single-section-wrapper">
<form action="{{ submit_url }}" method="post" class="review-form">
{% csrf_token %}
{{ form.item }}
<div>
{{ form.title.label }}
</div>
{{ form.title }}
<div class="clearfix">
<span class="float-left">
{{ form.body.label }}
</span>
<span class="float-right">
<span class="review-form__preview-button">{% trans '预览' %}</span>
</span>
</div>
<div id="rawContent">
{{ form.body }}
</div>
<div class="review-form__fyi">{% trans '不知道什么是Markdown可以参考' %}<a target="_blank" href="https://www.markdownguide.org/">{% trans '这里' %}</a></div>
<div class="review-form__option">
<div class="review-form__visibility-radio">
{{ form.visibility.label }}{{ form.visibility }}
</div>
<div class="review-form__share-checkbox">
{{ form.share_to_mastodon }}{{ form.share_to_mastodon.label }}
</div>
</div>
<div class="clearfix">
<input class="button float-right" type="submit" value="{% trans '提交' %}">
</div>
{{ form.media }}
</form>
</div>
</div>
<div class="grid__aside" id="aside">
{% include "sidebar_item.html" %}
</div>
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
</body>
</html>

View file

@ -9,6 +9,8 @@ urlpatterns = [
path('mark/<str:item_uuid>', mark, name='mark'),
path('add_to_collection/<str:item_uuid>', add_to_collection, name='add_to_collection'),
path('review/<str:piece_uuid>', review_retrieve, name='review_retrieve'),
path('review/create', review_create, name='review_create'),
path('review/<str:review_uuid>', review_retrieve, name='review_retrieve'),
path('review/create/<str:item_uuid>/', review_edit, name='review_create'),
path('review/edit/<str:item_uuid>/<str:review_uuid>', review_edit, name='review_edit'),
path('review/delete/<str:review_uuid>', review_delete, name='review_delete'),
]

View file

@ -17,6 +17,9 @@ from django.db.models import Q
import time
from management.models import Announcement
from django.utils.baseconv import base62
from .forms import *
from mastodon.api import share_review
_logger = logging.getLogger(__name__)
PAGE_SIZE = 10
@ -82,43 +85,85 @@ def mark(request, item_uuid):
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, 'mark.html', {
'item': item,
'mark': mark,
'shelf_type': shelf_type,
'tags': ','.join(tags),
'shelf_types': shelf_types,
})
elif request.method == 'POST':
visibility = int(request.POST.get('visibility', default=0))
rating = int(request.POST.get('rating', default=0))
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))
TagManager.tag_item_by_user(item, request.user, tags, visibility)
try:
mark.update(status, text, rating, visibility, share_to_mastodon=share_to_mastodon)
except Exception:
go_relogin(request)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
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 = request.POST.get('rating', default=0)
rating = int(rating) if rating 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))
TagManager.tag_item_by_user(item, request.user, tags, visibility)
try:
mark.update(status, text, rating, visibility, share_to_mastodon=share_to_mastodon)
except Exception:
go_relogin(request)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
def review_retrieve(request, piece_uuid):
piece = get_object_or_404(Review, uid=base62.decode(piece_uuid))
if not piece:
return HttpResponseNotFound("piece not found")
def review_retrieve(request, review_uuid):
piece = get_object_or_404(Review, uid=base62.decode(review_uuid))
if not piece.is_visible_to(request.user):
raise PermissionDenied()
return render(request, 'review.html', {'review': piece})
def review_edit(request, piece_uuid):
pass
@login_required
def review_edit(request, item_uuid, review_uuid=None):
item = get_object_or_404(Item, uid=base62.decode(item_uuid))
review = get_object_or_404(Review, uid=base62.decode(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})
return render(request, 'review_edit.html', {'form': form, 'item': item})
elif request.method == 'POST':
form = ReviewForm(request.POST, instance=review) if review else ReviewForm(request.POST)
if form.is_valid():
if not review:
form.instance.owner = request.user
form.instance.edited_time = timezone.now()
form.save()
if form.cleaned_data['share_to_mastodon']:
form.instance.save = lambda **args: None
form.instance.shared_link = None
if not share_review(form.instance):
return go_relogin(request)
return redirect(reverse("journal:review_retrieve", args=[form.instance.uuid]))
else:
return HttpResponseBadRequest(form.errors)
else:
return HttpResponseBadRequest()
def review_create(request):
pass
@login_required
def review_delete(request, review_uuid):
review = get_object_or_404(Review, uid=base62.decode(review_uuid))
if not review.is_editable_by(request.user):
raise PermissionDenied()
if request.method == 'GET':
review_form = ReviewForm(instance=review)
return render(request, 'review_delete.html', {'form': review_form, 'review': review})
elif request.method == 'POST':
item = review.item
print(review)
review.delete()
return redirect(item.url)
else:
return HttpResponseBadRequest()
def mark_list(request, shelf_type, item_category):
@ -133,5 +178,6 @@ def collection_list(request):
pass
@login_required
def liked_list(request):
pass

View file

@ -13,29 +13,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ site_name }}</title>
{% include "partial/_common_libs.html" with jquery=1 %}
<script src="{% static 'lib/js/rating-star.js' %}"></script>
<script>
$(document).ready( function() {
let render = function() {
let ratingLabels = $(".rating-star");
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({
initialRating: ratingScore,
readOnly: true,
starSize: 16,
});
});
};
document.body.addEventListener('htmx:load', function(evt) {
render();
});
render();
});
</script>
{% include "common_libs.html" with jquery=1 %}
<script src="{% static 'js/mastodon.js' %}"></script>
<script src="{% static 'js/home.js' %}"></script>
</head>