diff --git a/books/models.py b/books/models.py index 020c7f16..5e0c2ec5 100644 --- a/books/models.py +++ b/books/models.py @@ -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( diff --git a/books/urls.py b/books/urls.py index 0dcd5061..0185888e 100644 --- a/books/urls.py +++ b/books/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('update//', update, name='update'), path('delete//', delete, name='delete'), path('mark/', create_update_mark, name='create_update_mark'), + path('wish//', wish, name='wish'), re_path('(?P[0-9]+)/mark/list/(?:(?P\\d+))?', retrieve_mark_list, name='retrieve_mark_list'), path('mark/delete//', delete_mark, name='delete_mark'), path('/review/create/', create_review, name='create_review'), diff --git a/books/views.py b/books/views.py index c316ce85..e4ab00a1 100644 --- a/books/views.py +++ b/books/views.py @@ -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): diff --git a/common/models.py b/common/models.py index 15035746..d7cb87bf 100644 --- a/common/models.py +++ b/common/models.py @@ -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 diff --git a/common/static/lib/css/neo.css b/common/static/lib/css/neo.css index c415b9a0..b12fa96c 100644 --- a/common/static/lib/css/neo.css +++ b/common/static/lib/css/neo.css @@ -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. */ diff --git a/common/templates/common/search_result.html b/common/templates/common/search_result.html index f7ff701b..3667fa9e 100644 --- a/common/templates/common/search_result.html +++ b/common/templates/common/search_result.html @@ -21,6 +21,7 @@ + @@ -222,7 +223,9 @@ {% endcomment %} diff --git a/common/templates/partial/list_item_book.html b/common/templates/partial/list_item_book.html index 863218a6..ecbbd387 100644 --- a/common/templates/partial/list_item_book.html +++ b/common/templates/partial/list_item_book.html @@ -2,11 +2,16 @@ {% load highlight %} {% load i18n %} {% load l10n %} +{% load neo %} +{% current_user_marked_item book as marked %}
  • + {% if not marked %} + + {% endif %}
    @@ -30,9 +35,9 @@ {% else %} {{ book.title }} {% endif %} - + - {% if not request.GET.c or not request.GET.c in categories %} + {% if not request.GET.c and not hide_category %} [{{book.verbose_category_name}}] {% endif %} @@ -104,7 +109,39 @@ {% endfor %}
    - + + {% if mark %} +
    +
    +
    + {% endif %} + {% if collectionitem %}
    diff --git a/common/templates/partial/list_item_game.html b/common/templates/partial/list_item_game.html index fa3ee041..469f0e68 100644 --- a/common/templates/partial/list_item_game.html +++ b/common/templates/partial/list_item_game.html @@ -2,11 +2,16 @@ {% load highlight %} {% load i18n %} {% load l10n %} +{% load neo %} +{% current_user_marked_item game as marked %}
  • + {% if not marked %} + + {% endif %}
    {% if editable %} @@ -30,7 +35,7 @@ {% endif %} - {% if not request.GET.c or not request.GET.c in categories %} + {% if not request.GET.c and not hide_category %} [{{item.verbose_category_name}}] {% endif %} @@ -83,7 +88,39 @@ {% endfor %}
    - + + {% if mark %} +
    +
    +
    + {% endif %} + {% if collectionitem %}
    diff --git a/common/templates/partial/list_item_movie.html b/common/templates/partial/list_item_movie.html index 35b2f667..ea2d22eb 100644 --- a/common/templates/partial/list_item_movie.html +++ b/common/templates/partial/list_item_movie.html @@ -3,11 +3,16 @@ {% load i18n %} {% load l10n %} {% load humanize %} +{% load neo %} +{% current_user_marked_item movie as marked %}
  • + {% if not marked %} + + {% endif %}
    {% if editable %} @@ -47,7 +52,7 @@ {% endif %} - {% if not request.GET.c or not request.GET.c in categories %} + {% if not request.GET.c and not hide_category %} [{{movie.verbose_category_name}}] {% endif %} @@ -108,7 +113,39 @@ {% endfor %}
    - + + {% if mark %} +
    +
    +
    + {% endif %} + {% if collectionitem %}
    diff --git a/common/templates/partial/list_item_music.html b/common/templates/partial/list_item_music.html index ef313d63..62dccd9e 100644 --- a/common/templates/partial/list_item_music.html +++ b/common/templates/partial/list_item_music.html @@ -2,6 +2,8 @@ {% load highlight %} {% load i18n %} {% load l10n %} +{% load neo %} +{% current_user_marked_item music as marked %}
  • @@ -9,12 +11,17 @@ + {% if not marked %} + + {% endif %} {% elif music.category_name|lower == 'song' %} + {% if not marked %} + + {% endif %} {% endif %} -
    {% 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 %} [{{music.verbose_category_name}}] {% endif %} @@ -107,7 +114,44 @@ {% endfor %}
    - + + {% if mark %} +
    +
    +
    + {% endif %} + {% if collectionitem %}
    diff --git a/common/templatetags/neo.py b/common/templatetags/neo.py new file mode 100644 index 00000000..cb57e5e4 --- /dev/null +++ b/common/templatetags/neo.py @@ -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) diff --git a/games/models.py b/games/models.py index d7dc6d91..3238f51f 100644 --- a/games/models.py +++ b/games/models.py @@ -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( diff --git a/games/urls.py b/games/urls.py index 2a2f5d49..29f37500 100644 --- a/games/urls.py +++ b/games/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('update//', update, name='update'), path('delete//', delete, name='delete'), path('mark/', create_update_mark, name='create_update_mark'), + path('wish//', wish, name='wish'), re_path('(?P[0-9]+)/mark/list/(?:(?P\\d+))?', retrieve_mark_list, name='retrieve_mark_list'), path('mark/delete//', delete_mark, name='delete_mark'), path('/review/create/', create_review, name='create_review'), diff --git a/games/views.py b/games/views.py index c5a73458..eefb40aa 100644 --- a/games/views.py +++ b/games/views.py @@ -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): diff --git a/movies/models.py b/movies/models.py index 274706af..2945d66a 100644 --- a/movies/models.py +++ b/movies/models.py @@ -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) diff --git a/movies/urls.py b/movies/urls.py index 76f315f4..d059cfbe 100644 --- a/movies/urls.py +++ b/movies/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('update//', update, name='update'), path('delete//', delete, name='delete'), path('mark/', create_update_mark, name='create_update_mark'), + path('wish//', wish, name='wish'), re_path('(?P[0-9]+)/mark/list/(?:(?P\\d+))?', retrieve_mark_list, name='retrieve_mark_list'), path('mark/delete//', delete_mark, name='delete_mark'), path('/review/create/', create_review, name='create_review'), diff --git a/movies/views.py b/movies/views.py index 830b1e05..5a4c4c25 100644 --- a/movies/views.py +++ b/movies/views.py @@ -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): diff --git a/music/models.py b/music/models.py index 28d545d0..f66743b8 100644 --- a/music/models.py +++ b/music/models.py @@ -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) diff --git a/music/urls.py b/music/urls.py index d0f0b7d0..d99248e2 100644 --- a/music/urls.py +++ b/music/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('song/update//', update_song, name='update_song'), path('song/delete//', delete_song, name='delete_song'), path('song/mark/', create_update_song_mark, name='create_update_song_mark'), + path('song/wish//', wish_song, name='wish_song'), path('song//mark/list/', retrieve_song_mark_list, name='retrieve_song_mark_list'), path('song/mark/delete//', delete_song_mark, name='delete_song_mark'), @@ -19,12 +20,13 @@ urlpatterns = [ re_path('song/(?P[0-9]+)/mark/list/(?:(?P\\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//', retrieve_album, name='retrieve_album'), path('album/update//', update_album, name='update_album'), path('album/delete//', delete_album, name='delete_album'), path('album/mark/', create_update_album_mark, name='create_update_album_mark'), + path('album/wish//', wish_album, name='wish_album'), re_path('album/(?P[0-9]+)/mark/list/(?:(?P\\d+))?', retrieve_album_mark_list, name='retrieve_album_mark_list'), path('album/mark/delete//', delete_album_mark, name='delete_album_mark'), path('album//review/create/', create_album_review, name='create_album_review'), diff --git a/music/views.py b/music/views.py index 1bdc3578..7429f203 100644 --- a/music/views.py +++ b/music/views.py @@ -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): diff --git a/users/models.py b/users/models.py index c320e83e..752c4ad5 100644 --- a/users/models.py +++ b/users/models.py @@ -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) diff --git a/users/templates/users/item_list.html b/users/templates/users/item_list.html new file mode 100644 index 00000000..25a0c00f --- /dev/null +++ b/users/templates/users/item_list.html @@ -0,0 +1,190 @@ +{% load static %} +{% load i18n %} +{% load l10n %} +{% load admin_url %} +{% load mastodon %} +{% load oauth_token %} +{% load truncate %} +{% load thumb %} + + + + + + + {{ site_name }} - {{ user.mastodon_username }} {{ list_title }} + + + + + + + + + + + + +
    +
    + {% include "partial/_navbar.html" %} + +
    +
    +
    +
    +
    + +
    +
    + {{ user.mastodon_username }} {{ list_title }} +
    +
    +
      + {% for mark in marks %} + {% include "partial/list_item.html" with item=mark.item hide_category=True %} + {% empty %} +
      {% trans '无结果' %}
      + {% endfor %} +
    +
    + +
    +
    + +
    +
    + +
    + +
    +
    + + + + + +
    +
    +
    + +
    +
    + {% trans '关注的人' %} +
    + {% trans '更多' %} + +
    + +
    +
    + {% trans '被他们关注' %} +
    + {% trans '更多' %} + +
    + +
    +
    +
    + +
    +
    +
    +
    + {% include "partial/_footer.html" %} +
    + + + + + + {% if user == request.user %} + + {% else %} + + {% endif %} + + + + + + + diff --git a/users/views.py b/users/views.py index 32314db0..146781ce 100644 --- a/users/views.py +++ b/users/views.py @@ -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,