click corner action icon to add to wishlist
This commit is contained in:
parent
99724bbe0d
commit
44cd271127
23 changed files with 537 additions and 23 deletions
|
@ -111,6 +111,10 @@ class Book(Entity):
|
|||
def verbose_category_name(self):
|
||||
return _("书籍")
|
||||
|
||||
@property
|
||||
def mark_class(self):
|
||||
return BookMark
|
||||
|
||||
|
||||
class BookMark(Mark):
|
||||
book = models.ForeignKey(
|
||||
|
|
|
@ -9,6 +9,7 @@ urlpatterns = [
|
|||
path('update/<int:id>/', update, name='update'),
|
||||
path('delete/<int:id>/', delete, name='delete'),
|
||||
path('mark/', create_update_mark, name='create_update_mark'),
|
||||
path('wish/<int:id>/', wish, name='wish'),
|
||||
re_path('(?P<book_id>[0-9]+)/mark/list/(?:(?P<following_only>\\d+))?', retrieve_mark_list, name='retrieve_mark_list'),
|
||||
path('mark/delete/<int:id>/', delete_mark, name='delete_mark'),
|
||||
path('<int:book_id>/review/create/', create_review, name='create_review'),
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
from django.shortcuts import render, get_object_or_404, redirect, reverse
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError, HttpResponse
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Count
|
||||
|
@ -335,6 +335,26 @@ def create_update_mark(request):
|
|||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def wish(request, id):
|
||||
if request.method == 'POST':
|
||||
book = get_object_or_404(Book, pk=id)
|
||||
params = {
|
||||
'owner': request.user,
|
||||
'status': MarkStatusEnum.WISH,
|
||||
'visibility': 0,
|
||||
'book': book,
|
||||
}
|
||||
try:
|
||||
BookMark.objects.create(**params)
|
||||
except Exception:
|
||||
pass
|
||||
return HttpResponse("✔️")
|
||||
else:
|
||||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_mark_list(request, book_id, following_only=False):
|
||||
|
|
|
@ -170,6 +170,10 @@ class Entity(models.Model):
|
|||
def verbose_category_name(self):
|
||||
raise NotImplementedError("Subclass should implement this.")
|
||||
|
||||
@property
|
||||
def mark_class(self):
|
||||
raise NotImplementedError("Subclass should implement this.")
|
||||
|
||||
|
||||
class UserOwnedEntity(models.Model):
|
||||
is_private = models.BooleanField(default=False, null=True) # first set allow null, then migration, finally (in a few days) remove for good
|
||||
|
@ -187,7 +191,7 @@ class UserOwnedEntity(models.Model):
|
|||
owner = self.owner
|
||||
if owner == viewer:
|
||||
return True
|
||||
if owner.is_active == False:
|
||||
if not owner.is_active:
|
||||
return False
|
||||
if self.visibility == 2:
|
||||
return False
|
||||
|
@ -233,6 +237,11 @@ class UserOwnedEntity(models.Model):
|
|||
user_owned_entities = user_owned_entities.filter(visibility=0)
|
||||
return user_owned_entities
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
attr = re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', self.__class__.__name__)[0].lower()
|
||||
return getattr(self, attr)
|
||||
|
||||
|
||||
# commonly used entity classes
|
||||
###################################
|
||||
|
@ -258,7 +267,7 @@ class Mark(UserOwnedEntity):
|
|||
models.CheckConstraint(check=models.Q(
|
||||
rating__lte=10), name='mark_rating_upperbound'),
|
||||
]
|
||||
|
||||
|
||||
# TODO update entity rating when save
|
||||
# TODO update tags
|
||||
|
||||
|
|
|
@ -14,6 +14,23 @@
|
|||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.entity-list__entity-img-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entity-list__entity-action-icon {
|
||||
position: absolute;
|
||||
top:0;
|
||||
right:0;
|
||||
mix-blend-mode: hard-light;
|
||||
text-stroke: 1px black;
|
||||
background-color: lightgray;
|
||||
border-radius: 0 0 0 8px;
|
||||
padding: 0 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/***** MODAL DIALOG ****/
|
||||
#modal {
|
||||
/* Underlay covers entire screen. */
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<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' %}">
|
||||
<link rel="stylesheet" href="{% static 'lib/css/neo.css' %}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -222,7 +223,9 @@
|
|||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -2,11 +2,16 @@
|
|||
{% load highlight %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load neo %}
|
||||
{% current_user_marked_item book as marked %}
|
||||
<li class="entity-list__entity">
|
||||
<div class="entity-list__entity-img-wrapper">
|
||||
<a href="{% url 'books:retrieve' book.id %}">
|
||||
<img src="{{ book.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
{% if not marked %}
|
||||
<a class="entity-list__entity-action-icon" hx-post="{% url 'books:wish' book.id %}" title="加入想读">➕</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="entity-list__entity-text">
|
||||
|
@ -30,9 +35,9 @@
|
|||
{% else %}
|
||||
{{ book.title }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</a>
|
||||
{% if not request.GET.c or not request.GET.c in categories %}
|
||||
{% if not request.GET.c and not hide_category %}
|
||||
<span class="entity-list__entity-category">[{{book.verbose_category_name}}]</span>
|
||||
{% endif %}
|
||||
<a href="{{ book.source_url }}">
|
||||
|
@ -104,7 +109,39 @@
|
|||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if mark %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
<div class="entity-marks" style="margin-bottom: 0;">
|
||||
<ul class="entity-marks__mark-list">
|
||||
<li class="entity-marks__mark">
|
||||
{% if mark.rating %}
|
||||
<span class="entity-marks__rating-star rating-star"
|
||||
data-rating-score="{{ mark.rating | floatformat:"0" }}" style="left: -4px;"></span>
|
||||
{% endif %}
|
||||
{% if mark.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-marks__mark-time">
|
||||
{% trans '于' %} {{ mark.edited_time }}
|
||||
{% if status == 'reviewed' %}
|
||||
{% trans '评论' %}: <a href="{% url 'books:retrieve_review' mark.id %}">{{ mark.title }}</a>
|
||||
{% else %}
|
||||
{% trans '标记' %}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if mark.text %}
|
||||
<p class="entity-marks__mark-content">{{ mark.text }}</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collectionitem %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
|
|
|
@ -2,11 +2,16 @@
|
|||
{% load highlight %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load neo %}
|
||||
{% current_user_marked_item game as marked %}
|
||||
<li class="entity-list__entity">
|
||||
<div class="entity-list__entity-img-wrapper">
|
||||
<a href="{% url 'games:retrieve' game.id %}">
|
||||
<img src="{{ game.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
{% if not marked %}
|
||||
<a class="entity-list__entity-action-icon" hx-post="{% url 'games:wish' game.id %}" title="加入想玩">➕</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="entity-list__entity-text">
|
||||
{% if editable %}
|
||||
|
@ -30,7 +35,7 @@
|
|||
{% endif %}
|
||||
</a>
|
||||
|
||||
{% if not request.GET.c or not request.GET.c in categories %}
|
||||
{% if not request.GET.c and not hide_category %}
|
||||
<span class="entity-list__entity-category">[{{item.verbose_category_name}}]</span>
|
||||
{% endif %}
|
||||
<a href="{{ game.source_url }}">
|
||||
|
@ -83,7 +88,39 @@
|
|||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if mark %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
<div class="entity-marks" style="margin-bottom: 0;">
|
||||
<ul class="entity-marks__mark-list">
|
||||
<li class="entity-marks__mark">
|
||||
{% if mark.rating %}
|
||||
<span class="entity-marks__rating-star rating-star"
|
||||
data-rating-score="{{ mark.rating | floatformat:"0" }}" style="left: -4px;"></span>
|
||||
{% endif %}
|
||||
{% if mark.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-marks__mark-time">
|
||||
{% trans '于' %} {{ mark.edited_time }}
|
||||
{% if status == 'reviewed' %}
|
||||
{% trans '评论' %}: <a href="{% url 'games:retrieve_review' mark.id %}">{{ mark.title }}</a>
|
||||
{% else %}
|
||||
{% trans '标记' %}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if mark.text %}
|
||||
<p class="entity-marks__mark-content">{{ mark.text }}</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collectionitem %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load humanize %}
|
||||
{% load neo %}
|
||||
{% current_user_marked_item movie as marked %}
|
||||
<li class="entity-list__entity">
|
||||
<div class="entity-list__entity-img-wrapper">
|
||||
<a href="{% url 'movies:retrieve' movie.id %}">
|
||||
<img src="{{ movie.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
{% if not marked %}
|
||||
<a class="entity-list__entity-action-icon" hx-post="{% url 'movies:wish' movie.id %}" title="加入想看">➕</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="entity-list__entity-text">
|
||||
{% if editable %}
|
||||
|
@ -47,7 +52,7 @@
|
|||
{% endif %}
|
||||
</a>
|
||||
|
||||
{% if not request.GET.c or not request.GET.c in categories %}
|
||||
{% if not request.GET.c and not hide_category %}
|
||||
<span class="entity-list__entity-category">[{{movie.verbose_category_name}}]</span>
|
||||
{% endif %}
|
||||
<a href="{{ movie.source_url }}">
|
||||
|
@ -108,7 +113,39 @@
|
|||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if mark %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
<div class="entity-marks" style="margin-bottom: 0;">
|
||||
<ul class="entity-marks__mark-list">
|
||||
<li class="entity-marks__mark">
|
||||
{% if mark.rating %}
|
||||
<span class="entity-marks__rating-star rating-star"
|
||||
data-rating-score="{{ mark.rating | floatformat:"0" }}" style="left: -4px;"></span>
|
||||
{% endif %}
|
||||
{% if mark.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-marks__mark-time">
|
||||
{% trans '于' %} {{ mark.edited_time }}
|
||||
{% if status == 'reviewed' %}
|
||||
{% trans '评论' %}: <a href="{% url 'movies:retrieve_review' mark.id %}">{{ mark.title }}</a>
|
||||
{% else %}
|
||||
{% trans '标记' %}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if mark.text %}
|
||||
<p class="entity-marks__mark-content">{{ mark.text }}</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collectionitem %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
{% load highlight %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load neo %}
|
||||
{% current_user_marked_item music as marked %}
|
||||
<li class="entity-list__entity">
|
||||
<div class="entity-list__entity-img-wrapper">
|
||||
|
||||
|
@ -9,12 +11,17 @@
|
|||
<a href="{% url 'music:retrieve_album' music.id %}">
|
||||
<img src="{{ music.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
{% if not marked %}
|
||||
<a class="entity-list__entity-action-icon" hx-post="{% url 'music:wish_album' music.id %}" title="加入想听">➕</a>
|
||||
{% endif %}
|
||||
{% elif music.category_name|lower == 'song' %}
|
||||
<a href="{% url 'music:retrieve_song' music.id %}">
|
||||
<img src="{{ music.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
{% if not marked %}
|
||||
<a class="entity-list__entity-action-icon" hx-post="{% url 'music:wish_song' music.id %}" title="加入想听">➕</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="entity-list__entity-text">
|
||||
{% if editable %}
|
||||
|
@ -50,7 +57,7 @@
|
|||
{% endif %}
|
||||
|
||||
|
||||
{% if not request.GET.c or not request.GET.c in categories %}
|
||||
{% if not request.GET.c and not hide_category %}
|
||||
<span class="entity-list__entity-category">[{{music.verbose_category_name}}]</span>
|
||||
{% endif %}
|
||||
<a href="{{ music.source_url }}">
|
||||
|
@ -107,7 +114,44 @@
|
|||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if mark %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
<div class="entity-marks" style="margin-bottom: 0;">
|
||||
<ul class="entity-marks__mark-list">
|
||||
<li class="entity-marks__mark">
|
||||
{% if mark.rating %}
|
||||
<span class="entity-marks__rating-star rating-star"
|
||||
data-rating-score="{{ mark.rating | floatformat:"0" }}" style="left: -4px;"></span>
|
||||
{% endif %}
|
||||
{% if mark.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-marks__mark-time">
|
||||
{% trans '于' %} {{ mark.edited_time }}
|
||||
{% if status == 'reviewed' %}
|
||||
{% trans '评论' %}:
|
||||
{% if music.category_name|lower == 'album' %}
|
||||
<a href="{% url 'music:retrieve_album_review' mark.id %}">{{ mark.title }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'music:retrieve_song_review' mark.id %}">{{ mark.title }}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans '标记' %}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if mark.text %}
|
||||
<p class="entity-marks__mark-content">{{ mark.text }}</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if collectionitem %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="dividing-line dividing-line--dashed"></div>
|
||||
|
|
9
common/templatetags/neo.py
Normal file
9
common/templatetags/neo.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django import template
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def current_user_marked_item(context, item):
|
||||
return context['request'].user.get_mark_for_item(item)
|
|
@ -97,6 +97,10 @@ class Game(Entity):
|
|||
def verbose_category_name(self):
|
||||
return _("游戏")
|
||||
|
||||
@property
|
||||
def mark_class(self):
|
||||
return GameMark
|
||||
|
||||
|
||||
class GameMark(Mark):
|
||||
game = models.ForeignKey(
|
||||
|
|
|
@ -9,6 +9,7 @@ urlpatterns = [
|
|||
path('update/<int:id>/', update, name='update'),
|
||||
path('delete/<int:id>/', delete, name='delete'),
|
||||
path('mark/', create_update_mark, name='create_update_mark'),
|
||||
path('wish/<int:id>/', wish, name='wish'),
|
||||
re_path('(?P<game_id>[0-9]+)/mark/list/(?:(?P<following_only>\\d+))?', retrieve_mark_list, name='retrieve_mark_list'),
|
||||
path('mark/delete/<int:id>/', delete_mark, name='delete_mark'),
|
||||
path('<int:game_id>/review/create/', create_review, name='create_review'),
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
from django.shortcuts import render, get_object_or_404, redirect, reverse
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError, HttpResponse
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Count
|
||||
|
@ -338,6 +338,26 @@ def create_update_mark(request):
|
|||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def wish(request, id):
|
||||
if request.method == 'POST':
|
||||
game = get_object_or_404(Game, pk=id)
|
||||
params = {
|
||||
'owner': request.user,
|
||||
'status': MarkStatusEnum.WISH,
|
||||
'visibility': 0,
|
||||
'game': game,
|
||||
}
|
||||
try:
|
||||
GameMark.objects.create(**params)
|
||||
except Exception:
|
||||
pass
|
||||
return HttpResponse("✔️")
|
||||
else:
|
||||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_mark_list(request, game_id, following_only=False):
|
||||
|
|
|
@ -224,6 +224,10 @@ class Movie(Entity):
|
|||
else:
|
||||
return _("电影")
|
||||
|
||||
@property
|
||||
def mark_class(self):
|
||||
return MovieMark
|
||||
|
||||
|
||||
class MovieMark(Mark):
|
||||
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='movie_marks', null=True)
|
||||
|
|
|
@ -9,6 +9,7 @@ urlpatterns = [
|
|||
path('update/<int:id>/', update, name='update'),
|
||||
path('delete/<int:id>/', delete, name='delete'),
|
||||
path('mark/', create_update_mark, name='create_update_mark'),
|
||||
path('wish/<int:id>/', wish, name='wish'),
|
||||
re_path('(?P<movie_id>[0-9]+)/mark/list/(?:(?P<following_only>\\d+))?', retrieve_mark_list, name='retrieve_mark_list'),
|
||||
path('mark/delete/<int:id>/', delete_mark, name='delete_mark'),
|
||||
path('<int:movie_id>/review/create/', create_review, name='create_review'),
|
||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
|||
from django.shortcuts import render, get_object_or_404, redirect, reverse
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError, HttpResponse
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Count
|
||||
|
@ -337,6 +337,26 @@ def create_update_mark(request):
|
|||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def wish(request, id):
|
||||
if request.method == 'POST':
|
||||
movie = get_object_or_404(Movie, pk=id)
|
||||
params = {
|
||||
'owner': request.user,
|
||||
'status': MarkStatusEnum.WISH,
|
||||
'visibility': 0,
|
||||
'movie': movie,
|
||||
}
|
||||
try:
|
||||
MovieMark.objects.create(**params)
|
||||
except Exception:
|
||||
pass
|
||||
return HttpResponse("✔️")
|
||||
else:
|
||||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_mark_list(request, movie_id, following_only=False):
|
||||
|
|
|
@ -76,6 +76,10 @@ class Album(Entity):
|
|||
def verbose_category_name(self):
|
||||
return _("专辑")
|
||||
|
||||
@property
|
||||
def mark_class(self):
|
||||
return AlbumMark
|
||||
|
||||
|
||||
class Song(Entity):
|
||||
'''
|
||||
|
@ -122,11 +126,16 @@ class Song(Entity):
|
|||
|
||||
def get_tags_manager(self):
|
||||
return self.song_tags
|
||||
|
||||
|
||||
@property
|
||||
def verbose_category_name(self):
|
||||
return _("单曲")
|
||||
|
||||
@property
|
||||
def mark_class(self):
|
||||
return SongMark
|
||||
|
||||
|
||||
class SongMark(Mark):
|
||||
song = models.ForeignKey(
|
||||
Song, on_delete=models.CASCADE, related_name='song_marks', null=True)
|
||||
|
|
|
@ -9,6 +9,7 @@ urlpatterns = [
|
|||
path('song/update/<int:id>/', update_song, name='update_song'),
|
||||
path('song/delete/<int:id>/', delete_song, name='delete_song'),
|
||||
path('song/mark/', create_update_song_mark, name='create_update_song_mark'),
|
||||
path('song/wish/<int:id>/', wish_song, name='wish_song'),
|
||||
path('song/<int:song_id>/mark/list/',
|
||||
retrieve_song_mark_list, name='retrieve_song_mark_list'),
|
||||
path('song/mark/delete/<int:id>/', delete_song_mark, name='delete_song_mark'),
|
||||
|
@ -19,12 +20,13 @@ urlpatterns = [
|
|||
re_path('song/(?P<song_id>[0-9]+)/mark/list/(?:(?P<following_only>\\d+))?', retrieve_song_mark_list, name='retrieve_song_mark_list'),
|
||||
# path('song/scrape/', scrape_song, name='scrape_song'),
|
||||
path('song/click_to_scrape/', click_to_scrape_song, name='click_to_scrape_song'),
|
||||
|
||||
|
||||
path('album/create/', create_album, name='create_album'),
|
||||
path('album/<int:id>/', retrieve_album, name='retrieve_album'),
|
||||
path('album/update/<int:id>/', update_album, name='update_album'),
|
||||
path('album/delete/<int:id>/', delete_album, name='delete_album'),
|
||||
path('album/mark/', create_update_album_mark, name='create_update_album_mark'),
|
||||
path('album/wish/<int:id>/', wish_album, name='wish_album'),
|
||||
re_path('album/(?P<album_id>[0-9]+)/mark/list/(?:(?P<following_only>\\d+))?', retrieve_album_mark_list, name='retrieve_album_mark_list'),
|
||||
path('album/mark/delete/<int:id>/', delete_album_mark, name='delete_album_mark'),
|
||||
path('album/<int:album_id>/review/create/', create_album_review, name='create_album_review'),
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.utils import timezone
|
|||
from django.db.models import Count
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError
|
||||
from django.http import HttpResponseBadRequest, HttpResponseServerError, HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, get_object_or_404, redirect, reverse
|
||||
|
@ -356,6 +356,26 @@ def create_update_song_mark(request):
|
|||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def wish_song(request, id):
|
||||
if request.method == 'POST':
|
||||
song = get_object_or_404(Song, pk=id)
|
||||
params = {
|
||||
'owner': request.user,
|
||||
'status': MarkStatusEnum.WISH,
|
||||
'visibility': 0,
|
||||
'song': song,
|
||||
}
|
||||
try:
|
||||
SongMark.objects.create(**params)
|
||||
except Exception:
|
||||
pass
|
||||
return HttpResponse("✔️")
|
||||
else:
|
||||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_song_mark_list(request, song_id, following_only=False):
|
||||
|
@ -931,6 +951,26 @@ def create_update_album_mark(request):
|
|||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def wish_album(request, id):
|
||||
if request.method == 'POST':
|
||||
album = get_object_or_404(Album, pk=id)
|
||||
params = {
|
||||
'owner': request.user,
|
||||
'status': MarkStatusEnum.WISH,
|
||||
'visibility': 0,
|
||||
'album': album,
|
||||
}
|
||||
try:
|
||||
AlbumMark.objects.create(**params)
|
||||
except Exception:
|
||||
pass
|
||||
return HttpResponse("✔️")
|
||||
else:
|
||||
return HttpResponseBadRequest("invalid method")
|
||||
|
||||
|
||||
@mastodon_request_included
|
||||
@login_required
|
||||
def retrieve_album_mark_list(request, album_id, following_only=False):
|
||||
|
|
|
@ -100,6 +100,11 @@ class User(AbstractUser):
|
|||
def is_followed_by(self, target):
|
||||
return target.is_following(self)
|
||||
|
||||
def get_mark_for_item(self, item):
|
||||
params = {item.__class__.__name__.lower() + '_id': item.id, 'owner': self}
|
||||
mark = item.mark_class.objects.filter(**params).first()
|
||||
return mark
|
||||
|
||||
|
||||
class Preference(models.Model):
|
||||
user = models.OneToOneField(User, models.CASCADE, primary_key=True)
|
||||
|
|
190
users/templates/users/item_list.html
Normal file
190
users/templates/users/item_list.html
Normal file
|
@ -0,0 +1,190 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% 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 }} - {{ user.mastodon_username }} {{ list_title }}</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@1.6.1"></script>
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
|
||||
<script src="{% static 'js/mastodon.js' %}"></script>
|
||||
<script src="{% static 'js/home.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/neo.css' %}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
<div id="content-wrapper">
|
||||
{% include "partial/_navbar.html" %}
|
||||
|
||||
<section id="content" class="container">
|
||||
<div class="grid grid--reverse-order">
|
||||
<div class="grid__main grid__main--reverse-order">
|
||||
<div class="main-section-wrapper">
|
||||
<div class="entity-list">
|
||||
|
||||
<div class="set">
|
||||
<h5 class="entity-list__title">
|
||||
{{ user.mastodon_username }} {{ list_title }}
|
||||
</h5>
|
||||
</div>
|
||||
<ul class="entity-list__entities">
|
||||
{% for mark in marks %}
|
||||
{% include "partial/list_item.html" with item=mark.item hide_category=True %}
|
||||
{% empty %}
|
||||
<div>{% trans '无结果' %}</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
|
||||
{% if marks.pagination.has_prev %}
|
||||
<a href="?page=1" class="pagination__nav-link pagination__nav-link">«</a>
|
||||
<a href="?page={{ marks.previous_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">‹</a>
|
||||
{% endif %}
|
||||
|
||||
{% for page in marks.pagination.page_range %}
|
||||
|
||||
{% if page == marks.pagination.current_page %}
|
||||
<a href="?page={{ page }}" class="pagination__page-link pagination__page-link--current">{{ page }}</a>
|
||||
{% else %}
|
||||
<a href="?page={{ page }}" class="pagination__page-link">{{ page }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% if marks.pagination.has_next %}
|
||||
<a href="?page={{ marks.next_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--left-margin">›</a>
|
||||
<a href="?page={{ marks.pagination.last_page }}" class="pagination__nav-link">»</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid__aside grid__aside--reverse-order grid__aside--tablet-column">
|
||||
<div class="aside-section-wrapper aside-section-wrapper--no-margin">
|
||||
<div class="user-profile" id="userInfoCard">
|
||||
<div class="user-profile__header">
|
||||
<!-- <img src="" class="user-profile__avatar mast-avatar" alt="{{ user.username }}"> -->
|
||||
<img src="" class="user-profile__avatar mast-avatar">
|
||||
<a href="{% url 'users:home' user.mastodon_username %}">
|
||||
<h5 class="user-profile__username mast-displayname"></h5>
|
||||
</a>
|
||||
</div>
|
||||
<p class="user-profile__bio mast-brief"></p>
|
||||
<!-- <a href="#" class="follow">{% trans '关注TA' %}</a> -->
|
||||
|
||||
{% if request.user != user %}
|
||||
<a href="{% url 'users:report' %}?user_id={{ user.id }}"
|
||||
class="user-profile__report-link">{% trans '举报用户' %}</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relation-dropdown">
|
||||
<div class="relation-dropdown__button">
|
||||
<span class="icon-arrow">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
|
||||
<path d="M8.12,3.29,5,6.42,1.86,3.29H.45L5,7.84,9.55,3.29Z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="relation-dropdown__body">
|
||||
<div class="aside-section-wrapper aside-section-wrapper--transparent aside-section-wrapper--collapse">
|
||||
|
||||
<div class="user-relation" id="followings">
|
||||
<h5 class="user-relation__label">
|
||||
{% trans '关注的人' %}
|
||||
</h5>
|
||||
<a href="{% url 'users:following' user.mastodon_username %}"
|
||||
class="user-relation__more-link mast-following-more">{% trans '更多' %}</a>
|
||||
<ul class="user-relation__related-user-list mast-following">
|
||||
<li class="user-relation__related-user">
|
||||
<a>
|
||||
<img src="" alt="" class="user-relation__related-user-avatar">
|
||||
<div class="user-relation__related-user-name mast-displayname">
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="user-relation" id="followers">
|
||||
<h5 class="user-relation__label">
|
||||
{% trans '被他们关注' %}
|
||||
</h5>
|
||||
<a href="{% url 'users:followers' user.mastodon_username %}"
|
||||
class="user-relation__more-link mast-followers-more">{% trans '更多' %}</a>
|
||||
<ul class="user-relation__related-user-list mast-followers">
|
||||
<li class="user-relation__related-user">
|
||||
<a>
|
||||
<img src="" alt="" class="user-relation__related-user-avatar">
|
||||
<div class="user-relation__related-user-name mast-displayname">
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
|
||||
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
|
||||
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
|
||||
<!--current user mastodon id-->
|
||||
{% if user == request.user %}
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% else %}
|
||||
<div id="userMastodonID" hidden="true">{{ user.target_site_id }}</div>
|
||||
{% endif %}
|
||||
<div id="userPageURL" hidden="true">{% url 'users:home' 0 %}</div>
|
||||
<div id="spinner" hidden>
|
||||
<div class="spinner">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
|
@ -644,7 +644,7 @@ def book_list(request, id, status):
|
|||
list_title = str(BookMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的书"))
|
||||
return render(
|
||||
request,
|
||||
'users/book_list.html',
|
||||
'users/item_list.html',
|
||||
{
|
||||
'marks': marks,
|
||||
'user': user,
|
||||
|
@ -730,7 +730,7 @@ def movie_list(request, id, status):
|
|||
|
||||
return render(
|
||||
request,
|
||||
'users/movie_list.html',
|
||||
'users/item_list.html',
|
||||
{
|
||||
'marks': marks,
|
||||
'user': user,
|
||||
|
@ -816,7 +816,7 @@ def game_list(request, id, status):
|
|||
list_title = str(GameMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的游戏"))
|
||||
return render(
|
||||
request,
|
||||
'users/game_list.html',
|
||||
'users/item_list.html',
|
||||
{
|
||||
'marks': marks,
|
||||
'user': user,
|
||||
|
@ -915,7 +915,7 @@ def music_list(request, id, status):
|
|||
list_title = str(MusicMarkStatusTranslator(MarkStatusEnum[status.upper()])) + str(_("的音乐"))
|
||||
return render(
|
||||
request,
|
||||
'users/music_list.html',
|
||||
'users/item_list.html',
|
||||
{
|
||||
'marks': marks,
|
||||
'user': user,
|
||||
|
|
Loading…
Add table
Reference in a new issue