wip
This commit is contained in:
parent
3875a82381
commit
84359ec4fa
11 changed files with 423 additions and 51 deletions
|
@ -8,11 +8,9 @@ from common.forms import *
|
|||
class CollectionForm(forms.ModelForm):
|
||||
# id = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||||
title = forms.CharField(label=_("标题"))
|
||||
description = MarkdownxFormField(label=_("正文 (Markdown)"))
|
||||
description = MarkdownxFormField(label=_("详细介绍 (Markdown)"))
|
||||
share_to_mastodon = forms.BooleanField(
|
||||
label=_("分享到联邦网络"), initial=True, required=False)
|
||||
rating = forms.IntegerField(
|
||||
label=_("评分"), validators=[RatingValidator()], widget=forms.HiddenInput(), required=False)
|
||||
visibility = forms.TypedChoiceField(
|
||||
label=_("可见性"),
|
||||
initial=0,
|
||||
|
@ -25,16 +23,11 @@ class CollectionForm(forms.ModelForm):
|
|||
model = Collection
|
||||
fields = [
|
||||
'title',
|
||||
'visibility',
|
||||
'description',
|
||||
'cover',
|
||||
'visibility',
|
||||
]
|
||||
|
||||
widgets = {
|
||||
# 'name': forms.TextInput(attrs={'placeholder': _("收藏单名称")}),
|
||||
# 'developer': forms.TextInput(attrs={'placeholder': _("多个开发商使用英文逗号分隔")}),
|
||||
# 'publisher': forms.TextInput(attrs={'placeholder': _("多个发行商使用英文逗号分隔")}),
|
||||
# 'genre': forms.TextInput(attrs={'placeholder': _("多个类型使用英文逗号分隔")}),
|
||||
# 'platform': forms.TextInput(attrs={'placeholder': _("多个平台使用英文逗号分隔")}),
|
||||
'cover': PreviewImageInput(),
|
||||
}
|
||||
|
|
|
@ -22,15 +22,30 @@ class Collection(UserOwnedEntity):
|
|||
def __str__(self):
|
||||
return str(self.owner) + ': ' + self.name
|
||||
|
||||
@property
|
||||
def collectionitem_list(self):
|
||||
return sorted(list(self.collectionitem_set.all()), key=lambda i: i.position)
|
||||
|
||||
@property
|
||||
def item_list(self):
|
||||
return list(self.collectionitem_set.objects.all()).sort(lambda i: i.position)
|
||||
return map(lambda i: i.item, self.collectionitem_list)
|
||||
|
||||
@property
|
||||
def plain_description(self):
|
||||
html = markdown(self.description)
|
||||
return RE_HTML_TAG.sub(' ', html)
|
||||
|
||||
def append_item(self, item, comment=""):
|
||||
cl = self.collectionitem_list
|
||||
if item is None or len(list(filter(lambda i: i.item == item, cl))) > 0:
|
||||
return None
|
||||
else:
|
||||
i = CollectionItem(collection=self, position=cl[-1].position + 1 if len(cl) else 1, comment=comment)
|
||||
print(i)
|
||||
i.set_item(item)
|
||||
i.save()
|
||||
return i
|
||||
|
||||
|
||||
class CollectionItem(models.Model):
|
||||
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, null=True)
|
||||
|
@ -46,3 +61,16 @@ class CollectionItem(models.Model):
|
|||
def item(self):
|
||||
items = list(filter(lambda i: i is not None, [self.movie, self.book, self.album, self.song, self.game]))
|
||||
return items[0] if len(items) > 0 else None
|
||||
|
||||
# @item.setter
|
||||
def set_item(self, new_item):
|
||||
old_item = self.item
|
||||
if old_item == new_item:
|
||||
return
|
||||
if old_item is not None:
|
||||
self.movie = None
|
||||
self.book = None
|
||||
self.album = None
|
||||
self.song = None
|
||||
self.game = None
|
||||
setattr(self, new_item.__class__.__name__.lower(), new_item)
|
||||
|
|
|
@ -17,39 +17,23 @@
|
|||
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
{% include "partial/_navbar.html" %}
|
||||
<div id="content-wrapper">
|
||||
{% include "partial/_navbar.html" %}
|
||||
|
||||
<section id="content" class="container">
|
||||
<div class="grid">
|
||||
<div class="single-section-wrapper" id="main">
|
||||
<form class="entity-form" action="{{ submit_url }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{% for field in form %}
|
||||
|
||||
{% if field.name == 'release_date' %}
|
||||
{{ field.label_tag }}
|
||||
|
||||
{% else %}
|
||||
{% if field.name != 'id' %}
|
||||
{{ field.label_tag }}
|
||||
{% endif %}
|
||||
{{ field }}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{{ form }}
|
||||
<input class="button" type="submit" value="{% trans '提交' %}">
|
||||
</form>
|
||||
{{ form.media }}
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
|
||||
{% comment %}
|
||||
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
|
||||
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
|
||||
|
|
|
@ -18,12 +18,13 @@
|
|||
<meta property="og:article:author" content="{{ collection.owner.username }}">
|
||||
<meta property="og:url" content="{{ request.build_absolute_uri }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'img/logo_square.svg' %}">
|
||||
<title>{{ site_name }} {% trans '收藏單' %} - {{ collection.title }}</title>
|
||||
<title>{{ site_name }} {% trans '收藏单' %} - {{ collection.title }}</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.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' %}">
|
||||
<script src="https://unpkg.com/htmx.org@1.6.1"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -67,6 +68,78 @@
|
|||
</div>
|
||||
{{ form.media }}
|
||||
</div>
|
||||
<div class="entity-list">
|
||||
<ul class="entity-list__entities">
|
||||
{% for item in collection.collectionitem_list %}
|
||||
{% if item.item is not None %}
|
||||
<li class="entity-list__entity">
|
||||
<div class="entity-list__entity-img-wrapper">
|
||||
<a href="{{ item.item.get_absolute_url }}">
|
||||
<img src="{{ item.item.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
|
||||
</a>
|
||||
</div>
|
||||
<div class="entity-list__entity-text">
|
||||
{% if editable %}
|
||||
<div style="float:right">
|
||||
{% if not forloop.first %}
|
||||
<a hx-post="{% url 'collection:move_up_item' form.instance.id item.id %}">▲</a>
|
||||
{% endif %}
|
||||
{% if not forloop.last %}
|
||||
<a hx-post="{% url 'collection:move_down_item' form.instance.id item.id %}">▼</a>
|
||||
{% endif %}
|
||||
<a hx-post="{% url 'collection:delete_item' form.instance.id item.id %}">✖</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="entity-list__entity-title">
|
||||
<a href="{{ item.item.get_absolute_url }}">{{ item.item.title }}</a>
|
||||
<a href="{{ item.item.source_url }}">
|
||||
<span class="source-label source-label__{{ item.item.source_site }}">{{ item.item.get_source_site_display }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<span class="entity-list__entity-info entity-list__entity-info--full-length">
|
||||
<p class="entity-list__entity-brief">
|
||||
{{ item.item.brief }}
|
||||
</p>
|
||||
</span>
|
||||
<div class="tag-collection">
|
||||
{% for tag_dict in item.item.tag_list %}
|
||||
{% for k, v in tag_dict.items %}
|
||||
{% if k == 'content' %}
|
||||
<span class="tag-collection__tag">
|
||||
<a href="{% url 'common:search' %}?tag={{ v }}">{{ v }}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<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">
|
||||
<p class="entity-marks__mark-content">{{ item.comment }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
{% endfor %}
|
||||
{% if editable %}
|
||||
<li>
|
||||
<form action="{% url 'collection:append_item' form.instance.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="url" name="url" placeholder="https://neodb.social/movies/1/" style="min-width:24rem" required>
|
||||
<input type="text" name="comment" placeholder="{% trans '备注' %}" style="min-width:24rem">
|
||||
<input class="button" type="submit" value="{% trans '添加' %}">
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -102,12 +175,14 @@
|
|||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
$(".markdownx textarea").hide();
|
||||
</script>
|
||||
<script>
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
|
|
105
collection/templates/list.html
Normal file
105
collection/templates/list.html
Normal file
|
@ -0,0 +1,105 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% load humanize %}
|
||||
{% load admin_url %}
|
||||
{% load mastodon %}
|
||||
{% load oauth_token %}
|
||||
{% load truncate %}
|
||||
{% load highlight %}
|
||||
{% 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 }} - {{ request.user.mastodon_username }}{% trans '的收藏' %}</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.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' %}">
|
||||
</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="main-section-wrapper">
|
||||
<div class="entity-reviews">
|
||||
<h5 class="entity-reviews__title entity-reviews__title--stand-alone">
|
||||
{{ request.user.mastodon_username }}{% trans '的收藏单' %}
|
||||
</h5>
|
||||
<ul class="entity-reviews__review-list">
|
||||
|
||||
{% for collection in collections %}
|
||||
|
||||
<li class="entity-reviews__review entity-reviews__review--wider">
|
||||
<img src="{{ collection.cover|thumb:'normal' }}" style="width:40px; float:right"class="entity-card__img">
|
||||
<span class="entity-reviews__review-title"><a href="{% url 'collection:retrieve' collection.id %}">{{ collection.title }}</a></span>
|
||||
<span class="entity-reviews__review-time">{{ collection.edited_time }}</span>
|
||||
{% if collection.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 %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<div>{% trans '无结果' %}</div>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
|
||||
{% if collections.pagination.has_prev %}
|
||||
<a href="?page=1" class="pagination__nav-link pagination__nav-link">«</a>
|
||||
<a href="?page={{ collections.previous_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">‹</a>
|
||||
{% endif %}
|
||||
|
||||
{% for page in collections.pagination.page_range %}
|
||||
|
||||
{% if page == collections.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 collections.pagination.has_next %}
|
||||
<a href="?page={{ collections.next_page_number }}"
|
||||
class="pagination__nav-link pagination__nav-link--left-margin">›</a>
|
||||
<a href="?page={{ collections.pagination.last_page }}" class="pagination__nav-link">»</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
|
||||
{% comment %}
|
||||
<div id="oauth2Token" hidden="true">{% oauth_token %}</div>
|
||||
<div id="mastodonURI" hidden="true">{% mastodon request.user.mastodon_site %}</div>
|
||||
<!--current user mastodon id-->
|
||||
<div id="userMastodonID" hidden="true">{{ user.mastodon_id }}</div>
|
||||
{% endcomment %}
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
|
@ -4,12 +4,17 @@ from .views import *
|
|||
|
||||
app_name = 'collection'
|
||||
urlpatterns = [
|
||||
path('mine/', list, name='list'),
|
||||
path('create/', create, name='create'),
|
||||
path('<int:id>/', retrieve, name='retrieve'),
|
||||
path('update/<int:id>/', update, name='update'),
|
||||
path('delete/<int:id>/', delete, name='delete'),
|
||||
path('follow/<int:id>/', follow, name='follow'),
|
||||
path('unfollow/<int:id>/', follow, name='unfollow'),
|
||||
path('unfollow/<int:id>/', unfollow, name='unfollow'),
|
||||
path('<int:id>/append_item/', append_item, name='append_item'),
|
||||
path('<int:id>/delete_item/<int:item_id>', delete_item, name='delete_item'),
|
||||
path('<int:id>/move_up_item/<int:item_id>', move_up_item, name='move_up_item'),
|
||||
path('<int:id>/move_down_item/<int:item_id>', move_down_item, name='move_down_item'),
|
||||
path('with/<str:type>/<int:id>/', list_with, name='list_with'),
|
||||
# TODO: tag
|
||||
]
|
||||
|
|
|
@ -18,6 +18,10 @@ from common.models import SourceSiteEnum
|
|||
from .models import *
|
||||
from .forms import *
|
||||
from django.conf import settings
|
||||
import re
|
||||
from users.models import User
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -36,6 +40,13 @@ REVIEW_PER_PAGE = 20
|
|||
TAG_NUMBER = 10
|
||||
|
||||
|
||||
class HTTPResponseHXRedirect(HttpResponseRedirect):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self['HX-Redirect'] = self['Location']
|
||||
status_code = 200
|
||||
|
||||
|
||||
# public data
|
||||
###########################
|
||||
@login_required
|
||||
|
@ -73,7 +84,7 @@ def create(request):
|
|||
'create_update.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('添加收藏單'),
|
||||
'title': _('添加收藏单'),
|
||||
'submit_url': reverse("collection:create"),
|
||||
# provided for frontend js
|
||||
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
|
||||
|
@ -87,7 +98,7 @@ def create(request):
|
|||
|
||||
@login_required
|
||||
def update(request, id):
|
||||
page_title = _("修改游戏")
|
||||
page_title = _("修改收藏单")
|
||||
collection = get_object_or_404(Collection, pk=id)
|
||||
if not collection.is_visible_to(request.user):
|
||||
raise PermissionDenied()
|
||||
|
@ -112,10 +123,6 @@ def update(request, id):
|
|||
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")
|
||||
|
@ -131,7 +138,7 @@ def update(request, id):
|
|||
'this_site_enum_value': SourceSiteEnum.IN_SITE.value,
|
||||
}
|
||||
)
|
||||
return redirect(reverse("games:retrieve", args=[form.instance.id]))
|
||||
return redirect(reverse("collection:retrieve", args=[form.instance.id]))
|
||||
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
@ -156,32 +163,31 @@ def retrieve(request, id):
|
|||
{
|
||||
'collection': collection,
|
||||
'form': form,
|
||||
'editable': collection.is_editable_by(request.user),
|
||||
'followers': followers,
|
||||
|
||||
}
|
||||
)
|
||||
else:
|
||||
logger.warning('non-GET method at /games/<id>')
|
||||
logger.warning('non-GET method at /collections/<id>')
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@permission_required("games.delete_game")
|
||||
@permission_required("collections.delete_collection")
|
||||
@login_required
|
||||
def delete(request, id):
|
||||
collection = get_object_or_404(Collection, pk=id)
|
||||
if request.method == 'GET':
|
||||
game = get_object_or_404(Game, pk=id)
|
||||
return render(
|
||||
request,
|
||||
'games/delete.html',
|
||||
'collections/delete.html',
|
||||
{
|
||||
'game': game,
|
||||
'collection': collection,
|
||||
}
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
if request.user.is_staff:
|
||||
# only staff has right to delete
|
||||
game = get_object_or_404(Game, pk=id)
|
||||
game.delete()
|
||||
if request.user.is_staff or request.user == collection.owner:
|
||||
collection.delete()
|
||||
return redirect(reverse("common:home"))
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
|
@ -199,6 +205,112 @@ def unfollow(request, id):
|
|||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def list(request, user_id=None):
|
||||
if request.method == 'GET':
|
||||
queryset = Collection.objects.filter(owner=request.user if user_id is None else User.objects.get(id=user_id))
|
||||
paginator = Paginator(queryset, REVIEW_PER_PAGE)
|
||||
page_number = request.GET.get('page', default=1)
|
||||
reviews = paginator.get_page(page_number)
|
||||
reviews.pagination = PageLinksGenerator(
|
||||
PAGE_LINK_NUMBER, page_number, paginator.num_pages)
|
||||
return render(
|
||||
request,
|
||||
'list.html',
|
||||
{
|
||||
'collections': queryset,
|
||||
}
|
||||
)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
def get_entity_by_url(url):
|
||||
m = re.findall(r'^/?(movies|books|games|music/album|music/song)/(\d+)/?', url.lower().replace(settings.APP_WEBSITE.lower(), ''))
|
||||
if len(m) > 0:
|
||||
mapping = {
|
||||
'movies': Movie,
|
||||
'books': Book,
|
||||
'games': Game,
|
||||
'music/album': Album,
|
||||
'music/song': Song,
|
||||
}
|
||||
cls = mapping.get(m[0][0])
|
||||
id = int(m[0][1])
|
||||
if cls is not None:
|
||||
return cls.objects.get(id=id)
|
||||
return None
|
||||
|
||||
|
||||
@login_required
|
||||
def append_item(request, id):
|
||||
collection = get_object_or_404(Collection, pk=id)
|
||||
if request.method == 'POST' and collection.is_editable_by(request.user):
|
||||
url = request.POST.get('url')
|
||||
comment = request.POST.get('comment')
|
||||
item = get_entity_by_url(url)
|
||||
collection.append_item(item, comment)
|
||||
collection.save()
|
||||
return redirect(reverse("collection:retrieve", args=[id]))
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def delete_item(request, id, item_id):
|
||||
collection = get_object_or_404(Collection, pk=id)
|
||||
if request.method == 'POST' and collection.is_editable_by(request.user):
|
||||
# item_id = int(request.POST.get('item_id'))
|
||||
item = CollectionItem.objects.get(id=item_id)
|
||||
if item is not None and item.collection == collection:
|
||||
item.delete()
|
||||
# collection.save()
|
||||
return HTTPResponseHXRedirect(redirect_to=reverse("collection:retrieve", args=[id]))
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def move_up_item(request, id, item_id):
|
||||
collection = get_object_or_404(Collection, pk=id)
|
||||
if request.method == 'POST' and collection.is_editable_by(request.user):
|
||||
# item_id = int(request.POST.get('item_id'))
|
||||
item = CollectionItem.objects.get(id=item_id)
|
||||
if item is not None and item.collection == collection:
|
||||
items = collection.collectionitem_list
|
||||
idx = items.index(item)
|
||||
if idx > 0:
|
||||
o = items[idx - 1]
|
||||
p = o.position
|
||||
o.position = item.position
|
||||
item.position = p
|
||||
o.save()
|
||||
item.save()
|
||||
# collection.save()
|
||||
return HTTPResponseHXRedirect(redirect_to=reverse("collection:retrieve", args=[id]))
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def move_down_item(request, id, item_id):
|
||||
collection = get_object_or_404(Collection, pk=id)
|
||||
if request.method == 'POST' and collection.is_editable_by(request.user):
|
||||
# item_id = int(request.POST.get('item_id'))
|
||||
item = CollectionItem.objects.get(id=item_id)
|
||||
if item is not None and item.collection == collection:
|
||||
items = collection.collectionitem_list
|
||||
idx = items.index(item)
|
||||
if idx + 1 < len(items):
|
||||
o = items[idx + 1]
|
||||
p = o.position
|
||||
o.position = item.position
|
||||
item.position = p
|
||||
o.save()
|
||||
item.save()
|
||||
# collection.save()
|
||||
return HTTPResponseHXRedirect(redirect_to=reverse("collection:retrieve", args=[id]))
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def list_with(request, type, id):
|
||||
pass
|
||||
|
|
|
@ -174,6 +174,9 @@ class UserOwnedEntity(models.Model):
|
|||
else:
|
||||
return True
|
||||
|
||||
def is_editable_by(self, viewer):
|
||||
return True if viewer.is_staff or viewer.is_superuser or viewer == self.owner else False
|
||||
|
||||
@classmethod
|
||||
def get_available(cls, entity, request_user, following_only=False):
|
||||
# e.g. SongMark.get_available(song, request.user, request.session['oauth_token'])
|
||||
|
|
|
@ -531,6 +531,38 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="entity-sort" id="collectionCreated">
|
||||
<h5 class="entity-sort__label">
|
||||
{% trans '创建的收藏单' %}
|
||||
</h5>
|
||||
<span class="entity-sort__count">
|
||||
{{ collections_count }}
|
||||
</span>
|
||||
{% if collections_more %}
|
||||
<a href="{% url 'users:collection_list' user.mastodon_username %}"
|
||||
class="entity-sort__more-link">{% trans '更多' %}</a>
|
||||
{% endif %}
|
||||
{% if user == request.user %}
|
||||
<a href="{% url 'collection:create' %}"class="entity-sort__more-link">{% trans '新建' %}</a>
|
||||
{% endif %}
|
||||
|
||||
<ul class="entity-sort__entity-list">
|
||||
{% for collection in collections %}
|
||||
<li class="entity-sort__entity">
|
||||
|
||||
<a href="{% url 'collection:retrieve' collection.id %}">
|
||||
<img src="{{ collection.cover|thumb:'normal' }}"
|
||||
alt="{{collection.title}}" class="entity-sort__entity-img">
|
||||
<span class="entity-sort__entity-name"
|
||||
title="{{collection.title}}">{{ collection.title }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div>暂无记录</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% if user == request.user %}
|
||||
|
|
|
@ -20,6 +20,7 @@ urlpatterns = [
|
|||
path('<int:id>/', home, name='home'),
|
||||
path('<int:id>/followers/', followers, name='followers'),
|
||||
path('<int:id>/following/', following, name='following'),
|
||||
path('<int:id>/collections/', collection_list, name='collection_list'),
|
||||
path('<int:id>/book/<str:status>/', book_list, name='book_list'),
|
||||
path('<int:id>/movie/<str:status>/', movie_list, name='movie_list'),
|
||||
path('<int:id>/music/<str:status>/', music_list, name='music_list'),
|
||||
|
@ -27,6 +28,7 @@ urlpatterns = [
|
|||
path('<str:id>/', home, name='home'),
|
||||
path('<str:id>/followers/', followers, name='followers'),
|
||||
path('<str:id>/following/', following, name='following'),
|
||||
path('<str:id>/collections/', collection_list, name='collection_list'),
|
||||
path('<str:id>/book/<str:status>/', book_list, name='book_list'),
|
||||
path('<str:id>/movie/<str:status>/', movie_list, name='movie_list'),
|
||||
path('<str:id>/music/<str:status>/', music_list, name='music_list'),
|
||||
|
|
|
@ -37,6 +37,7 @@ from books.models import BookMark, BookReview
|
|||
from movies.models import MovieMark, MovieReview
|
||||
from games.models import GameMark, GameReview
|
||||
from music.models import AlbumMark, SongMark, AlbumReview, SongReview
|
||||
from collection.models import Collection
|
||||
|
||||
|
||||
# Views
|
||||
|
@ -301,7 +302,7 @@ def home(request, id):
|
|||
album_reviews = AlbumReview.get_available_by_user(user, relation['following'])
|
||||
game_reviews = GameReview.get_available_by_user(user, relation['following'])
|
||||
|
||||
|
||||
collections = Collection.objects.filter(owner=user)
|
||||
# book marks
|
||||
filtered_book_marks = filter_marks(book_marks, BOOKS_PER_SET, 'book')
|
||||
book_marks_count = count_marks(book_marks, "book")
|
||||
|
@ -364,6 +365,10 @@ def home(request, id):
|
|||
'music_reviews_count': len(music_reviews),
|
||||
'game_reviews_count': game_reviews.count(),
|
||||
|
||||
'collections': collections.order_by("-edited_time")[:BOOKS_PER_SET],
|
||||
'collections_count': collections.count(),
|
||||
'collections_more': collections.count() > BOOKS_PER_SET,
|
||||
|
||||
'layout': layout,
|
||||
'reports': reports,
|
||||
'unread_announcements': unread_announcements,
|
||||
|
@ -902,6 +907,34 @@ def manage_report(request):
|
|||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@login_required
|
||||
def collection_list(request, id):
|
||||
from collection.views import list
|
||||
if isinstance(id, str):
|
||||
try:
|
||||
username = id.split('@')[0]
|
||||
site = id.split('@')[1]
|
||||
except IndexError as e:
|
||||
return HttpResponseBadRequest("Invalid user id")
|
||||
query_kwargs = {'username': username, 'mastodon_site': site}
|
||||
elif isinstance(id, int):
|
||||
query_kwargs = {'pk': id}
|
||||
try:
|
||||
user = User.objects.get(**query_kwargs)
|
||||
except ObjectDoesNotExist:
|
||||
msg = _("😖哎呀这位老师还没有注册书影音呢,快去长毛象喊TA来吧!")
|
||||
sec_msg = _("目前只开放本站用户注册")
|
||||
return render(
|
||||
request,
|
||||
'common/error.html',
|
||||
{
|
||||
'msg': msg,
|
||||
'secondary_msg': sec_msg,
|
||||
}
|
||||
)
|
||||
return list(request, user.id)
|
||||
|
||||
|
||||
# Utils
|
||||
########################################
|
||||
def refresh_mastodon_data_task(user, token=None):
|
||||
|
|
Loading…
Add table
Reference in a new issue