new data model: tag pages

This commit is contained in:
Your Name 2022-12-27 14:52:03 -05:00
parent 41b6426ecb
commit f36ec360e3
29 changed files with 832 additions and 55 deletions

View file

@ -7,6 +7,7 @@ from .game.models import Game
from .podcast.models import Podcast
from .performance.models import Performance
from .collection.models import Collection as CatalogCollection
from django.contrib.contenttypes.models import ContentType
# class Exhibition(Item):
@ -25,3 +26,20 @@ from .collection.models import Collection as CatalogCollection
# class Meta:
# proxy = True
CATEGORY_LIST = {}
CONTENT_TYPE_LIST = {}
def _init_item_subclasses():
for cls in Item.__subclasses__():
c = getattr(cls, 'category', None)
if c not in CATEGORY_LIST:
CATEGORY_LIST[c] = [cls]
else:
CATEGORY_LIST[c].append(cls)
CONTENT_TYPE_LIST[cls] = ContentType.objects.get(app_label='catalog', model=cls.__name__.lower()).id
_init_item_subclasses()

View file

@ -136,6 +136,7 @@ class TMDB_Movie(AbstractSite):
@SiteManager.register
class TMDB_TV(AbstractSite):
SITE_NAME = SiteName.TMDB
ID_TYPE = IdType.TMDB_TV
URL_PATTERNS = [r'\w+://www.themoviedb.org/tv/(\d+)[^/]*$', r'\w+://www.themoviedb.org/tv/(\d+)[^/]*/seasons']
WIKI_PROPERTY_ID = '?'
@ -246,6 +247,7 @@ class TMDB_TV(AbstractSite):
@SiteManager.register
class TMDB_TVSeason(AbstractSite):
SITE_NAME = SiteName.TMDB
ID_TYPE = IdType.TMDB_TVSeason
URL_PATTERNS = [r'\w+://www.themoviedb.org/tv/(\d+)[^/]*/season/(\d+)[^/]*$']
WIKI_PROPERTY_ID = '?'

View file

@ -1,6 +1,6 @@
function catalog_init(context) {
// readonly star rating of detail display section
let ratingLabels = $("#main .rating-star", context);
let ratingLabels = $(".grid__main .rating-star", context);
$(ratingLabels).each( function(index, value) {
let ratingScore = $(this).data("rating-score") / 2;
$(this).starRating({

View file

@ -14,10 +14,10 @@
{% block details %}
<div class="entity-detail__fields">
<div class="entity-detail__rating">
{% if item.rating and item.rating_number >= 5 %}
{% if item.rating and item.rating_count >= 5 %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:"0" }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating }} </span>
<small>({{ item.rating_number }}人评分)</small>
<small>({{ item.rating_count }}人评分)</small>
{% else %}
<span> {% trans '评分:评分人数不足' %}</span>
{% endif %}

View file

@ -62,7 +62,7 @@
{% endif %}
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
{% if item.last_editor and item.last_editor.preference.show_last_edit %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -14,10 +14,10 @@
{% block details %}
<div class="entity-detail__fields">
<div class="entity-detail__rating">
{% if item.rating and item.rating_number >= 5 %}
{% if item.rating and item.rating_count >= 5 %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:"0" }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating }} </span>
<small>({{ item.rating_number }}人评分)</small>
<small>({{ item.rating_count }}人评分)</small>
{% else %}
<span> {% trans '评分:评分人数不足' %}</span>
{% endif %}
@ -91,7 +91,7 @@
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
{% if item.last_editor and item.last_editor.preference.show_last_edit %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -155,7 +155,7 @@
{% endif %}
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
{% if item.last_editor and item.last_editor.preference.show_last_edit %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' 'fixme' %}">{{ item.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -36,10 +36,10 @@
{% block details %}
<div class="entity-detail__fields">
<div class="entity-detail__rating">
{% if item.rating and item.rating_number >= 5 %}
{% if item.rating and item.rating_count >= 5 %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:"0" }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating }} </span>
<small>({{ item.rating_number }}人评分)</small>
<small>({{ item.rating_count }}人评分)</small>
{% else %}
<span> {% trans '评分:评分人数不足' %}</span>
{% endif %}
@ -155,7 +155,7 @@
{% endif %}
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
{% if item.last_editor and item.last_editor.preference.show_last_edit %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -36,10 +36,10 @@
{% block details %}
<div class="entity-detail__fields">
<div class="entity-detail__rating">
{% if item.rating and item.rating_number >= 5 %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:"0" }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating }} </span>
<small>({{ item.rating_number }}人评分)</small>
{% if item.rating and item.rating_count >= 5 %}
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:0 }}"></span>
<span class="entity-detail__rating-score"> {{ item.rating | floatformat:1 }} </span>
<small>({{ item.rating_count }}人评分)</small>
{% else %}
<span> {% trans '评分:评分人数不足' %}</span>
{% endif %}
@ -155,7 +155,7 @@
{% endif %}
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
{% if item.last_editor and item.last_editor.preference.show_last_edit %}
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
{% endif %}

View file

@ -17,7 +17,7 @@ def _get_all_url_paths():
urlpatterns = [
re_path(r'^item/(?P<item_uid>[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/?$', retrieve_by_uuid, name='retrieve_by_uuid'),
re_path(r'^item/(?P<item_uid>[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})?$', retrieve_by_uuid, name='retrieve_by_uuid'),
re_path(r'^(?P<item_path>' + _get_all_url_paths() + ')/(?P<item_uuid>[A-Za-z0-9]{21,22})$', retrieve, name='retrieve'),
re_path(r'^(?P<item_path>' + _get_all_url_paths() + ')/(?P<item_uuid>[A-Za-z0-9]{21,22})/reviews', review_list, name='review_list'),
re_path(r'^(?P<item_path>' + _get_all_url_paths() + ')/(?P<item_uuid>[A-Za-z0-9]{21,22})/marks(?:/(?P<following_only>\\w+))?', mark_list, name='mark_list'),

View file

@ -87,8 +87,8 @@
</h5>
<a href="{% url 'users:tag_list' user.mastodon_username %}">{% trans '更多' %}</a>
<div class="tag-collection" style="margin-left: 0;">
{% if tags %}
{% for t in tags %}
{% if top_tags %}
{% for t in top_tags %}
<span class="tag-collection__tag">
<a href="/users/{{ user.mastodon_username }}/tag/{{ t }}/">{{ t }}</a>
</span>

View file

@ -20,7 +20,9 @@ import uuid
from catalog.common.utils import DEFAULT_ITEM_COVER, item_cover_path
from django.utils.baseconv import base62
from django.db.models import Q
from catalog.models import *
import mistune
from django.contrib.contenttypes.models import ContentType
class VisibilityType(models.IntegerChoices):
@ -37,6 +39,16 @@ def query_following(user):
return Q(owner_id__in=user.following, visibility__lt=2) | Q(owner_id=user.id)
def query_item_category(item_category):
classes = CATEGORY_LIST[item_category]
# q = Q(item__instance_of=classes[0])
# for cls in classes[1:]:
# q = q | Q(instance_of=cls)
# return q
contenttype_ids = [CONTENT_TYPE_LIST[cls] for cls in classes]
return Q(item__polymorphic_ctype__in=sorted(contenttype_ids))
class Piece(PolymorphicModel, UserOwnedObjectMixin):
url_path = 'piece' # subclass must specify this
uid = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True)
@ -67,6 +79,12 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
class Content(Piece):
item = models.ForeignKey(Item, on_delete=models.PROTECT)
@cached_property
def mark(self):
m = Mark(self.owner, self.item)
m.review = self
return m
def __str__(self):
return f"{self.uuid}@{self.item}"
@ -271,14 +289,17 @@ class ListMember(Piece):
ListMember - List class's member class
It's an abstract class, subclass must add this:
_list = models.ForeignKey('ListClass', related_name='members', on_delete=models.CASCADE)
it starts with _ bc Django internally created OneToOne Field on Piece
https://docs.djangoproject.com/en/3.2/topics/db/models/#specifying-the-parent-link-field
parent = models.ForeignKey('List', related_name='members', on_delete=models.CASCADE)
"""
item = models.ForeignKey(Item, on_delete=models.PROTECT)
position = models.PositiveIntegerField()
@cached_property
def mark(self):
m = Mark(self.owner, self.item)
m.shelfmember = self
return m
class Meta:
abstract = True
@ -322,12 +343,6 @@ ShelfTypeNames = [
class ShelfMember(ListMember):
parent = models.ForeignKey('Shelf', related_name='members', on_delete=models.CASCADE)
@ cached_property
def mark(self):
m = Mark(self.owner, self.item)
m.shelfmember = self
return m
class Shelf(List):
class Meta:
@ -515,8 +530,8 @@ class TagManager:
@ staticmethod
def all_tags_for_user(user):
tags = user.tag_set.all().values('title').annotate(frequency=Count('members')).order_by('-frequency')
return sorted(list(map(lambda t: t['title'], tags)))
tags = user.tag_set.all().values('title').annotate(frequency=Count('members__id')).order_by('-frequency')
return list(map(lambda t: t['title'], tags))
@ staticmethod
def tag_item_by_user(item, user, tag_titles, default_visibility=0):

View file

@ -0,0 +1,21 @@
{% extends "list_item_base.html" %}
{% load i18n %}
{% load highlight %}
{% block info %}
{% if item.artist %}{% trans '艺术家' %}:
{% for artist in item.artist %}
<span>{{ artist }}</span>
{% if not forloop.last %} {% endif %}
{% endfor %}
{% endif %}
{% if item.genre %}/ {% trans '流派' %}:
{{ item.genre }}
{% endif %}
{% if item.release_date %}/ {% trans '发行日期' %}:
{{ item.release_date }}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,136 @@
{# parameters: item, collection_edit, hide_category #}
{% load thumb %}
{% load highlight %}
{% load i18n %}
{% load l10n %}
{% load user_actions %}
{% wish_item_action item as action %}
<li class="entity-list__entity">
<div class="entity-list__entity-img-wrapper">
<a href="{{ item.url }}">
<img src="{{ item.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img">
</a>
{% if not action.taken %}
<a class="entity-list__entity-action-icon" hx-post="{{ action.url }}" title="加入想读"></a>
{% endif %}
</div>
<div class="entity-list__entity-text">
{% if collection_edit %}
<div class="collection-item-position-edit">
{% if not forloop.first %}
<a hx-target=".entity-list" hx-post="{% url 'collection:move_up_item' form.instance.id collectionitem.id %}"></a>
{% endif %}
{% if not forloop.last %}
<a hx-target=".entity-list" hx-post="{% url 'collection:move_down_item' form.instance.id collectionitem.id %}"></a>
{% endif %}
<a hx-target=".entity-list" hx-post="{% url 'collection:delete_item' form.instance.id collectionitem.id %}"></a>
</div>
{% endif %}
<div class="entity-list__entity-title">
<a href="{{ item.url }}" class="entity-list__entity-link">
{% if request.GET.q %}
{{ item.title | highlight:request.GET.q }}
{% else %}
{{ item.title }}
{% endif %}
</a>
{% if not request.GET.c and not hide_category %}
<span class="entity-list__entity-category">[{{item.category.label}}]</span>
{% endif %}
{% for res in item.external_resources.all %}
<a href="{{ res.url }}">
<span class="source-label source-label__{{ res.site_name }}">{{ res.site_name.label }}</span>
</a>
{% endfor %}
</div>
{% if item.rating %}
<div class="rating-star entity-list__rating-star" data-rating-score="{{ item.rating | floatformat:0 }}"></div>
<span class="entity-list__rating-score rating-score">{{ item.rating | floatformat:1 }}</span>
{% else %}
<div class="entity-list__rating entity-list__rating--empty"> {% trans '暂无评分' %}</div>
{% endif %}
<span class="entity-list__entity-info">
{% block info %}
{% endblock %}
</span>
<span class="entity-list__entity-info entity-list__entity-info--full-length">
{% block info_full %}
{% endblock %}
</span>
<p class="entity-list__entity-brief">
{{ item.brief }}
</p>
<div class="tag-collection">
{% for tag_dict in item.tags %}
<span class="tag-collection__tag">
<a href="{% url 'common:search' %}?tag={{ tag_dict }}">{{ tag_dict }}</a>
</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" style="float:right;">
{% trans '标记于' %} {{ member.created_time }}
</span>
<p class="entity-marks__mark-content">&nbsp;
{% if mark.text %}
{{ mark.text }}
{% endif %}
</p>
</li>
{% if mark.review %}
<li class="entity-marks__mark">
<span class="entity-marks__mark-time" style="float:right;">
{% trans '评论于' %} {{ mark.review.created_time }}
</span>
<p class="entity-marks__mark-content">
<a href="{{ mark.review.url }}">{{ mark.review.title }}</a>
{% if mark.review.visibility > 0 %}
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
</svg></span>
{% endif %}
</p>
</li>
{% endif %}
</ul>
</div>
{% endif %}
{% if collectionitem %}
<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" hx-target="this" hx-swap="innerHTML">
{% include "show_item_comment.html" %}
</p>
</li>
</ul>
</div>
{% endif %}
</div>
</li>

View file

@ -0,0 +1,51 @@
{% extends "list_item_base.html" %}
{% load i18n %}
{% load highlight %}
{% block info %}
{% if item.pub_year %} /
{{ item.pub_year }}{% trans '年' %}{% if item.pub_month %}{{item.pub_month }}{% trans '月' %}{% endif %}
{% endif %}
{% if item.author %} /
{% for author in item.author %}
{% if request.GET.q %}
{{ author | highlight:request.GET.q }}
{% else %}
{{ author }}
{% endif %}
{% if not forloop.last %},{% endif %}
{% endfor %}
{% endif %}
{% if item.translator %} /
{% trans '翻译' %}:
{% for translator in item.translator %}
{% if request.GET.q %}
{{ translator | highlight:request.GET.q }}
{% else %}
{{ translator }}
{% endif %}
{% if not forloop.last %},{% endif %}
{% endfor %}
{% endif %}
{% if item.subtitle %} /
{% trans '副标题' %}:
{% if request.GET.q %}
{{ item.subtitle | highlight:request.GET.q }}
{% else %}
{{ item.subtitle }}
{% endif %}
{% endif %}
{% if item.orig_title %} /
{% trans '原名' %}:
{% if request.GET.q %}
{{ item.orig_title | highlight:request.GET.q }}
{% else %}
{{ item.orig_title }}
{% endif %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,32 @@
{% extends "list_item_base.html" %}
{% load i18n %}
{% load highlight %}
{% block info %}
{% if item.other_title %}{% trans '别名' %}:
{% for other_title in item.other_title %}
{{ other_title }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if item.developer %}{% trans '开发商' %}:
{% for developer in item.developer %}
{{ developer }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if item.genre %}{% trans '类型' %}:
{% for genre in item.genre %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% if item.platform %}{% trans '平台' %}:
{% for platform in item.platform %}
{{ platform }}{% if not forloop.last %} {% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,45 @@
{% extends "list_item_base.html" %}
{% load i18n %}
{% load highlight %}
{% block info %}
{% if item.director %}{% trans '导演' %}:
{% for director in item.director %}
{% if request.GET.q %}
{{ director | highlight:request.GET.q }}
{% else %}
{{ director }}
{% endif %}
{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if item.genre %}{% trans '类型' %}:
{% for genre in item.genre %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% endblock %}
{% block info_full %}
{% if item.actor %}{% trans '主演' %}:
{% for actor in item.actor %}
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
{% if request.GET.q %}
{{ actor | highlight:request.GET.q }}
{% else %}
{{ actor }}
{% endif %}
</span>
{% if forloop.counter <= 5 %}
{% if not forloop.counter == 5 and not forloop.last %} {% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,48 @@
{% extends "list_item_base.html" %}
{% load i18n %}
{% load highlight %}
{% block info %}
{% if item.director %}{% trans '导演' %}:
{% for director in item.director %}
{% if request.GET.q %}
{{ director | highlight:request.GET.q }}
{% else %}
{{ director }}
{% endif %}
{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if item.genre %}{% trans '类型' %}:
{% for genre in item.genre %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% endblock %}
{% block info_full %}
{% if item.actor %}{% trans '主演' %}:
{% for actor in item.actor %}
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
{% if request.GET.q %}
{{ actor | highlight:request.GET.q }}
{% else %}
{{ actor }}
{% endif %}
</span>
{% if forloop.counter <= 5 %}
{% if not forloop.counter == 5 and not forloop.last %} {% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% if item.show %}
<!-- {{ item.show }} -->
{% endif %}
{% endblock %}

View file

@ -0,0 +1,46 @@
{% extends "list_item_base.html" %}
{% load i18n %}
{% load highlight %}
{% block info %}
{% if item.director %}{% trans '导演' %}:
{% for director in item.director %}
{% if request.GET.q %}
{{ director | highlight:request.GET.q }}
{% else %}
{{ director }}
{% endif %}
{% if not forloop.last %},{% endif %}
{% endfor %}/
{% endif %}
{% if item.genre %}{% trans '类型' %}:
{% for genre in item.genre %}
{{ genre }}{% if not forloop.last %} {% endif %}
{% endfor %}/
{% endif %}
{% endblock %}
{% block info_full %}
{% if item.actor %}{% trans '主演' %}:
{% for actor in item.actor %}
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
{% if request.GET.q %}
{{ actor | highlight:request.GET.q }}
{% else %}
{{ actor }}
{% endif %}
</span>
{% if forloop.counter <= 5 %}
{% if not forloop.counter == 5 and not forloop.last %} {% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,92 @@
{% 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">
{% block title %}
<title>{{ site_name }} - {{ user.mastodon_username }}</title>
{% endblock %}
{% include "common_libs.html" with jquery=1 %}
</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">
{% block head %}
{{ user.mastodon_username }}
{% endblock %}
</h5>
</div>
<ul class="entity-list__entities">
{% for member in members %}
{% with "list_item_"|add:member.item.class_name|add:".html" as template %}
{% include template with item=member.item mark=member.mark hide_category=True %}
{% endwith %}
{% empty %}
<div>{% trans '无结果' %}</div>
{% endfor %}
</ul>
</div>
<div class="pagination">
{% if members.pagination.has_prev %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page=1" class="pagination__nav-link pagination__nav-link">&laquo;</a>
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ members.previous_page_number }}"
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">&lsaquo;</a>
{% endif %}
{% for page in members.pagination.page_range %}
{% if page == members.pagination.current_page %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ page }}" class="pagination__page-link pagination__page-link--current">{{ page }}</a>
{% else %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ page }}" class="pagination__page-link">{{ page }}</a>
{% endif %}
{% endfor %}
{% if members.pagination.has_next %}
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ members.next_page_number }}"
class="pagination__nav-link pagination__nav-link--left-margin">&rsaquo;</a>
<a href="?{% if request.GET.t %}t={{ request.GET.t }}&{% endif %}page={{ members.pagination.last_page }}" class="pagination__nav-link">&raquo;</a>
{% endif %}
</div>
</div>
</div>
{% include "partial/_sidebar.html" %}
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})
</script>
</body>
</html>

View file

@ -0,0 +1,10 @@
{% extends "user_item_list_base.html" %}
{% load i18n %}
{% block title%}
<title>{{ site_name }} - {{ user.mastodon_username }} - {% trans '标记' %}</title>
{% endblock %}
{% block head %}
{{ user.mastodon_username }} - {% trans '标记' %}
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends "user_item_list_base.html" %}
{% load i18n %}
{% block title%}
<title>{{ site_name }} - {{ user.mastodon_username }} - {% trans '评论' %}</title>
{% endblock %}
{% block head %}
{{ user.mastodon_username }} - {% trans '评论' %}
{% endblock %}

View file

@ -0,0 +1,65 @@
{% 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 }} - 我的标签</title>
{% include "common_libs.html" with jquery=1 %}
</head>
<body>
<div id="page-wrapper">
<div id="content-wrapper">
{% include "partial/_navbar.html" %}
<section id="content">
<div class="grid">
<div class="grid__main" id="main">
<div class="main-section-wrapper">
<div class="entity-reviews">
<div class="tag-collection entity-reviews__review-list">
<h5>{% trans '全部标签' %}</h5>
{% for v in tags %}
<span style="display: inline-block;margin: 4px;">
<span class="tag-collection__tag" style="display:inline;float:none;">
<a href="{% url 'journal:user_tag_member_list' user.mastodon_username v.title %}">{{ v.title }}</a>
</span>
<span class="entity-reviews__review-time">({{ v.total }})</span>
</span>
{% empty %}
{% trans '暂无标签' %}
{% endfor %}
<div class="clearfix" style="margin-bottom: 16px;"></div>
</div>
</div>
</div>
</div>
{% include "partial/_sidebar.html" %}
</div>
</section>
</div>
{% include "partial/_footer.html" %}
</div>
<script>
</script>
</body>
</html>

View file

@ -0,0 +1,10 @@
{% extends "user_item_list_base.html" %}
{% load i18n %}
{% block title%}
<title>{{ site_name }} - {{ user.mastodon_username }} - {% trans '标签' %}</title>
{% endblock %}
{% block head %}
{{ user.mastodon_username }} - {% trans '标签' %}
{% endblock %}

View file

@ -1,8 +1,20 @@
from django.urls import path, re_path
from .views import *
from catalog.models import *
app_name = 'journal'
def _get_all_categories():
res = "|".join(CATEGORY_LIST.keys())
return res
def _get_all_shelf_types():
return "|".join(ShelfType.values)
urlpatterns = [
path('wish/<str:item_uuid>', wish, name='wish'),
path('like/<str:piece_uuid>', like, name='like'),
@ -13,4 +25,12 @@ urlpatterns = [
path('review/create/<str:item_uuid>/', review_edit, name='review_create'),
path('review/edit/<str:item_uuid>/<str:review_uuid>', review_edit, name='review_edit'),
path('review/delete/<str:review_uuid>', review_delete, name='review_delete'),
re_path(r'^user/(?P<user_name>[A-Za-z0-0_\-.@]+)/(?P<shelf_type>' + _get_all_shelf_types() + ')/(?P<item_category>' + _get_all_categories() + ')/$', user_mark_list, name='user_mark_list'),
re_path(r'^user/(?P<user_name>[A-Za-z0-0_\-.@]+)/reviews/(?P<item_category>' + _get_all_categories() + ')/$', user_review_list, name='user_review_list'),
re_path(r'^user/(?P<user_name>[A-Za-z0-0_\-.@]+)/tags/(?P<tag_title>[^/]+)/$', user_tag_member_list, name='user_tag_member_list'),
re_path(r'^user/(?P<user_name>[A-Za-z0-0_\-.@]+)/collections/$', user_collection_list, name='user_collection_list'),
re_path(r'^user/(?P<user_name>[A-Za-z0-0_\-.@]+)/like/collections/$', user_liked_collection_list, name='user_liked_collection_list'),
re_path(r'^user/(?P<user_name>[A-Za-z0-0_\-.@]+)/tags/$', user_tag_list, name='user_tag_list'),
]

View file

@ -19,6 +19,7 @@ from management.models import Announcement
from django.utils.baseconv import base62
from .forms import *
from mastodon.api import share_review
from users.views import render_user_blocked, render_user_not_found
_logger = logging.getLogger(__name__)
@ -71,7 +72,7 @@ def add_to_collection(request, item_uuid):
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
def go_relogin(request):
def render_relogin(request):
return render(request, 'common/error.html', {
'url': reverse("users:connect") + '?domain=' + request.user.mastodon_site,
'msg': _("信息已保存,但是未能分享到联邦网络"),
@ -110,7 +111,7 @@ def mark(request, item_uuid):
try:
mark.update(status, text, rating, visibility, share_to_mastodon=share_to_mastodon)
except Exception:
go_relogin(request)
return render_relogin(request)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
@ -141,7 +142,7 @@ def review_edit(request, item_uuid, review_uuid=None):
form.instance.save = lambda **args: None
form.instance.shared_link = None
if not share_review(form.instance):
return go_relogin(request)
return render_relogin(request)
return redirect(reverse("journal:review_retrieve", args=[form.instance.uuid]))
else:
return HttpResponseBadRequest(form.errors)
@ -165,18 +166,115 @@ def review_delete(request, review_uuid):
return HttpResponseBadRequest()
def mark_list(request, shelf_type, item_category):
pass
def render_list_not_fount(request):
msg = _("相关列表不存在")
return render(
request,
'common/error.html',
{
'msg': msg,
}
)
def review_list(request):
pass
def collection_list(request):
pass
def _render_list(request, user_name, type, shelf_type=None, item_category=None, tag_title=None):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (request.user.is_blocked_by(user) or request.user.is_blocking(user)):
return render_user_blocked(request)
if type == 'mark':
shelf = user.shelf_manager.get_shelf(item_category, shelf_type)
queryset = ShelfMember.objects.filter(owner=user, parent=shelf)
elif type == 'tagmember':
tag = Tag.objects.filter(owner=user, title=tag_title).first()
if not tag:
return render_list_not_fount(request)
if tag.visibility != 0 and user != request.user:
return render_list_not_fount(request)
queryset = TagMember.objects.filter(parent=tag)
elif type == 'review':
queryset = Review.objects.filter(owner=user)
queryset = queryset.filter(query_item_category(item_category))
else:
return HttpResponseBadRequest()
if user != request.user:
if request.user.is_following(user):
queryset = queryset.filter(visibility__ne=2)
else:
queryset = queryset.filter(visibility=0)
paginator = Paginator(queryset, PAGE_SIZE)
page_number = request.GET.get('page', default=1)
members = paginator.get_page(page_number)
return render(request, f'user_{type}_list.html', {
'user': user,
'members': members,
})
@login_required
def liked_list(request):
pass
def user_mark_list(request, user_name, shelf_type, item_category):
return _render_list(request, user_name, 'mark', shelf_type=shelf_type, item_category=item_category)
@login_required
def user_tag_member_list(request, user_name, tag_title):
return _render_list(request, user_name, 'tagmember', tag_title=tag_title)
@login_required
def user_review_list(request, user_name, item_category):
return _render_list(request, user_name, 'review', item_category=item_category)
@login_required
def user_tag_list(request, user_name):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (request.user.is_blocked_by(user) or request.user.is_blocking(user)):
return render_user_blocked(request)
tags = Tag.objects.filter(owner=user)
tags = user.tag_set.all()
if user != request.user:
tags = tags.filter(visibility=0)
tags = tags.values('title').annotate(total=Count('members')).order_by('-total')
return render(request, f'user_tag_list.html', {
'user': user,
'tags': tags,
})
@login_required
def user_collection_list(request, user_name):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (request.user.is_blocked_by(user) or request.user.is_blocking(user)):
return render_user_blocked(request)
collections = Tag.objects.filter(owner=user)
if user != request.user:
if request.user.is_following(user):
collections = collections.filter(visibility__ne=2)
else:
collections = collections.filter(visibility=0)
return render(request, f'user_collection_list.html', {
'user': user,
'collections': collections,
})
@login_required
def user_liked_collection_list(request, user_name):
user = User.get(user_name)
if user is None:
return render_user_not_found(request)
if user != request.user and (request.user.is_blocked_by(user) or request.user.is_blocking(user)):
return render_user_blocked(request)
collections = Collection.objects.filter(likes__owner=user)
if user != request.user:
collections = collections.filter(query_visible(request.user))
return render(request, f'user_collection_list.html', {
'user': user,
'collections': collections,
})

View file

@ -0,0 +1,54 @@
{% load static %}
{% load i18n %}
{% load l10n %}
{% load admin_url %}
{% load mastodon %}
{% load oauth_token %}
{% load truncate %}
{% load thumb %}
{% load prettydate %}
{% load user_actions %}
{% wish_item_action activity.action_object.item as action %}
<div class="entity-list__entity-img-wrapper">
<a href="{{ activity.action_object.item.url }}">
<img src="{{ activity.action_object.item.cover|thumb:'normal' }}" alt="" class="entity-list__entity-img" style="min-width:80px;max-width:80px">
</a>
{% if not action.take %}
<a class="entity-list__entity-action-icon" hx-post="{{ action.url }}"></a>
{% endif %}
</div>
<div class="entity-list__entity-text">
<div class="collection-item-position-edit">
<span class="entity-marks__mark-time">
{% if activity.action_object.metadata.shared_link %}
<a href="{{ activity.action_object.metadata.shared_link }}" action_object="_blank">
<img src="{% static 'img/fediverse.svg' %}" style="filter: invert(93%) sepia(1%) saturate(53%) hue-rotate(314deg) brightness(95%) contrast(80%); vertical-align:text-top; max-width:14px; margin-right:6px;" />
<span class="entity-marks__mark-time">{{ activity.action_object.created_time|prettydate }}</span></a>
{% else %}
<a><span class="entity-marks__mark-time">{{ activity.action_object.created_time|prettydate }}</span></a>
{% endif %}
</span>
</div>
<span class="entity-list__entity-info" style="top:0px;">
<a href="{% url 'users:home' activity.owner.mastodon_username %}">{{ activity.owner.display_name }}</a> {% trans '评论了' %}
<a href="{{ activity.action_object.item.url }}">{{ activity.action_object.item.title }}
{% if activity.action_object.item.year %}<small style="font-weight: lighter">({{ activity.action_object.item.year }})</small>{% endif %}
</a>
</span>
<div class="entity-list__entity-title">
<a href="{{ activity.action_object.url }}" class="entity-list__entity-link" style="font-weight:bold;">{{ activity.action_object.title }}</a>
</div>
<p class="entity-list__entity-brief">
{% if activity.action_object.review %}
<a href="{{ activity.review.url }}">{{ activity.review.title }}</a>
{% endif %}
{% if activity.action_object.rating_grade %}
<span class="entity-marks__rating-star rating-star" data-rating-score="{{ activity.action_object.rating_grade }}" style=""></span>
{% endif %}
<p class="entity-marks__mark-content">
<!-- body -->
</p>
</p>
</div>

View file

@ -34,7 +34,7 @@ def feed(request):
request,
'feed.html',
{
'tags': user.tag_manager.all_tags[:10],
'top_tags': user.tag_manager.all_tags[:10],
'unread_announcements': unread,
}
)

View file

@ -56,6 +56,17 @@ def render_user_not_found(request):
)
def render_user_blocked(request):
msg = _("你没有访问TA主页的权限😥")
return render(
request,
'common/error.html',
{
'msg': msg,
}
)
def home_redirect(request, id):
try:
query_kwargs = {'pk': id}
@ -93,7 +104,7 @@ def home(request, id):
reports = Report.objects.order_by(
'-submitted_time').filter(is_read=False)
unread_announcements = Announcement.objects.filter(
pk__gt=request.user.read_announcement_index).order_by('-pk')
pk__gt=request.user.read_announcement_index).order_by('-pk')
try:
request.user.read_announcement_index = Announcement.objects.latest(
'pk').pk
@ -510,14 +521,7 @@ def music_list(request, id, status):
tag = request.GET.get('t', default='')
if not user == request.user:
if request.user.is_blocked_by(user) or request.user.is_blocking(user):
msg = _("你没有访问TA主页的权限😥")
return render(
request,
'common/error.html',
{
'msg': msg,
}
)
return render_user_blocked(request)
is_following = request.user.is_following(user)
if status == 'reviewed':
queryset = list(AlbumReview.get_available_by_user(user, is_following).order_by("-edited_time")) + \
@ -527,7 +531,7 @@ def music_list(request, id, status):
else:
queryset = list(AlbumMark.get_available_by_user(user, is_following).filter(
status=MarkStatusEnum[status.upper()])) \
+ list(SongMark.get_available_by_user(user, is_following).filter(
+ list(SongMark.get_available_by_user(user, is_following).filter(
status=MarkStatusEnum[status.upper()]))
else:
if status == 'reviewed':