diff --git a/collection/forms.py b/collection/forms.py
index 0aa317d2..4db1e885 100644
--- a/collection/forms.py
+++ b/collection/forms.py
@@ -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(),
}
diff --git a/collection/models.py b/collection/models.py
index a1a89a61..936032f8 100644
--- a/collection/models.py
+++ b/collection/models.py
@@ -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)
diff --git a/collection/templates/create_update.html b/collection/templates/create_update.html
index d2d69cb3..0189e566 100644
--- a/collection/templates/create_update.html
+++ b/collection/templates/create_update.html
@@ -17,39 +17,23 @@
+ {% include "partial/_navbar.html" %}
- {% include "partial/_navbar.html" %}
-
{% include "partial/_footer.html" %}
-
{% comment %}
{% oauth_token %}
{% mastodon request.user.mastodon_site %}
diff --git a/collection/templates/detail.html b/collection/templates/detail.html
index f20c9d43..9a4f06ce 100644
--- a/collection/templates/detail.html
+++ b/collection/templates/detail.html
@@ -18,12 +18,13 @@
-
{{ site_name }} {% trans '收藏單' %} - {{ collection.title }}
+
{{ site_name }} {% trans '收藏单' %} - {{ collection.title }}
+
@@ -67,6 +68,78 @@
{{ form.media }}
+
+
+ {% for item in collection.collectionitem_list %}
+ {% if item.item is not None %}
+ -
+
+
+ {% if editable %}
+
+ {% if not forloop.first %}
+
▲
+ {% endif %}
+ {% if not forloop.last %}
+
▼
+ {% endif %}
+
✖
+
+ {% endif %}
+
+
+
+ {{ item.item.brief }}
+
+
+
+ {% for tag_dict in item.item.tag_list %}
+ {% for k, v in tag_dict.items %}
+ {% if k == 'content' %}
+
+ {{ v }}
+
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+
+
+
+
+
+ -
+
{{ item.comment }}
+
+
+
+
+
+ {% endif %}
+ {% empty %}
+ {% endfor %}
+ {% if editable %}
+ -
+
+
+ {% endif %}
+
+
+
+
@@ -102,12 +175,14 @@
{{ user.mastodon_id }}
{% endcomment %}
-
+
diff --git a/collection/templates/list.html b/collection/templates/list.html
new file mode 100644
index 00000000..e6f73166
--- /dev/null
+++ b/collection/templates/list.html
@@ -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 %}
+
+
+
+
+
+
+ {{ site_name }} - {{ request.user.mastodon_username }}{% trans '的收藏' %}
+
+
+
+
+
+
+
+
+
+
+ {% include "partial/_navbar.html" %}
+
+
+
+
+
+
+
+ {{ request.user.mastodon_username }}{% trans '的收藏单' %}
+
+
+
+ {% for collection in collections %}
+
+ -
+
+ {{ collection.title }}
+ {{ collection.edited_time }}
+ {% if collection.visibility > 0 %}
+
+ {% endif %}
+
+ {% empty %}
+ {% trans '无结果' %}
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+ {% include "partial/_footer.html" %}
+
+
+
+ {% comment %}
+ {% oauth_token %}
+ {% mastodon request.user.mastodon_site %}
+
+ {{ user.mastodon_id }}
+ {% endcomment %}
+
+
+
+
+
+
diff --git a/collection/urls.py b/collection/urls.py
index 99b8d9af..634a710b 100644
--- a/collection/urls.py
+++ b/collection/urls.py
@@ -4,12 +4,17 @@ from .views import *
app_name = 'collection'
urlpatterns = [
+ path('mine/', list, name='list'),
path('create/', create, name='create'),
path('/', retrieve, name='retrieve'),
path('update//', update, name='update'),
path('delete//', delete, name='delete'),
path('follow//', follow, name='follow'),
- path('unfollow//', follow, name='unfollow'),
+ path('unfollow//', unfollow, name='unfollow'),
+ path('/append_item/', append_item, name='append_item'),
+ path('/delete_item/', delete_item, name='delete_item'),
+ path('/move_up_item/', move_up_item, name='move_up_item'),
+ path('/move_down_item/', move_down_item, name='move_down_item'),
path('with///', list_with, name='list_with'),
# TODO: tag
]
diff --git a/collection/views.py b/collection/views.py
index 3dfc9729..6e403c22 100644
--- a/collection/views.py
+++ b/collection/views.py
@@ -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/')
+ logger.warning('non-GET method at /collections/')
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
diff --git a/common/models.py b/common/models.py
index 77d67704..247f6e04 100644
--- a/common/models.py
+++ b/common/models.py
@@ -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'])
diff --git a/users/templates/users/home.html b/users/templates/users/home.html
index 0f5e9eb2..b4b7d6bb 100644
--- a/users/templates/users/home.html
+++ b/users/templates/users/home.html
@@ -531,6 +531,38 @@
+
+
+ {% trans '创建的收藏单' %}
+
+
+ {{ collections_count }}
+
+ {% if collections_more %}
+
{% trans '更多' %}
+ {% endif %}
+ {% if user == request.user %}
+
{% trans '新建' %}
+ {% endif %}
+
+
+
+
{% if user == request.user %}
diff --git a/users/urls.py b/users/urls.py
index 8d8bc695..4b61fa32 100644
--- a/users/urls.py
+++ b/users/urls.py
@@ -20,6 +20,7 @@ urlpatterns = [
path('/', home, name='home'),
path('/followers/', followers, name='followers'),
path('/following/', following, name='following'),
+ path('/collections/', collection_list, name='collection_list'),
path('/book//', book_list, name='book_list'),
path('/movie//', movie_list, name='movie_list'),
path('/music//', music_list, name='music_list'),
@@ -27,6 +28,7 @@ urlpatterns = [
path('/', home, name='home'),
path('/followers/', followers, name='followers'),
path('/following/', following, name='following'),
+ path('/collections/', collection_list, name='collection_list'),
path('/book//', book_list, name='book_list'),
path('/movie//', movie_list, name='movie_list'),
path('/music//', music_list, name='music_list'),
diff --git a/users/views.py b/users/views.py
index 8b21d46f..0147e891 100644
--- a/users/views.py
+++ b/users/views.py
@@ -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):