new data model: view detail page
This commit is contained in:
parent
b2af6f3230
commit
47cd239e21
36 changed files with 4063 additions and 176 deletions
|
@ -51,7 +51,7 @@ class Edition(Item):
|
||||||
'pages',
|
'pages',
|
||||||
'contents',
|
'contents',
|
||||||
'series',
|
'series',
|
||||||
'producer',
|
'imprint',
|
||||||
]
|
]
|
||||||
subtitle = jsondata.CharField(null=True, blank=True, default=None)
|
subtitle = jsondata.CharField(null=True, blank=True, default=None)
|
||||||
orig_title = jsondata.CharField(null=True, blank=True, default=None)
|
orig_title = jsondata.CharField(null=True, blank=True, default=None)
|
||||||
|
@ -66,7 +66,7 @@ class Edition(Item):
|
||||||
series = jsondata.CharField(null=True, blank=True, default=None)
|
series = jsondata.CharField(null=True, blank=True, default=None)
|
||||||
contents = jsondata.CharField(null=True, blank=True, default=None)
|
contents = jsondata.CharField(null=True, blank=True, default=None)
|
||||||
price = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
price = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
||||||
producer = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
imprint = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isbn10(self):
|
def isbn10(self):
|
||||||
|
|
|
@ -129,7 +129,8 @@ class CharField(JSONFieldMixin, fields.CharField):
|
||||||
class DateField(JSONFieldMixin, fields.DateField):
|
class DateField(JSONFieldMixin, fields.DateField):
|
||||||
def to_json(self, value):
|
def to_json(self, value):
|
||||||
if value:
|
if value:
|
||||||
assert isinstance(value, (datetime, date))
|
if not isinstance(value, (datetime, date)):
|
||||||
|
value = dateparse.parse_date(value)
|
||||||
return value.strftime('%Y-%m-%d')
|
return value.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
def from_json(self, value):
|
def from_json(self, value):
|
||||||
|
@ -140,6 +141,8 @@ class DateField(JSONFieldMixin, fields.DateField):
|
||||||
class DateTimeField(JSONFieldMixin, fields.DateTimeField):
|
class DateTimeField(JSONFieldMixin, fields.DateTimeField):
|
||||||
def to_json(self, value):
|
def to_json(self, value):
|
||||||
if value:
|
if value:
|
||||||
|
if not isinstance(value, (datetime, date)):
|
||||||
|
value = dateparse.parse_date(value)
|
||||||
if not timezone.is_aware(value):
|
if not timezone.is_aware(value):
|
||||||
value = timezone.make_aware(value)
|
value = timezone.make_aware(value)
|
||||||
return value.isoformat()
|
return value.isoformat()
|
||||||
|
|
|
@ -20,7 +20,7 @@ class SiteName(models.TextChoices):
|
||||||
IMDB = 'imdb', _('IMDB')
|
IMDB = 'imdb', _('IMDB')
|
||||||
TMDB = 'tmdb', _('The Movie Database')
|
TMDB = 'tmdb', _('The Movie Database')
|
||||||
Bandcamp = 'bandcamp', _('Bandcamp')
|
Bandcamp = 'bandcamp', _('Bandcamp')
|
||||||
Spotify_Album = 'spotify', _('Spotify')
|
Spotify = 'spotify', _('Spotify')
|
||||||
IGDB = 'igdb', _('IGDB')
|
IGDB = 'igdb', _('IGDB')
|
||||||
Steam = 'steam', _('Steam')
|
Steam = 'steam', _('Steam')
|
||||||
Bangumi = 'bangumi', _('Bangumi')
|
Bangumi = 'bangumi', _('Bangumi')
|
||||||
|
@ -231,7 +231,7 @@ class Item(SoftDeleteMixin, PolymorphicModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
return f'/{self.url_path}/{self.url_id}'
|
return f'/{self.url_path}/{self.url_id}/'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def class_name(self):
|
def class_name(self):
|
||||||
|
@ -239,7 +239,7 @@ class Item(SoftDeleteMixin, PolymorphicModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_url(cls, url_or_b62):
|
def get_by_url(cls, url_or_b62):
|
||||||
b62 = url_or_b62.split('/')[-1]
|
b62 = url_or_b62.strip().split('/')[-2]
|
||||||
return cls.objects.get(uid=uuid.UUID(int=base62.decode(b62)))
|
return cls.objects.get(uid=uuid.UUID(int=base62.decode(b62)))
|
||||||
|
|
||||||
# def get_lookup_id(self, id_type: str) -> str:
|
# def get_lookup_id(self, id_type: str) -> str:
|
||||||
|
|
|
@ -1,10 +1,65 @@
|
||||||
from catalog.common import *
|
from catalog.common import *
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Game(Item):
|
class Game(Item):
|
||||||
category = ItemCategory.Game
|
category = ItemCategory.Game
|
||||||
url_path = 'game'
|
url_path = 'game'
|
||||||
|
demonstrative = _('这个游戏')
|
||||||
igdb = PrimaryLookupIdDescriptor(IdType.IGDB)
|
igdb = PrimaryLookupIdDescriptor(IdType.IGDB)
|
||||||
steam = PrimaryLookupIdDescriptor(IdType.Steam)
|
steam = PrimaryLookupIdDescriptor(IdType.Steam)
|
||||||
douban_game = PrimaryLookupIdDescriptor(IdType.DoubanGame)
|
douban_game = PrimaryLookupIdDescriptor(IdType.DoubanGame)
|
||||||
platforms = jsondata.ArrayField(default=list)
|
|
||||||
|
METADATA_COPY_LIST = [
|
||||||
|
'title',
|
||||||
|
'other_title',
|
||||||
|
'developer',
|
||||||
|
'publisher',
|
||||||
|
'release_date',
|
||||||
|
'genre',
|
||||||
|
'platform',
|
||||||
|
'brief',
|
||||||
|
]
|
||||||
|
|
||||||
|
other_title = jsondata.ArrayField(
|
||||||
|
models.CharField(blank=True, default='', max_length=500),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
|
||||||
|
developer = jsondata.ArrayField(
|
||||||
|
models.CharField(blank=True, default='', max_length=500),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
|
||||||
|
publisher = jsondata.ArrayField(
|
||||||
|
models.CharField(blank=True, default='', max_length=500),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
|
||||||
|
release_date = jsondata.DateField(
|
||||||
|
auto_now=False,
|
||||||
|
auto_now_add=False,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
genre = jsondata.ArrayField(
|
||||||
|
models.CharField(blank=True, default='', max_length=200),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
|
||||||
|
platform = jsondata.ArrayField(
|
||||||
|
models.CharField(blank=True, default='', max_length=200),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from .common.models import Item
|
||||||
from .book.models import Edition, Work, Series
|
from .book.models import Edition, Work, Series
|
||||||
from .movie.models import Movie
|
from .movie.models import Movie
|
||||||
from .tv.models import TVShow, TVSeason, TVEpisode
|
from .tv.models import TVShow, TVSeason, TVEpisode
|
||||||
|
|
|
@ -10,12 +10,12 @@ class Movie(Item):
|
||||||
tmdb_movie = PrimaryLookupIdDescriptor(IdType.TMDB_Movie)
|
tmdb_movie = PrimaryLookupIdDescriptor(IdType.TMDB_Movie)
|
||||||
douban_movie = PrimaryLookupIdDescriptor(IdType.DoubanMovie)
|
douban_movie = PrimaryLookupIdDescriptor(IdType.DoubanMovie)
|
||||||
duration = jsondata.IntegerField(blank=True, default=None)
|
duration = jsondata.IntegerField(blank=True, default=None)
|
||||||
|
demonstrative = _('这部电影')
|
||||||
|
|
||||||
METADATA_COPY_LIST = [
|
METADATA_COPY_LIST = [
|
||||||
'title',
|
'title',
|
||||||
'orig_title',
|
'orig_title',
|
||||||
'other_title',
|
'other_title',
|
||||||
'imdb_code',
|
|
||||||
'director',
|
'director',
|
||||||
'playwright',
|
'playwright',
|
||||||
'actor',
|
'actor',
|
||||||
|
@ -33,7 +33,6 @@ class Movie(Item):
|
||||||
]
|
]
|
||||||
orig_title = jsondata.CharField(_("original title"), blank=True, default='', max_length=500)
|
orig_title = jsondata.CharField(_("original title"), blank=True, default='', max_length=500)
|
||||||
other_title = jsondata.ArrayField(models.CharField(_("other title"), blank=True, default='', max_length=500), null=True, blank=True, default=list, )
|
other_title = jsondata.ArrayField(models.CharField(_("other title"), blank=True, default='', max_length=500), null=True, blank=True, default=list, )
|
||||||
imdb_code = jsondata.CharField(blank=True, max_length=10, null=False, db_index=True, default='')
|
|
||||||
director = jsondata.ArrayField(models.CharField(_("director"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
director = jsondata.ArrayField(models.CharField(_("director"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
playwright = jsondata.ArrayField(models.CharField(_("playwright"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
playwright = jsondata.ArrayField(models.CharField(_("playwright"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
actor = jsondata.ArrayField(models.CharField(_("actor"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
actor = jsondata.ArrayField(models.CharField(_("actor"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
|
|
@ -1,12 +1,36 @@
|
||||||
from catalog.common import *
|
from catalog.common import *
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class Album(Item):
|
class Album(Item):
|
||||||
url_path = 'album'
|
url_path = 'album'
|
||||||
category = ItemCategory.Music
|
category = ItemCategory.Music
|
||||||
|
demonstrative = _('这张专辑')
|
||||||
barcode = PrimaryLookupIdDescriptor(IdType.GTIN)
|
barcode = PrimaryLookupIdDescriptor(IdType.GTIN)
|
||||||
douban_music = PrimaryLookupIdDescriptor(IdType.DoubanMusic)
|
douban_music = PrimaryLookupIdDescriptor(IdType.DoubanMusic)
|
||||||
spotify_album = PrimaryLookupIdDescriptor(IdType.Spotify_Album)
|
spotify_album = PrimaryLookupIdDescriptor(IdType.Spotify_Album)
|
||||||
|
METADATA_COPY_LIST = [
|
||||||
class Meta:
|
'title',
|
||||||
proxy = True
|
'other_title',
|
||||||
|
'album_type',
|
||||||
|
'media',
|
||||||
|
'disc_count',
|
||||||
|
'artist',
|
||||||
|
'genre',
|
||||||
|
'release_date',
|
||||||
|
'duration',
|
||||||
|
'company',
|
||||||
|
'track_list',
|
||||||
|
'brief',
|
||||||
|
]
|
||||||
|
release_date = jsondata.DateField(_('发行日期'), auto_now=False, auto_now_add=False, null=True, blank=True)
|
||||||
|
duration = jsondata.IntegerField(_("时长"), null=True, blank=True)
|
||||||
|
artist = jsondata.ArrayField(models.CharField(_("artist"), blank=True, default='', max_length=200), null=True, blank=True, default=list)
|
||||||
|
genre = jsondata.CharField(_("流派"), blank=True, default='', max_length=100)
|
||||||
|
company = jsondata.ArrayField(models.CharField(blank=True, default='', max_length=500), null=True, blank=True, default=list)
|
||||||
|
track_list = jsondata.TextField(_("曲目"), blank=True, default="")
|
||||||
|
other_title = jsondata.CharField(blank=True, default='', max_length=500)
|
||||||
|
album_type = jsondata.CharField(blank=True, default='', max_length=500)
|
||||||
|
media = jsondata.CharField(blank=True, default='', max_length=500)
|
||||||
|
disc_count = jsondata.IntegerField(blank=True, default='', max_length=500)
|
||||||
|
|
|
@ -8,6 +8,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class ApplePodcast(AbstractSite):
|
class ApplePodcast(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.ApplePodcast
|
||||||
ID_TYPE = IdType.ApplePodcast
|
ID_TYPE = IdType.ApplePodcast
|
||||||
URL_PATTERNS = [r"https://[^.]+.apple.com/\w+/podcast/*[^/?]*/id(\d+)"]
|
URL_PATTERNS = [r"https://[^.]+.apple.com/\w+/podcast/*[^/?]*/id(\d+)"]
|
||||||
WIKI_PROPERTY_ID = 'P5842'
|
WIKI_PROPERTY_ID = 'P5842'
|
||||||
|
|
|
@ -8,6 +8,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class Bangumi(AbstractSite):
|
class Bangumi(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Bangumi
|
||||||
ID_TYPE = IdType.Bangumi
|
ID_TYPE = IdType.Bangumi
|
||||||
URL_PATTERNS = [
|
URL_PATTERNS = [
|
||||||
r"https://bgm\.tv/subject/(\d+)",
|
r"https://bgm\.tv/subject/(\d+)",
|
||||||
|
|
|
@ -140,7 +140,7 @@ class DoubanBook(AbstractSite):
|
||||||
|
|
||||||
imprint_elem = content.xpath(
|
imprint_elem = content.xpath(
|
||||||
"//div[@id='info']//span[text()='出品方:']/following-sibling::a[1]/text()")
|
"//div[@id='info']//span[text()='出品方:']/following-sibling::a[1]/text()")
|
||||||
producer = imprint_elem[0].strip() if imprint_elem else None
|
imprint = imprint_elem[0].strip() if imprint_elem else None
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'title': title,
|
'title': title,
|
||||||
|
@ -160,7 +160,7 @@ class DoubanBook(AbstractSite):
|
||||||
'brief': brief,
|
'brief': brief,
|
||||||
'contents': contents,
|
'contents': contents,
|
||||||
'series': series,
|
'series': series,
|
||||||
'producer': producer,
|
'imprint': imprint,
|
||||||
'cover_image_url': img_url,
|
'cover_image_url': img_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class DoubanDrama(AbstractSite):
|
class DoubanDrama(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Douban
|
||||||
ID_TYPE = IdType.DoubanDrama
|
ID_TYPE = IdType.DoubanDrama
|
||||||
URL_PATTERNS = [r"\w+://www.douban.com/location/drama/(\d+)/"]
|
URL_PATTERNS = [r"\w+://www.douban.com/location/drama/(\d+)/"]
|
||||||
WIKI_PROPERTY_ID = 'P6443'
|
WIKI_PROPERTY_ID = 'P6443'
|
||||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class DoubanGame(AbstractSite):
|
class DoubanGame(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Douban
|
||||||
ID_TYPE = IdType.DoubanGame
|
ID_TYPE = IdType.DoubanGame
|
||||||
URL_PATTERNS = [r"\w+://www\.douban\.com/game/(\d+)/{0,1}", r"\w+://m.douban.com/game/subject/(\d+)/{0,1}"]
|
URL_PATTERNS = [r"\w+://www\.douban\.com/game/(\d+)/{0,1}", r"\w+://m.douban.com/game/subject/(\d+)/{0,1}"]
|
||||||
WIKI_PROPERTY_ID = ''
|
WIKI_PROPERTY_ID = ''
|
||||||
|
|
|
@ -5,57 +5,15 @@ from catalog.tv.models import *
|
||||||
import logging
|
import logging
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from .tmdb import TMDB_TV, search_tmdb_by_imdb_id
|
from .tmdb import TMDB_TV, search_tmdb_by_imdb_id, query_tmdb_tv_episode
|
||||||
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MovieGenreEnum(models.TextChoices):
|
|
||||||
DRAMA = 'Drama', _('剧情')
|
|
||||||
KIDS = 'Kids', _('儿童')
|
|
||||||
COMEDY = 'Comedy', _('喜剧')
|
|
||||||
BIOGRAPHY = 'Biography', _('传记')
|
|
||||||
ACTION = 'Action', _('动作')
|
|
||||||
HISTORY = 'History', _('历史')
|
|
||||||
ROMANCE = 'Romance', _('爱情')
|
|
||||||
WAR = 'War', _('战争')
|
|
||||||
SCI_FI = 'Sci-Fi', _('科幻')
|
|
||||||
CRIME = 'Crime', _('犯罪')
|
|
||||||
ANIMATION = 'Animation', _('动画')
|
|
||||||
WESTERN = 'Western', _('西部')
|
|
||||||
MYSTERY = 'Mystery', _('悬疑')
|
|
||||||
FANTASY = 'Fantasy', _('奇幻')
|
|
||||||
THRILLER = 'Thriller', _('惊悚')
|
|
||||||
ADVENTURE = 'Adventure', _('冒险')
|
|
||||||
HORROR = 'Horror', _('恐怖')
|
|
||||||
DISASTER = 'Disaster', _('灾难')
|
|
||||||
DOCUMENTARY = 'Documentary', _('纪录片')
|
|
||||||
MARTIAL_ARTS = 'Martial-Arts', _('武侠')
|
|
||||||
SHORT = 'Short', _('短片')
|
|
||||||
ANCIENT_COSTUM = 'Ancient-Costum', _('古装')
|
|
||||||
EROTICA = 'Erotica', _('情色')
|
|
||||||
SPORT = 'Sport', _('运动')
|
|
||||||
GAY_LESBIAN = 'Gay/Lesbian', _('同性')
|
|
||||||
OPERA = 'Opera', _('戏曲')
|
|
||||||
MUSIC = 'Music', _('音乐')
|
|
||||||
FILM_NOIR = 'Film-Noir', _('黑色电影')
|
|
||||||
MUSICAL = 'Musical', _('歌舞')
|
|
||||||
REALITY_TV = 'Reality-TV', _('真人秀')
|
|
||||||
FAMILY = 'Family', _('家庭')
|
|
||||||
TALK_SHOW = 'Talk-Show', _('脱口秀')
|
|
||||||
NEWS = 'News', _('新闻')
|
|
||||||
SOAP = 'Soap', _('肥皂剧')
|
|
||||||
TV_MOVIE = 'TV Movie', _('电视电影')
|
|
||||||
THEATRE = 'Theatre', _('舞台艺术')
|
|
||||||
OTHER = 'Other', _('其他')
|
|
||||||
|
|
||||||
|
|
||||||
# MovieGenreTranslator = ChoicesDictGenerator(MovieGenreEnum)
|
|
||||||
|
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class DoubanMovie(AbstractSite):
|
class DoubanMovie(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Douban
|
||||||
ID_TYPE = IdType.DoubanMovie
|
ID_TYPE = IdType.DoubanMovie
|
||||||
URL_PATTERNS = [r"\w+://movie\.douban\.com/subject/(\d+)/{0,1}", r"\w+://m.douban.com/movie/subject/(\d+)/{0,1}"]
|
URL_PATTERNS = [r"\w+://movie\.douban\.com/subject/(\d+)/{0,1}", r"\w+://m.douban.com/movie/subject/(\d+)/{0,1}"]
|
||||||
WIKI_PROPERTY_ID = '?'
|
WIKI_PROPERTY_ID = '?'
|
||||||
|
@ -109,30 +67,16 @@ class DoubanMovie(AbstractSite):
|
||||||
"//div[@id='info']//span[text()='主演']/following-sibling::span[1]/a/text()")
|
"//div[@id='info']//span[text()='主演']/following-sibling::span[1]/a/text()")
|
||||||
actor = list(map(lambda a: a[:200], actor_elem)) if actor_elem else None
|
actor = list(map(lambda a: a[:200], actor_elem)) if actor_elem else None
|
||||||
|
|
||||||
# construct genre translator
|
|
||||||
genre_translator = {}
|
|
||||||
attrs = [attr for attr in dir(MovieGenreEnum) if '__' not in attr]
|
|
||||||
for attr in attrs:
|
|
||||||
genre_translator[getattr(MovieGenreEnum, attr).label] = getattr(
|
|
||||||
MovieGenreEnum, attr).value
|
|
||||||
|
|
||||||
genre_elem = content.xpath("//span[@property='v:genre']/text()")
|
genre_elem = content.xpath("//span[@property='v:genre']/text()")
|
||||||
|
genre = []
|
||||||
if genre_elem:
|
if genre_elem:
|
||||||
genre = []
|
|
||||||
for g in genre_elem:
|
for g in genre_elem:
|
||||||
g = g.split(' ')[0]
|
g = g.split(' ')[0]
|
||||||
if g == '紀錄片': # likely some original data on douban was corrupted
|
if g == '紀錄片': # likely some original data on douban was corrupted
|
||||||
g = '纪录片'
|
g = '纪录片'
|
||||||
elif g == '鬼怪':
|
elif g == '鬼怪':
|
||||||
g = '惊悚'
|
g = '惊悚'
|
||||||
if g in genre_translator:
|
genre.append(g)
|
||||||
genre.append(genre_translator[g])
|
|
||||||
elif g in genre_translator.values():
|
|
||||||
genre.append(g)
|
|
||||||
else:
|
|
||||||
_logger.error(f'unable to map genre {g}')
|
|
||||||
else:
|
|
||||||
genre = None
|
|
||||||
|
|
||||||
showtime_elem = content.xpath(
|
showtime_elem = content.xpath(
|
||||||
"//span[@property='v:initialReleaseDate']/text()")
|
"//span[@property='v:initialReleaseDate']/text()")
|
||||||
|
@ -230,7 +174,7 @@ class DoubanMovie(AbstractSite):
|
||||||
'year': year,
|
'year': year,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'season_number': season,
|
'season_number': season,
|
||||||
'episodes': episodes,
|
'episode_count': episodes,
|
||||||
'single_episode_length': single_episode_length,
|
'single_episode_length': single_episode_length,
|
||||||
'brief': brief,
|
'brief': brief,
|
||||||
'is_series': is_series,
|
'is_series': is_series,
|
||||||
|
@ -252,8 +196,11 @@ class DoubanMovie(AbstractSite):
|
||||||
pd.metadata['preferred_model'] = 'TVSeason'
|
pd.metadata['preferred_model'] = 'TVSeason'
|
||||||
tmdb_show_id = res_data['tv_episode_results'][0]['show_id']
|
tmdb_show_id = res_data['tv_episode_results'][0]['show_id']
|
||||||
if res_data['tv_episode_results'][0]['episode_number'] != 1:
|
if res_data['tv_episode_results'][0]['episode_number'] != 1:
|
||||||
_logger.error(f'Douban Movie {self.url} mapping to unexpected imdb episode {imdb_code}')
|
_logger.warning(f'Douban Movie {self.url} mapping to unexpected imdb episode {imdb_code}')
|
||||||
# TODO correct the IMDB id
|
resp = query_tmdb_tv_episode(tmdb_show_id, res_data['tv_episode_results'][0]['season_number'], 1)
|
||||||
|
imdb_code = resp['external_ids']['imdb_id']
|
||||||
|
_logger.warning(f'Douban Movie {self.url} re-mapped to imdb episode {imdb_code}')
|
||||||
|
|
||||||
pd.lookup_ids[IdType.IMDB] = imdb_code
|
pd.lookup_ids[IdType.IMDB] = imdb_code
|
||||||
if tmdb_show_id:
|
if tmdb_show_id:
|
||||||
pd.metadata['required_resources'] = [{
|
pd.metadata['required_resources'] = [{
|
||||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class DoubanMusic(AbstractSite):
|
class DoubanMusic(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Douban
|
||||||
ID_TYPE = IdType.DoubanMusic
|
ID_TYPE = IdType.DoubanMusic
|
||||||
URL_PATTERNS = [r"\w+://music\.douban\.com/subject/(\d+)/{0,1}", r"\w+://m.douban.com/music/subject/(\d+)/{0,1}"]
|
URL_PATTERNS = [r"\w+://music\.douban\.com/subject/(\d+)/{0,1}", r"\w+://m.douban.com/music/subject/(\d+)/{0,1}"]
|
||||||
WIKI_PROPERTY_ID = ''
|
WIKI_PROPERTY_ID = ''
|
||||||
|
@ -56,51 +57,48 @@ class DoubanMusic(AbstractSite):
|
||||||
brief = '\n'.join([e.strip() for e in brief_elem[0].xpath(
|
brief = '\n'.join([e.strip() for e in brief_elem[0].xpath(
|
||||||
'./text()')]) if brief_elem else None
|
'./text()')]) if brief_elem else None
|
||||||
|
|
||||||
gtin = None
|
|
||||||
isrc = None
|
|
||||||
other_info = {}
|
|
||||||
other_elem = content.xpath(
|
|
||||||
"//div[@id='info']//span[text()='又名:']/following-sibling::text()[1]")
|
|
||||||
if other_elem:
|
|
||||||
other_info['又名'] = other_elem[0].strip()
|
|
||||||
other_elem = content.xpath(
|
|
||||||
"//div[@id='info']//span[text()='专辑类型:']/following-sibling::text()[1]")
|
|
||||||
if other_elem:
|
|
||||||
other_info['专辑类型'] = other_elem[0].strip()
|
|
||||||
other_elem = content.xpath(
|
|
||||||
"//div[@id='info']//span[text()='介质:']/following-sibling::text()[1]")
|
|
||||||
if other_elem:
|
|
||||||
other_info['介质'] = other_elem[0].strip()
|
|
||||||
other_elem = content.xpath(
|
|
||||||
"//div[@id='info']//span[text()='ISRC:']/following-sibling::text()[1]")
|
|
||||||
if other_elem:
|
|
||||||
other_info['ISRC'] = other_elem[0].strip()
|
|
||||||
isrc = other_elem[0].strip()
|
|
||||||
other_elem = content.xpath(
|
|
||||||
"//div[@id='info']//span[text()='条形码:']/following-sibling::text()[1]")
|
|
||||||
if other_elem:
|
|
||||||
other_info['条形码'] = other_elem[0].strip()
|
|
||||||
gtin = other_elem[0].strip()
|
|
||||||
other_elem = content.xpath(
|
|
||||||
"//div[@id='info']//span[text()='碟片数:']/following-sibling::text()[1]")
|
|
||||||
if other_elem:
|
|
||||||
other_info['碟片数'] = other_elem[0].strip()
|
|
||||||
|
|
||||||
img_url_elem = content.xpath("//div[@id='mainpic']//img/@src")
|
img_url_elem = content.xpath("//div[@id='mainpic']//img/@src")
|
||||||
img_url = img_url_elem[0].strip() if img_url_elem else None
|
img_url = img_url_elem[0].strip() if img_url_elem else None
|
||||||
|
|
||||||
pd = ResourceContent(metadata={
|
data = {
|
||||||
'title': title,
|
'title': title,
|
||||||
'artist': artist,
|
'artist': artist,
|
||||||
'genre': genre,
|
'genre': genre,
|
||||||
'release_date': release_date,
|
'release_date': release_date,
|
||||||
'duration': None,
|
'duration': None,
|
||||||
'company': company,
|
'company': [company],
|
||||||
'track_list': track_list,
|
'track_list': track_list,
|
||||||
'brief': brief,
|
'brief': brief,
|
||||||
'other_info': other_info,
|
|
||||||
'cover_image_url': img_url
|
'cover_image_url': img_url
|
||||||
})
|
}
|
||||||
|
gtin = None
|
||||||
|
isrc = None
|
||||||
|
other_elem = content.xpath(
|
||||||
|
"//div[@id='info']//span[text()='又名:']/following-sibling::text()[1]")
|
||||||
|
if other_elem:
|
||||||
|
data['other_title'] = other_elem[0].strip()
|
||||||
|
other_elem = content.xpath(
|
||||||
|
"//div[@id='info']//span[text()='专辑类型:']/following-sibling::text()[1]")
|
||||||
|
if other_elem:
|
||||||
|
data['album_type'] = other_elem[0].strip()
|
||||||
|
other_elem = content.xpath(
|
||||||
|
"//div[@id='info']//span[text()='介质:']/following-sibling::text()[1]")
|
||||||
|
if other_elem:
|
||||||
|
data['media'] = other_elem[0].strip()
|
||||||
|
other_elem = content.xpath(
|
||||||
|
"//div[@id='info']//span[text()='ISRC:']/following-sibling::text()[1]")
|
||||||
|
if other_elem:
|
||||||
|
isrc = other_elem[0].strip()
|
||||||
|
other_elem = content.xpath(
|
||||||
|
"//div[@id='info']//span[text()='条形码:']/following-sibling::text()[1]")
|
||||||
|
if other_elem:
|
||||||
|
gtin = other_elem[0].strip()
|
||||||
|
other_elem = content.xpath(
|
||||||
|
"//div[@id='info']//span[text()='碟片数:']/following-sibling::text()[1]")
|
||||||
|
if other_elem:
|
||||||
|
data['disc_count'] = other_elem[0].strip()
|
||||||
|
|
||||||
|
pd = ResourceContent(metadata=data)
|
||||||
if gtin:
|
if gtin:
|
||||||
pd.lookup_ids[IdType.GTIN] = gtin
|
pd.lookup_ids[IdType.GTIN] = gtin
|
||||||
if isrc:
|
if isrc:
|
||||||
|
|
|
@ -39,6 +39,7 @@ def search_igdb_by_3p_url(steam_url):
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class IGDB(AbstractSite):
|
class IGDB(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.IGDB
|
||||||
ID_TYPE = IdType.IGDB
|
ID_TYPE = IdType.IGDB
|
||||||
URL_PATTERNS = [r"\w+://www\.igdb\.com/games/([a-zA-Z0-9\-_]+)"]
|
URL_PATTERNS = [r"\w+://www\.igdb\.com/games/([a-zA-Z0-9\-_]+)"]
|
||||||
WIKI_PROPERTY_ID = '?'
|
WIKI_PROPERTY_ID = '?'
|
||||||
|
@ -90,9 +91,9 @@ class IGDB(AbstractSite):
|
||||||
steam_url = website['url']
|
steam_url = website['url']
|
||||||
pd = ResourceContent(metadata={
|
pd = ResourceContent(metadata={
|
||||||
'title': r['name'],
|
'title': r['name'],
|
||||||
'other_title': None,
|
'other_title': [],
|
||||||
'developer': developer,
|
'developer': [developer],
|
||||||
'publisher': publisher,
|
'publisher': [publisher],
|
||||||
'release_date': release_date,
|
'release_date': release_date,
|
||||||
'genre': genre,
|
'genre': genre,
|
||||||
'platform': platform,
|
'platform': platform,
|
||||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class IMDB(AbstractSite):
|
class IMDB(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.IMDB
|
||||||
ID_TYPE = IdType.IMDB
|
ID_TYPE = IdType.IMDB
|
||||||
URL_PATTERNS = [r'\w+://www.imdb.com/title/(tt\d+)']
|
URL_PATTERNS = [r'\w+://www.imdb.com/title/(tt\d+)']
|
||||||
WIKI_PROPERTY_ID = '?'
|
WIKI_PROPERTY_ID = '?'
|
||||||
|
|
|
@ -21,6 +21,7 @@ spotify_token_expire_time = time.time()
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class Spotify(AbstractSite):
|
class Spotify(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Spotify
|
||||||
ID_TYPE = IdType.Spotify_Album
|
ID_TYPE = IdType.Spotify_Album
|
||||||
URL_PATTERNS = [r'\w+://open\.spotify\.com/album/([a-zA-Z0-9]+)']
|
URL_PATTERNS = [r'\w+://open\.spotify\.com/album/([a-zA-Z0-9]+)']
|
||||||
WIKI_PROPERTY_ID = '?'
|
WIKI_PROPERTY_ID = '?'
|
||||||
|
@ -106,7 +107,7 @@ def get_spotify_token():
|
||||||
invoke_spotify_token()
|
invoke_spotify_token()
|
||||||
return spotify_token
|
return spotify_token
|
||||||
|
|
||||||
|
|
||||||
def is_spotify_token_expired():
|
def is_spotify_token_expired():
|
||||||
global spotify_token_expire_time
|
global spotify_token_expire_time
|
||||||
return True if spotify_token_expire_time <= time.time() else False
|
return True if spotify_token_expire_time <= time.time() else False
|
||||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class Steam(AbstractSite):
|
class Steam(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.Steam
|
||||||
ID_TYPE = IdType.Steam
|
ID_TYPE = IdType.Steam
|
||||||
URL_PATTERNS = [r"\w+://store\.steampowered\.com/app/(\d+)"]
|
URL_PATTERNS = [r"\w+://store\.steampowered\.com/app/(\d+)"]
|
||||||
WIKI_PROPERTY_ID = '?'
|
WIKI_PROPERTY_ID = '?'
|
||||||
|
|
|
@ -20,6 +20,12 @@ def search_tmdb_by_imdb_id(imdb_id):
|
||||||
return res_data
|
return res_data
|
||||||
|
|
||||||
|
|
||||||
|
def query_tmdb_tv_episode(tv, season, episode):
|
||||||
|
tmdb_api_url = f"https://api.themoviedb.org/3/tv/{tv}/season/{season}/episode/{episode}?api_key={settings.TMDB_API3_KEY}&language=zh-CN&append_to_response=external_ids"
|
||||||
|
res_data = BasicDownloader(tmdb_api_url).download().json()
|
||||||
|
return res_data
|
||||||
|
|
||||||
|
|
||||||
def _copy_dict(s, key_map):
|
def _copy_dict(s, key_map):
|
||||||
d = {}
|
d = {}
|
||||||
for src, dst in key_map.items():
|
for src, dst in key_map.items():
|
||||||
|
@ -27,39 +33,9 @@ def _copy_dict(s, key_map):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
genre_map = {
|
|
||||||
'Sci-Fi & Fantasy': 'Sci-Fi',
|
|
||||||
'War & Politics': 'War',
|
|
||||||
'儿童': 'Kids',
|
|
||||||
'冒险': 'Adventure',
|
|
||||||
'剧情': 'Drama',
|
|
||||||
'动作': 'Action',
|
|
||||||
'动作冒险': 'Action',
|
|
||||||
'动画': 'Animation',
|
|
||||||
'历史': 'History',
|
|
||||||
'喜剧': 'Comedy',
|
|
||||||
'奇幻': 'Fantasy',
|
|
||||||
'家庭': 'Family',
|
|
||||||
'恐怖': 'Horror',
|
|
||||||
'悬疑': 'Mystery',
|
|
||||||
'惊悚': 'Thriller',
|
|
||||||
'战争': 'War',
|
|
||||||
'新闻': 'News',
|
|
||||||
'爱情': 'Romance',
|
|
||||||
'犯罪': 'Crime',
|
|
||||||
'电视电影': 'TV Movie',
|
|
||||||
'真人秀': 'Reality-TV',
|
|
||||||
'科幻': 'Sci-Fi',
|
|
||||||
'纪录': 'Documentary',
|
|
||||||
'肥皂剧': 'Soap',
|
|
||||||
'脱口秀': 'Talk-Show',
|
|
||||||
'西部': 'Western',
|
|
||||||
'音乐': 'Music',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SiteManager.register
|
@SiteManager.register
|
||||||
class TMDB_Movie(AbstractSite):
|
class TMDB_Movie(AbstractSite):
|
||||||
|
SITE_NAME = SiteName.TMDB
|
||||||
ID_TYPE = IdType.TMDB_Movie
|
ID_TYPE = IdType.TMDB_Movie
|
||||||
URL_PATTERNS = [r'\w+://www.themoviedb.org/movie/(\d+)']
|
URL_PATTERNS = [r'\w+://www.themoviedb.org/movie/(\d+)']
|
||||||
WIKI_PROPERTY_ID = '?'
|
WIKI_PROPERTY_ID = '?'
|
||||||
|
@ -98,8 +74,7 @@ class TMDB_Movie(AbstractSite):
|
||||||
# in minutes
|
# in minutes
|
||||||
duration = res_data['runtime'] if res_data['runtime'] else None
|
duration = res_data['runtime'] if res_data['runtime'] else None
|
||||||
|
|
||||||
genre = list(map(lambda x: genre_map[x['name']] if x['name']
|
genre = [x['name'] for x in res_data['genres']]
|
||||||
in genre_map else 'Other', res_data['genres']))
|
|
||||||
language = list(map(lambda x: x['name'], res_data['spoken_languages']))
|
language = list(map(lambda x: x['name'], res_data['spoken_languages']))
|
||||||
brief = res_data['overview']
|
brief = res_data['overview']
|
||||||
|
|
||||||
|
@ -199,8 +174,8 @@ class TMDB_TV(AbstractSite):
|
||||||
# in minutes
|
# in minutes
|
||||||
duration = res_data['runtime'] if res_data['runtime'] else None
|
duration = res_data['runtime'] if res_data['runtime'] else None
|
||||||
|
|
||||||
genre = list(map(lambda x: genre_map[x['name']] if x['name']
|
genre = [x['name'] for x in res_data['genres']]
|
||||||
in genre_map else 'Other', res_data['genres']))
|
|
||||||
language = list(map(lambda x: x['name'], res_data['spoken_languages']))
|
language = list(map(lambda x: x['name'], res_data['spoken_languages']))
|
||||||
brief = res_data['overview']
|
brief = res_data['overview']
|
||||||
|
|
||||||
|
@ -248,6 +223,7 @@ class TMDB_TV(AbstractSite):
|
||||||
'language': language,
|
'language': language,
|
||||||
'year': year,
|
'year': year,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
|
'season_count': res_data['number_of_seasons'],
|
||||||
'season': None,
|
'season': None,
|
||||||
'episodes': None,
|
'episodes': None,
|
||||||
'single_episode_length': None,
|
'single_episode_length': None,
|
||||||
|
@ -292,7 +268,7 @@ class TMDB_TVSeason(AbstractSite):
|
||||||
d = BasicDownloader(api_url).download().json()
|
d = BasicDownloader(api_url).download().json()
|
||||||
if not d.get('id'):
|
if not d.get('id'):
|
||||||
raise ParseError('id')
|
raise ParseError('id')
|
||||||
pd = ResourceContent(metadata=_copy_dict(d, {'name': 'title', 'overview': 'brief', 'air_date': 'air_date', 'season_number': 0, 'external_ids': 0}))
|
pd = ResourceContent(metadata=_copy_dict(d, {'name': 'title', 'overview': 'brief', 'air_date': 'air_date', 'season_number': 0, 'external_ids': []}))
|
||||||
pd.metadata['required_resources'] = [{
|
pd.metadata['required_resources'] = [{
|
||||||
'model': 'TVShow',
|
'model': 'TVShow',
|
||||||
'id_type': IdType.TMDB_TV,
|
'id_type': IdType.TMDB_TV,
|
||||||
|
|
|
@ -12,8 +12,106 @@
|
||||||
|
|
||||||
<!-- class specific details -->
|
<!-- class specific details -->
|
||||||
{% block details %}
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<span> {% trans '评分:评分人数不足' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.artist %}{% trans '艺术家:' %}
|
||||||
|
{% for artist in item.artist %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="artist">{{ artist }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.artist|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="artistMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#artistMore").on('click', function (e) {
|
||||||
|
$("span.artist:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.company %}{% trans '发行方:' %}
|
||||||
|
{% for company in item.company %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="company">{{ company }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.company|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="companyMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#companyMore").on('click', function (e) {
|
||||||
|
$("span.company:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.release_date %}
|
||||||
|
{% trans '发行日期:' %}{{ item.release_date }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.duration %}
|
||||||
|
{% trans '时长:' %}{{ item.get_duration_display }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.genre %}
|
||||||
|
{% trans '流派:' %}{{ item.genre }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.barcode %}
|
||||||
|
{% trans '条形码:' %}{{ item.barcode }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="entity-detail__fields">
|
||||||
|
<div>{% if item.other_title %}
|
||||||
|
{% trans '又名:' %}{{ item.other_title }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.album_type %}
|
||||||
|
{% trans '专辑类型:' %}{{ item.album_type }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.media %}
|
||||||
|
{% trans '介质:' %}{{ item.media }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.disc_count %}
|
||||||
|
{% trans '碟片数:' %}{{ item.disc_count }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
|
||||||
|
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'music:update_album' item.id %}">{% trans '编辑这个作品' %}</a>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
/<a href="{% url 'music:delete_album' item.id %}"> {% trans '删除' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific sidebar -->
|
<!-- class specific sidebar -->
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
|
{% if item.get_embed_link %}
|
||||||
|
<iframe src="{{ item.get_embed_link }}" height="320" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<div>{% if item.binding %}{% trans '装帧:' %}{{ item.binding }}{% endif %}</div>
|
<div>{% if item.binding %}{% trans '装帧:' %}{{ item.binding }}{% endif %}</div>
|
||||||
<div>{% if item.price %}{% trans '定价:' %}{{ item.price }}{% endif %}</div>
|
<div>{% if item.price %}{% trans '定价:' %}{{ item.price }}{% endif %}</div>
|
||||||
<div>{% if item.pages %}{% trans '页数:' %}{{ item.pages }}{% endif %}</div>
|
<div>{% if item.pages %}{% trans '页数:' %}{{ item.pages }}{% endif %}</div>
|
||||||
|
<div>{% if item.imprint %}{% trans '出品方:' %}{{ item.imprint }}{% endif %}</div>
|
||||||
{% if item.other_info %}
|
{% if item.other_info %}
|
||||||
{% for k, v in item.other_info.items %}
|
{% for k, v in item.other_info.items %}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -12,6 +12,100 @@
|
||||||
|
|
||||||
<!-- class specific details -->
|
<!-- class specific details -->
|
||||||
{% block details %}
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<span> {% trans '评分:评分人数不足' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>{% if item.other_title %}{% trans '别名:' %}
|
||||||
|
{% for other_title in item.other_title %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="other_title">{{ other_title }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.other_title|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="otherTitleMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#otherTitleMore").on('click', function (e) {
|
||||||
|
$("span.other_title:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if item.genre %}{% trans '类型:' %}
|
||||||
|
{% for genre in item.genre %}
|
||||||
|
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if item.developer %}{% trans '开发商:' %}
|
||||||
|
{% for developer in item.developer %}
|
||||||
|
<span>{{ developer }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if item.publisher %}{% trans '发行商:' %}
|
||||||
|
{% for publisher in item.publisher %}
|
||||||
|
<span>{{ publisher }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>{% if item.release_date %}
|
||||||
|
{% trans '发行日期:' %}{{ item.release_date }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if item.other_info %}
|
||||||
|
{% for k, v in item.other_info.items %}
|
||||||
|
<div>
|
||||||
|
{{ k }}:{{ v | urlize }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="entity-detail__fields">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if item.platform %}{% trans '平台:' %}
|
||||||
|
{% for platform in item.platform %}
|
||||||
|
<span>{{ platform }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
|
||||||
|
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'games:update' item.id %}">{% trans '编辑这个游戏' %}</a>
|
||||||
|
|
||||||
|
{% if user.is_staff %}
|
||||||
|
/<a href="{% url 'games:delete' item.id %}"> {% trans '删除' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific sidebar -->
|
<!-- class specific sidebar -->
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="entity-detail__info">
|
<div class="entity-detail__info">
|
||||||
|
{% block title %}
|
||||||
<h5 class="entity-detail__title">
|
<h5 class="entity-detail__title">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</h5>
|
</h5>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block details %}
|
{% block details %}
|
||||||
<div class="entity-detail__fields">
|
<div class="entity-detail__fields">
|
||||||
|
@ -112,6 +114,16 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if item.track_list %}
|
||||||
|
<div class="entity-desc" id="description">
|
||||||
|
<h5 class="entity-desc__title">{% trans '曲目' %}</h5>
|
||||||
|
<p class="entity-desc__content">{{ item.track_list | linebreaksbr }}</p>
|
||||||
|
<div class="entity-desc__unfold-button entity-desc__unfold-button--hidden">
|
||||||
|
<a href="javascript:void(0);">展开全部</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="entity-marks">
|
<div class="entity-marks">
|
||||||
<h5 class="entity-marks__title">{% trans '标记' %}</h5>
|
<h5 class="entity-marks__title">{% trans '标记' %}</h5>
|
||||||
<a href="{% url 'books:retrieve_mark_list' item.id %}" class="entity-marks__more-link">{% trans '全部标记' %}</a>
|
<a href="{% url 'books:retrieve_mark_list' item.id %}" class="entity-marks__more-link">{% trans '全部标记' %}</a>
|
||||||
|
@ -152,10 +164,10 @@
|
||||||
<div class="aside-section-wrapper">
|
<div class="aside-section-wrapper">
|
||||||
{% if mark.shelf_type %}
|
{% if mark.shelf_type %}
|
||||||
<div class="mark-panel">
|
<div class="mark-panel">
|
||||||
|
<span class="mark-panel__status">{% trans '我' %}{% trans mark.shelf_label %}</span>
|
||||||
{% if mark.rating %}
|
{% if mark.rating %}
|
||||||
<span class="mark-panel__rating-star rating-star" data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
|
<span class="mark-panel__rating-star rating-star" data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="mark-panel__status">{% trans '我' %}{% trans mark.shelf_label %}</span>
|
|
||||||
{% if mark.visibility > 0 %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
|
|
|
@ -10,8 +10,162 @@
|
||||||
{% load strip_scheme %}
|
{% load strip_scheme %}
|
||||||
{% load thumb %}
|
{% load thumb %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<h5 class="entity-detail__title">
|
||||||
|
{% if item.season_number %}
|
||||||
|
{{ item.title }} {% trans '第' %}{{ item.season_number|apnumber }}{% trans '季' %} {{ item.orig_title }} Season {{ item.season_number }}
|
||||||
|
<span class="entity-detail__title entity-detail__title--secondary">
|
||||||
|
{% if item.year %}({{ item.year }}){% endif %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
{{ item.title }} {{ item.orig_title }}
|
||||||
|
<span class="entity-detail__title entity-detail__title--secondary">
|
||||||
|
{% if item.year %}({{ item.year }}){% endif %}
|
||||||
|
</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 %}
|
||||||
|
</h5>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific details -->
|
<!-- class specific details -->
|
||||||
{% block details %}
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<span> {% trans '评分:评分人数不足' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.imdb %}
|
||||||
|
{% trans 'IMDb:' %}<a href="https://www.imdb.com/title/{{ item.imdb }}/" target="_blank">{{ item.imdb }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.director %}{% trans '导演:' %}
|
||||||
|
{% for director in item.director %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="director">{{ director }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.director|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="directorMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#directorMore").on('click', function (e) {
|
||||||
|
$("span.director:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.playwright %}{% trans '编剧:' %}
|
||||||
|
{% for playwright in item.playwright %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="playwright">{{ playwright }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.playwright|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="playwrightMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#playwrightMore").on('click', function (e) {
|
||||||
|
$("span.playwright:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.actor %}{% trans '主演:' %}
|
||||||
|
{% for actor in item.actor %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;"{% endif %}>
|
||||||
|
<span class="actor">{{ actor }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if item.actor|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="actorMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#actorMore").on('click', function(e) {
|
||||||
|
$("span.actor:not(:visible)").each(function(e){
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.genre %}{% trans '类型:' %}
|
||||||
|
{% for genre in item.genre %}
|
||||||
|
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.area %}{% trans '制片国家/地区:' %}
|
||||||
|
{% for area in item.area %}
|
||||||
|
<span>{{ area }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.language %}{% trans '语言:' %}
|
||||||
|
{% for language in item.language %}
|
||||||
|
<span>{{ language }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="entity-detail__fields">
|
||||||
|
<div>{% if item.duration %}{% trans '片长:' %}{{ item.duration }}{% endif %}</div>
|
||||||
|
<div>{% if item.season_count %}{% trans '季数:' %}{{ item.season_count }}{% endif %}</div>
|
||||||
|
<div>{% if item.episode_count %}{% trans '集数:' %}{{ item.episode_count }}{% endif %}</div>
|
||||||
|
<div>{% if item.single_episode_length %}{% trans '单集长度:' %}{{ item.single_episode_length }}{% endif %}</div>
|
||||||
|
|
||||||
|
<div>{% if item.showtime %}{% trans '上映时间:' %}
|
||||||
|
{% for showtime in item.showtime %}
|
||||||
|
{% for time, region in showtime.items %}
|
||||||
|
<span>{{ time }}{% if region != '' %}({{ region }}){% endif %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.other_title %}{% trans '又名:' %}
|
||||||
|
{% for other_title in item.other_title %}
|
||||||
|
<span>{{ other_title }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.site %}{% trans '网站:' %}
|
||||||
|
<a href="{{ item.site }}" target="_blank">{{ item.site|strip_scheme }}</a>
|
||||||
|
{% endif %}</div>
|
||||||
|
{% if item.other_info %}
|
||||||
|
{% for k, v in item.other_info.items %}
|
||||||
|
<div>
|
||||||
|
{{ k }}:{{ v | urlize }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
|
||||||
|
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'movies:update' item.id %}">{% trans '编辑这部电影' %}</a>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
/<a href="{% url 'movies:delete' item.id %}"> {% trans '删除' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific sidebar -->
|
<!-- class specific sidebar -->
|
||||||
|
|
|
@ -10,8 +10,162 @@
|
||||||
{% load strip_scheme %}
|
{% load strip_scheme %}
|
||||||
{% load thumb %}
|
{% load thumb %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<h5 class="entity-detail__title">
|
||||||
|
{% if item.season_number %}
|
||||||
|
{{ item.title }} {% trans '第' %}{{ item.season_number|apnumber }}{% trans '季' %} {{ item.orig_title }} Season {{ item.season_number }}
|
||||||
|
<span class="entity-detail__title entity-detail__title--secondary">
|
||||||
|
{% if item.year %}({{ item.year }}){% endif %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
{{ item.title }} {{ item.orig_title }}
|
||||||
|
<span class="entity-detail__title entity-detail__title--secondary">
|
||||||
|
{% if item.year %}({{ item.year }}){% endif %}
|
||||||
|
</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 %}
|
||||||
|
</h5>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific details -->
|
<!-- class specific details -->
|
||||||
{% block details %}
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<span> {% trans '评分:评分人数不足' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.imdb %}
|
||||||
|
{% trans 'IMDb:' %}<a href="https://www.imdb.com/title/{{ item.imdb }}/" target="_blank">{{ item.imdb }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.director %}{% trans '导演:' %}
|
||||||
|
{% for director in item.director %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="director">{{ director }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.director|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="directorMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#directorMore").on('click', function (e) {
|
||||||
|
$("span.director:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.playwright %}{% trans '编剧:' %}
|
||||||
|
{% for playwright in item.playwright %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="playwright">{{ playwright }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.playwright|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="playwrightMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#playwrightMore").on('click', function (e) {
|
||||||
|
$("span.playwright:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.actor %}{% trans '主演:' %}
|
||||||
|
{% for actor in item.actor %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;"{% endif %}>
|
||||||
|
<span class="actor">{{ actor }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if item.actor|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="actorMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#actorMore").on('click', function(e) {
|
||||||
|
$("span.actor:not(:visible)").each(function(e){
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.genre %}{% trans '类型:' %}
|
||||||
|
{% for genre in item.genre %}
|
||||||
|
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.area %}{% trans '制片国家/地区:' %}
|
||||||
|
{% for area in item.area %}
|
||||||
|
<span>{{ area }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.language %}{% trans '语言:' %}
|
||||||
|
{% for language in item.language %}
|
||||||
|
<span>{{ language }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="entity-detail__fields">
|
||||||
|
<div>{% if item.duration %}{% trans '片长:' %}{{ item.duration }}{% endif %}</div>
|
||||||
|
<div>{% if item.season_count %}{% trans '季数:' %}{{ item.season_count }}{% endif %}</div>
|
||||||
|
<div>{% if item.episode_count %}{% trans '集数:' %}{{ item.episode_count }}{% endif %}</div>
|
||||||
|
<div>{% if item.single_episode_length %}{% trans '单集长度:' %}{{ item.single_episode_length }}{% endif %}</div>
|
||||||
|
|
||||||
|
<div>{% if item.showtime %}{% trans '上映时间:' %}
|
||||||
|
{% for showtime in item.showtime %}
|
||||||
|
{% for time, region in showtime.items %}
|
||||||
|
<span>{{ time }}{% if region != '' %}({{ region }}){% endif %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.other_title %}{% trans '又名:' %}
|
||||||
|
{% for other_title in item.other_title %}
|
||||||
|
<span>{{ other_title }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.site %}{% trans '网站:' %}
|
||||||
|
<a href="{{ item.site }}" target="_blank">{{ item.site|strip_scheme }}</a>
|
||||||
|
{% endif %}</div>
|
||||||
|
{% if item.other_info %}
|
||||||
|
{% for k, v in item.other_info.items %}
|
||||||
|
<div>
|
||||||
|
{{ k }}:{{ v | urlize }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
|
||||||
|
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'movies:update' item.id %}">{% trans '编辑这部电影' %}</a>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
/<a href="{% url 'movies:delete' item.id %}"> {% trans '删除' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific sidebar -->
|
<!-- class specific sidebar -->
|
||||||
|
|
|
@ -10,8 +10,162 @@
|
||||||
{% load strip_scheme %}
|
{% load strip_scheme %}
|
||||||
{% load thumb %}
|
{% load thumb %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<h5 class="entity-detail__title">
|
||||||
|
{% if item.season_number %}
|
||||||
|
{{ item.title }} {% trans '第' %}{{ item.season_number|apnumber }}{% trans '季' %} {{ item.orig_title }} Season {{ item.season_number }}
|
||||||
|
<span class="entity-detail__title entity-detail__title--secondary">
|
||||||
|
{% if item.year %}({{ item.year }}){% endif %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
{{ item.title }} {{ item.orig_title }}
|
||||||
|
<span class="entity-detail__title entity-detail__title--secondary">
|
||||||
|
{% if item.year %}({{ item.year }}){% endif %}
|
||||||
|
</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 %}
|
||||||
|
</h5>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific details -->
|
<!-- class specific details -->
|
||||||
{% block details %}
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<span> {% trans '评分:评分人数不足' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.imdb %}
|
||||||
|
{% trans 'IMDb:' %}<a href="https://www.imdb.com/title/{{ item.imdb }}/" target="_blank">{{ item.imdb }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>{% if item.director %}{% trans '导演:' %}
|
||||||
|
{% for director in item.director %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="director">{{ director }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.director|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="directorMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#directorMore").on('click', function (e) {
|
||||||
|
$("span.director:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.playwright %}{% trans '编剧:' %}
|
||||||
|
{% for playwright in item.playwright %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;" {% endif %}>
|
||||||
|
<span class="playwright">{{ playwright }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if item.playwright|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="playwrightMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#playwrightMore").on('click', function (e) {
|
||||||
|
$("span.playwright:not(:visible)").each(function (e) {
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.actor %}{% trans '主演:' %}
|
||||||
|
{% for actor in item.actor %}
|
||||||
|
<span {% if forloop.counter > 5 %}style="display: none;"{% endif %}>
|
||||||
|
<span class="actor">{{ actor }}</span>
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if item.actor|length > 5 %}
|
||||||
|
<a href="javascript:void(0);" id="actorMore">{% trans '更多' %}</a>
|
||||||
|
<script>
|
||||||
|
$("#actorMore").on('click', function(e) {
|
||||||
|
$("span.actor:not(:visible)").each(function(e){
|
||||||
|
$(this).parent().removeAttr('style');
|
||||||
|
});
|
||||||
|
$(this).remove();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.genre %}{% trans '类型:' %}
|
||||||
|
{% for genre in item.genre %}
|
||||||
|
<span>{{ genre }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.area %}{% trans '制片国家/地区:' %}
|
||||||
|
{% for area in item.area %}
|
||||||
|
<span>{{ area }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.language %}{% trans '语言:' %}
|
||||||
|
{% for language in item.language %}
|
||||||
|
<span>{{ language }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="entity-detail__fields">
|
||||||
|
<div>{% if item.duration %}{% trans '片长:' %}{{ item.duration }}{% endif %}</div>
|
||||||
|
<div>{% if item.season_count %}{% trans '季数:' %}{{ item.season_count }}{% endif %}</div>
|
||||||
|
<div>{% if item.episode_count %}{% trans '集数:' %}{{ item.episode_count }}{% endif %}</div>
|
||||||
|
<div>{% if item.single_episode_length %}{% trans '单集长度:' %}{{ item.single_episode_length }}{% endif %}</div>
|
||||||
|
|
||||||
|
<div>{% if item.showtime %}{% trans '上映时间:' %}
|
||||||
|
{% for showtime in item.showtime %}
|
||||||
|
{% for time, region in showtime.items %}
|
||||||
|
<span>{{ time }}{% if region != '' %}({{ region }}){% endif %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.other_title %}{% trans '又名:' %}
|
||||||
|
{% for other_title in item.other_title %}
|
||||||
|
<span>{{ other_title }}</span>{% if not forloop.last %} / {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}</div>
|
||||||
|
<div>{% if item.site %}{% trans '网站:' %}
|
||||||
|
<a href="{{ item.site }}" target="_blank">{{ item.site|strip_scheme }}</a>
|
||||||
|
{% endif %}</div>
|
||||||
|
{% if item.other_info %}
|
||||||
|
{% for k, v in item.other_info.items %}
|
||||||
|
<div>
|
||||||
|
{{ k }}:{{ v | urlize }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if item.last_editor and item.last_editor.preference.show_last_edit or user.is_staff %}
|
||||||
|
<div>{% trans '最近编辑者:' %}<a href="{% url 'users:home' item.last_editor.mastodon_username %}">{{ item.last_editor | default:"" }}</a></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'movies:update' item.id %}">{% trans '编辑这部电影' %}</a>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
/<a href="{% url 'movies:delete' item.id %}"> {% trans '删除' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- class specific sidebar -->
|
<!-- class specific sidebar -->
|
||||||
|
|
|
@ -26,27 +26,96 @@ For now, we follow Douban convention, but keep an eye on it in case it breaks it
|
||||||
"""
|
"""
|
||||||
from catalog.common import *
|
from catalog.common import *
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class TVShow(Item):
|
class TVShow(Item):
|
||||||
category = ItemCategory.TV
|
category = ItemCategory.TV
|
||||||
url_path = 'tv'
|
url_path = 'tv'
|
||||||
|
demonstrative = _('这部剧集')
|
||||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||||
tmdb_tv = PrimaryLookupIdDescriptor(IdType.TMDB_TV)
|
tmdb_tv = PrimaryLookupIdDescriptor(IdType.TMDB_TV)
|
||||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||||
season_count = jsondata.IntegerField(blank=True, default=None)
|
season_count = jsondata.IntegerField(null=True)
|
||||||
|
|
||||||
|
METADATA_COPY_LIST = [
|
||||||
|
'title',
|
||||||
|
'season_count',
|
||||||
|
'orig_title',
|
||||||
|
'other_title',
|
||||||
|
'director',
|
||||||
|
'playwright',
|
||||||
|
'actor',
|
||||||
|
'genre',
|
||||||
|
'showtime',
|
||||||
|
'site',
|
||||||
|
'area',
|
||||||
|
'language',
|
||||||
|
'year',
|
||||||
|
'duration',
|
||||||
|
'season_number',
|
||||||
|
'single_episode_length',
|
||||||
|
'brief',
|
||||||
|
]
|
||||||
|
orig_title = jsondata.CharField(_("original title"), blank=True, default='', max_length=500)
|
||||||
|
other_title = jsondata.ArrayField(models.CharField(_("other title"), blank=True, default='', max_length=500), null=True, blank=True, default=list, )
|
||||||
|
director = jsondata.ArrayField(models.CharField(_("director"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
playwright = jsondata.ArrayField(models.CharField(_("playwright"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
actor = jsondata.ArrayField(models.CharField(_("actor"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
genre = jsondata.ArrayField(models.CharField(_("genre"), blank=True, default='', max_length=50), null=True, blank=True, default=list, ) # , choices=MovieGenreEnum.choices
|
||||||
|
showtime = jsondata.ArrayField(null=True, blank=True, default=list, )
|
||||||
|
site = jsondata.URLField(_('site url'), blank=True, default='', max_length=200)
|
||||||
|
area = jsondata.ArrayField(models.CharField(_("country or region"), blank=True, default='', max_length=100, ), null=True, blank=True, default=list, )
|
||||||
|
language = jsondata.ArrayField(models.CharField(blank=True, default='', max_length=100, ), null=True, blank=True, default=list, )
|
||||||
|
year = jsondata.IntegerField(null=True, blank=True)
|
||||||
|
season_number = jsondata.IntegerField(null=True, blank=True)
|
||||||
|
single_episode_length = jsondata.IntegerField(null=True, blank=True)
|
||||||
|
duration = jsondata.CharField(blank=True, default='', max_length=200)
|
||||||
|
|
||||||
|
|
||||||
class TVSeason(Item):
|
class TVSeason(Item):
|
||||||
category = ItemCategory.TV
|
category = ItemCategory.TV
|
||||||
url_path = 'tv/season'
|
url_path = 'tv/season'
|
||||||
|
demonstrative = _('这部剧集')
|
||||||
douban_movie = PrimaryLookupIdDescriptor(IdType.DoubanMovie)
|
douban_movie = PrimaryLookupIdDescriptor(IdType.DoubanMovie)
|
||||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||||
tmdb_tvseason = PrimaryLookupIdDescriptor(IdType.TMDB_TVSeason)
|
tmdb_tvseason = PrimaryLookupIdDescriptor(IdType.TMDB_TVSeason)
|
||||||
show = models.ForeignKey(TVShow, null=True, on_delete=models.SET_NULL, related_name='seasons')
|
show = models.ForeignKey(TVShow, null=True, on_delete=models.SET_NULL, related_name='seasons')
|
||||||
season_number = models.PositiveIntegerField()
|
season_number = models.PositiveIntegerField(null=True)
|
||||||
episode_count = jsondata.IntegerField(blank=True, default=None)
|
episode_count = models.PositiveIntegerField(null=True)
|
||||||
METADATA_COPY_LIST = ['title', 'brief', 'season_number', 'episode_count']
|
|
||||||
|
METADATA_COPY_LIST = [
|
||||||
|
'title',
|
||||||
|
'orig_title',
|
||||||
|
'other_title',
|
||||||
|
'director',
|
||||||
|
'playwright',
|
||||||
|
'actor',
|
||||||
|
'genre',
|
||||||
|
'showtime',
|
||||||
|
'site',
|
||||||
|
'area',
|
||||||
|
'language',
|
||||||
|
'year',
|
||||||
|
'duration',
|
||||||
|
'season_number',
|
||||||
|
'episode_count',
|
||||||
|
'single_episode_length',
|
||||||
|
'brief',
|
||||||
|
]
|
||||||
|
orig_title = jsondata.CharField(_("original title"), blank=True, default='', max_length=500)
|
||||||
|
other_title = jsondata.ArrayField(models.CharField(_("other title"), blank=True, default='', max_length=500), null=True, blank=True, default=list, )
|
||||||
|
director = jsondata.ArrayField(models.CharField(_("director"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
playwright = jsondata.ArrayField(models.CharField(_("playwright"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
actor = jsondata.ArrayField(models.CharField(_("actor"), blank=True, default='', max_length=200), null=True, blank=True, default=list, )
|
||||||
|
genre = jsondata.ArrayField(models.CharField(_("genre"), blank=True, default='', max_length=50), null=True, blank=True, default=list, ) # , choices=MovieGenreEnum.choices
|
||||||
|
showtime = jsondata.ArrayField(null=True, blank=True, default=list, )
|
||||||
|
site = jsondata.URLField(_('site url'), blank=True, default='', max_length=200)
|
||||||
|
area = jsondata.ArrayField(models.CharField(_("country or region"), blank=True, default='', max_length=100, ), null=True, blank=True, default=list, )
|
||||||
|
language = jsondata.ArrayField(models.CharField(blank=True, default='', max_length=100, ), null=True, blank=True, default=list, )
|
||||||
|
year = jsondata.IntegerField(null=True, blank=True)
|
||||||
|
single_episode_length = jsondata.IntegerField(null=True, blank=True)
|
||||||
|
duration = jsondata.CharField(blank=True, default='', max_length=200)
|
||||||
|
|
||||||
def update_linked_items_from_external_resource(self, resource):
|
def update_linked_items_from_external_resource(self, resource):
|
||||||
"""add Work from resource.metadata['work'] if not yet"""
|
"""add Work from resource.metadata['work'] if not yet"""
|
||||||
|
@ -63,6 +132,6 @@ class TVEpisode(Item):
|
||||||
url_path = 'tv/episode'
|
url_path = 'tv/episode'
|
||||||
show = models.ForeignKey(TVShow, null=True, on_delete=models.SET_NULL, related_name='episodes')
|
show = models.ForeignKey(TVShow, null=True, on_delete=models.SET_NULL, related_name='episodes')
|
||||||
season = models.ForeignKey(TVSeason, null=True, on_delete=models.SET_NULL, related_name='episodes')
|
season = models.ForeignKey(TVSeason, null=True, on_delete=models.SET_NULL, related_name='episodes')
|
||||||
episode_number = models.PositiveIntegerField()
|
episode_number = models.PositiveIntegerField(null=True)
|
||||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||||
METADATA_COPY_LIST = ['title', 'brief', 'episode_number']
|
METADATA_COPY_LIST = ['title', 'brief', 'episode_number']
|
||||||
|
|
|
@ -79,6 +79,13 @@ class DoubanMovieTVTestCase(TestCase):
|
||||||
p3 = SiteManager.get_site_by_url(url3).get_resource_ready()
|
p3 = SiteManager.get_site_by_url(url3).get_resource_ready()
|
||||||
self.assertEqual(p3.item.__class__.__name__, 'TVShow')
|
self.assertEqual(p3.item.__class__.__name__, 'TVShow')
|
||||||
|
|
||||||
|
@use_local_response
|
||||||
|
def test_scrape_fix_imdb(self):
|
||||||
|
url = 'https://movie.douban.com/subject/35597581/'
|
||||||
|
item = SiteManager.get_site_by_url(url).get_resource_ready().item
|
||||||
|
# this douban links to S6E3, we'll reset it to S6E1 to keep consistant
|
||||||
|
self.assertEqual(item.imdb, 'tt21599650')
|
||||||
|
|
||||||
|
|
||||||
class MultiTVSitesTestCase(TestCase):
|
class MultiTVSitesTestCase(TestCase):
|
||||||
@use_local_response
|
@use_local_response
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from .api import api
|
from .api import api
|
||||||
from .views import *
|
from .views import *
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
|
def _get_all_url_paths():
|
||||||
|
paths = ['item']
|
||||||
|
for cls in Item.__subclasses__():
|
||||||
|
p = getattr(cls, 'url_path', None)
|
||||||
|
if p:
|
||||||
|
paths.append(p)
|
||||||
|
res = "|".join(paths)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", api.urls),
|
re_path(r'(?P<item_path>' + _get_all_url_paths() + ')/(?P<item_uid>[A-Za-z0-9]{21,22})/', retrieve, name='retrieve'),
|
||||||
re_path('book/(?P<uid>[A-Za-z0-9]{21,22})/', retrieve, name='retrieve'),
|
path("api/", api.urls),
|
||||||
]
|
]
|
||||||
|
|
|
@ -27,9 +27,12 @@ from journal.models import Mark
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def retrieve(request, uid):
|
def retrieve(request, item_path, item_uid):
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
item = get_object_or_404(Edition, uid=base62.decode(uid))
|
item = get_object_or_404(Item, uid=base62.decode(item_uid))
|
||||||
|
item_url = f'/{item_path}/{item_uid}/'
|
||||||
|
if item.url != item_url:
|
||||||
|
return redirect(item.url)
|
||||||
mark = None
|
mark = None
|
||||||
review = None
|
review = None
|
||||||
mark_list = None
|
mark_list = None
|
||||||
|
|
|
@ -327,23 +327,33 @@ class ShelfManager:
|
||||||
# metadata=None means no change
|
# metadata=None means no change
|
||||||
if not item:
|
if not item:
|
||||||
raise ValueError('empty item')
|
raise ValueError('empty item')
|
||||||
lastqm = self._shelf_member_for_item(item)
|
last_shelfmember = self._shelf_member_for_item(item)
|
||||||
lastqmm = lastqm.metadata if lastqm else None
|
last_shelf = last_shelfmember._shelf if last_shelfmember else None
|
||||||
lastq = lastqm._shelf if lastqm else None
|
last_metadata = last_shelfmember.metadata if last_shelfmember else None
|
||||||
lastqt = lastq.shelf_type if lastq else None
|
last_visibility = last_shelfmember.visibility if last_shelfmember else None
|
||||||
shelf = self.get_shelf(item.category, shelf_type) if shelf_type else None
|
shelf = self.get_shelf(item.category, shelf_type) if shelf_type else None
|
||||||
if lastq != shelf:
|
changed = False
|
||||||
if lastq:
|
if last_shelf != shelf: # change shelf
|
||||||
lastq.remove_item(item)
|
changed = True
|
||||||
|
if last_shelf:
|
||||||
|
last_shelf.remove_item(item)
|
||||||
if shelf:
|
if shelf:
|
||||||
shelf.append_item(item, visibility=visibility, metadata=metadata or {})
|
shelf.append_item(item, visibility=visibility, metadata=metadata or {})
|
||||||
elif metadata is not None:
|
elif last_shelf is None:
|
||||||
lastqm.metadata = metadata
|
raise ValueError('empty shelf')
|
||||||
lastqm.save()
|
else:
|
||||||
elif lastqm:
|
if metadata is not None and metadata != last_metadata: # change metadata
|
||||||
metadata = lastqm.metadata
|
changed = True
|
||||||
if lastqt != shelf_type or (lastqt and metadata != lastqmm):
|
last_shelfmember.metadata = metadata
|
||||||
ShelfLogEntry.objects.create(owner=self.owner, shelf=shelf, item=item, metadata=metadata or {})
|
last_shelfmember.visibility = visibility
|
||||||
|
last_shelfmember.save()
|
||||||
|
elif visibility != last_visibility: # change visibility
|
||||||
|
last_shelfmember.visibility = visibility
|
||||||
|
last_shelfmember.save()
|
||||||
|
if changed:
|
||||||
|
if metadata is None:
|
||||||
|
metadata = last_metadata or {}
|
||||||
|
ShelfLogEntry.objects.create(owner=self.owner, shelf=shelf, item=item, metadata=metadata)
|
||||||
|
|
||||||
def get_log(self):
|
def get_log(self):
|
||||||
return ShelfLogEntry.objects.filter(owner=self.owner).order_by('timestamp')
|
return ShelfLogEntry.objects.filter(owner=self.owner).order_by('timestamp')
|
||||||
|
@ -488,6 +498,10 @@ class Mark:
|
||||||
def shelf_label(self):
|
def shelf_label(self):
|
||||||
return self.shelfmember._shelf.shelf_label if self.shelfmember else None
|
return self.shelfmember._shelf.shelf_label if self.shelfmember else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_time(self):
|
||||||
|
return self.shelfmember.created_time if self.shelfmember else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visibility(self):
|
def visibility(self):
|
||||||
return self.shelfmember.visibility if self.shelfmember else None
|
return self.shelfmember.visibility if self.shelfmember else None
|
||||||
|
|
|
@ -62,9 +62,13 @@ class ShelfTest(TestCase):
|
||||||
log = shelf_manager.get_log_for_item(book1)
|
log = shelf_manager.get_log_for_item(book1)
|
||||||
self.assertEqual(log.count(), 4)
|
self.assertEqual(log.count(), 4)
|
||||||
self.assertEqual(log.last().metadata, {'progress': 10})
|
self.assertEqual(log.last().metadata, {'progress': 10})
|
||||||
shelf_manager.move_item(book1, ShelfType.STARTED, metadata={'progress': 100})
|
shelf_manager.move_item(book1, ShelfType.STARTED, metadata={'progress': 90})
|
||||||
log = shelf_manager.get_log_for_item(book1)
|
log = shelf_manager.get_log_for_item(book1)
|
||||||
self.assertEqual(log.count(), 5)
|
self.assertEqual(log.count(), 5)
|
||||||
|
self.assertEqual(Mark(user, book1).visibility, 0)
|
||||||
|
shelf_manager.move_item(book1, ShelfType.STARTED, metadata={'progress': 90}, visibility=1)
|
||||||
|
self.assertEqual(Mark(user, book1).visibility, 1)
|
||||||
|
self.assertEqual(shelf_manager.get_log_for_item(book1).count(), 5)
|
||||||
|
|
||||||
|
|
||||||
class TagTest(TestCase):
|
class TagTest(TestCase):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{"movie_results":[],"person_results":[],"tv_results":[],"tv_episode_results":[{"id":3854678,"name":"双胞胎贝丝","overview":"夏茉和莫蒂沉浸在全新的超逼真游戏机。","media_type":"tv_episode","vote_average":8.8,"vote_count":8,"air_date":"2022-09-18","episode_number":3,"production_code":"","runtime":22,"season_number":6,"show_id":60625,"still_path":"/8ZCNAxkDo67GtcBJBDIERdywnar.jpg"}],"tv_season_results":[]}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3098
test_data/https___movie_douban_com_subject_35597581_
Normal file
3098
test_data/https___movie_douban_com_subject_35597581_
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue