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',
|
||||
'contents',
|
||||
'series',
|
||||
'producer',
|
||||
'imprint',
|
||||
]
|
||||
subtitle = 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)
|
||||
contents = jsondata.CharField(null=True, blank=True, default=None)
|
||||
price = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
||||
producer = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
||||
imprint = jsondata.FloatField(_("发表月份"), null=True, blank=True)
|
||||
|
||||
@property
|
||||
def isbn10(self):
|
||||
|
|
|
@ -129,7 +129,8 @@ class CharField(JSONFieldMixin, fields.CharField):
|
|||
class DateField(JSONFieldMixin, fields.DateField):
|
||||
def to_json(self, 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')
|
||||
|
||||
def from_json(self, value):
|
||||
|
@ -140,6 +141,8 @@ class DateField(JSONFieldMixin, fields.DateField):
|
|||
class DateTimeField(JSONFieldMixin, fields.DateTimeField):
|
||||
def to_json(self, value):
|
||||
if value:
|
||||
if not isinstance(value, (datetime, date)):
|
||||
value = dateparse.parse_date(value)
|
||||
if not timezone.is_aware(value):
|
||||
value = timezone.make_aware(value)
|
||||
return value.isoformat()
|
||||
|
|
|
@ -20,7 +20,7 @@ class SiteName(models.TextChoices):
|
|||
IMDB = 'imdb', _('IMDB')
|
||||
TMDB = 'tmdb', _('The Movie Database')
|
||||
Bandcamp = 'bandcamp', _('Bandcamp')
|
||||
Spotify_Album = 'spotify', _('Spotify')
|
||||
Spotify = 'spotify', _('Spotify')
|
||||
IGDB = 'igdb', _('IGDB')
|
||||
Steam = 'steam', _('Steam')
|
||||
Bangumi = 'bangumi', _('Bangumi')
|
||||
|
@ -231,7 +231,7 @@ class Item(SoftDeleteMixin, PolymorphicModel):
|
|||
|
||||
@property
|
||||
def url(self):
|
||||
return f'/{self.url_path}/{self.url_id}'
|
||||
return f'/{self.url_path}/{self.url_id}/'
|
||||
|
||||
@property
|
||||
def class_name(self):
|
||||
|
@ -239,7 +239,7 @@ class Item(SoftDeleteMixin, PolymorphicModel):
|
|||
|
||||
@classmethod
|
||||
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)))
|
||||
|
||||
# def get_lookup_id(self, id_type: str) -> str:
|
||||
|
|
|
@ -1,10 +1,65 @@
|
|||
from catalog.common import *
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Game(Item):
|
||||
category = ItemCategory.Game
|
||||
url_path = 'game'
|
||||
demonstrative = _('这个游戏')
|
||||
igdb = PrimaryLookupIdDescriptor(IdType.IGDB)
|
||||
steam = PrimaryLookupIdDescriptor(IdType.Steam)
|
||||
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 .movie.models import Movie
|
||||
from .tv.models import TVShow, TVSeason, TVEpisode
|
||||
|
|
|
@ -10,12 +10,12 @@ class Movie(Item):
|
|||
tmdb_movie = PrimaryLookupIdDescriptor(IdType.TMDB_Movie)
|
||||
douban_movie = PrimaryLookupIdDescriptor(IdType.DoubanMovie)
|
||||
duration = jsondata.IntegerField(blank=True, default=None)
|
||||
demonstrative = _('这部电影')
|
||||
|
||||
METADATA_COPY_LIST = [
|
||||
'title',
|
||||
'orig_title',
|
||||
'other_title',
|
||||
'imdb_code',
|
||||
'director',
|
||||
'playwright',
|
||||
'actor',
|
||||
|
@ -33,7 +33,6 @@ class Movie(Item):
|
|||
]
|
||||
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, )
|
||||
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, )
|
||||
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, )
|
||||
|
|
|
@ -1,12 +1,36 @@
|
|||
from catalog.common import *
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Album(Item):
|
||||
url_path = 'album'
|
||||
category = ItemCategory.Music
|
||||
demonstrative = _('这张专辑')
|
||||
barcode = PrimaryLookupIdDescriptor(IdType.GTIN)
|
||||
douban_music = PrimaryLookupIdDescriptor(IdType.DoubanMusic)
|
||||
spotify_album = PrimaryLookupIdDescriptor(IdType.Spotify_Album)
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
METADATA_COPY_LIST = [
|
||||
'title',
|
||||
'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
|
||||
class ApplePodcast(AbstractSite):
|
||||
SITE_NAME = SiteName.ApplePodcast
|
||||
ID_TYPE = IdType.ApplePodcast
|
||||
URL_PATTERNS = [r"https://[^.]+.apple.com/\w+/podcast/*[^/?]*/id(\d+)"]
|
||||
WIKI_PROPERTY_ID = 'P5842'
|
||||
|
|
|
@ -8,6 +8,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
@SiteManager.register
|
||||
class Bangumi(AbstractSite):
|
||||
SITE_NAME = SiteName.Bangumi
|
||||
ID_TYPE = IdType.Bangumi
|
||||
URL_PATTERNS = [
|
||||
r"https://bgm\.tv/subject/(\d+)",
|
||||
|
|
|
@ -140,7 +140,7 @@ class DoubanBook(AbstractSite):
|
|||
|
||||
imprint_elem = content.xpath(
|
||||
"//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 = {
|
||||
'title': title,
|
||||
|
@ -160,7 +160,7 @@ class DoubanBook(AbstractSite):
|
|||
'brief': brief,
|
||||
'contents': contents,
|
||||
'series': series,
|
||||
'producer': producer,
|
||||
'imprint': imprint,
|
||||
'cover_image_url': img_url,
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
@SiteManager.register
|
||||
class DoubanDrama(AbstractSite):
|
||||
SITE_NAME = SiteName.Douban
|
||||
ID_TYPE = IdType.DoubanDrama
|
||||
URL_PATTERNS = [r"\w+://www.douban.com/location/drama/(\d+)/"]
|
||||
WIKI_PROPERTY_ID = 'P6443'
|
||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
@SiteManager.register
|
||||
class DoubanGame(AbstractSite):
|
||||
SITE_NAME = SiteName.Douban
|
||||
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}"]
|
||||
WIKI_PROPERTY_ID = ''
|
||||
|
|
|
@ -5,57 +5,15 @@ from catalog.tv.models import *
|
|||
import logging
|
||||
from django.db import models
|
||||
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__)
|
||||
|
||||
|
||||
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
|
||||
class DoubanMovie(AbstractSite):
|
||||
SITE_NAME = SiteName.Douban
|
||||
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}"]
|
||||
WIKI_PROPERTY_ID = '?'
|
||||
|
@ -109,30 +67,16 @@ class DoubanMovie(AbstractSite):
|
|||
"//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
|
||||
|
||||
# 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 = []
|
||||
if genre_elem:
|
||||
genre = []
|
||||
for g in genre_elem:
|
||||
g = g.split(' ')[0]
|
||||
if g == '紀錄片': # likely some original data on douban was corrupted
|
||||
g = '纪录片'
|
||||
elif g == '鬼怪':
|
||||
g = '惊悚'
|
||||
if g in genre_translator:
|
||||
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
|
||||
genre.append(g)
|
||||
|
||||
showtime_elem = content.xpath(
|
||||
"//span[@property='v:initialReleaseDate']/text()")
|
||||
|
@ -230,7 +174,7 @@ class DoubanMovie(AbstractSite):
|
|||
'year': year,
|
||||
'duration': duration,
|
||||
'season_number': season,
|
||||
'episodes': episodes,
|
||||
'episode_count': episodes,
|
||||
'single_episode_length': single_episode_length,
|
||||
'brief': brief,
|
||||
'is_series': is_series,
|
||||
|
@ -252,8 +196,11 @@ class DoubanMovie(AbstractSite):
|
|||
pd.metadata['preferred_model'] = 'TVSeason'
|
||||
tmdb_show_id = res_data['tv_episode_results'][0]['show_id']
|
||||
if res_data['tv_episode_results'][0]['episode_number'] != 1:
|
||||
_logger.error(f'Douban Movie {self.url} mapping to unexpected imdb episode {imdb_code}')
|
||||
# TODO correct the IMDB id
|
||||
_logger.warning(f'Douban Movie {self.url} mapping to unexpected imdb episode {imdb_code}')
|
||||
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
|
||||
if tmdb_show_id:
|
||||
pd.metadata['required_resources'] = [{
|
||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
@SiteManager.register
|
||||
class DoubanMusic(AbstractSite):
|
||||
SITE_NAME = SiteName.Douban
|
||||
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}"]
|
||||
WIKI_PROPERTY_ID = ''
|
||||
|
@ -56,51 +57,48 @@ class DoubanMusic(AbstractSite):
|
|||
brief = '\n'.join([e.strip() for e in brief_elem[0].xpath(
|
||||
'./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 = img_url_elem[0].strip() if img_url_elem else None
|
||||
|
||||
pd = ResourceContent(metadata={
|
||||
data = {
|
||||
'title': title,
|
||||
'artist': artist,
|
||||
'genre': genre,
|
||||
'release_date': release_date,
|
||||
'duration': None,
|
||||
'company': company,
|
||||
'company': [company],
|
||||
'track_list': track_list,
|
||||
'brief': brief,
|
||||
'other_info': other_info,
|
||||
'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:
|
||||
pd.lookup_ids[IdType.GTIN] = gtin
|
||||
if isrc:
|
||||
|
|
|
@ -39,6 +39,7 @@ def search_igdb_by_3p_url(steam_url):
|
|||
|
||||
@SiteManager.register
|
||||
class IGDB(AbstractSite):
|
||||
SITE_NAME = SiteName.IGDB
|
||||
ID_TYPE = IdType.IGDB
|
||||
URL_PATTERNS = [r"\w+://www\.igdb\.com/games/([a-zA-Z0-9\-_]+)"]
|
||||
WIKI_PROPERTY_ID = '?'
|
||||
|
@ -90,9 +91,9 @@ class IGDB(AbstractSite):
|
|||
steam_url = website['url']
|
||||
pd = ResourceContent(metadata={
|
||||
'title': r['name'],
|
||||
'other_title': None,
|
||||
'developer': developer,
|
||||
'publisher': publisher,
|
||||
'other_title': [],
|
||||
'developer': [developer],
|
||||
'publisher': [publisher],
|
||||
'release_date': release_date,
|
||||
'genre': genre,
|
||||
'platform': platform,
|
||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
@SiteManager.register
|
||||
class IMDB(AbstractSite):
|
||||
SITE_NAME = SiteName.IMDB
|
||||
ID_TYPE = IdType.IMDB
|
||||
URL_PATTERNS = [r'\w+://www.imdb.com/title/(tt\d+)']
|
||||
WIKI_PROPERTY_ID = '?'
|
||||
|
|
|
@ -21,6 +21,7 @@ spotify_token_expire_time = time.time()
|
|||
|
||||
@SiteManager.register
|
||||
class Spotify(AbstractSite):
|
||||
SITE_NAME = SiteName.Spotify
|
||||
ID_TYPE = IdType.Spotify_Album
|
||||
URL_PATTERNS = [r'\w+://open\.spotify\.com/album/([a-zA-Z0-9]+)']
|
||||
WIKI_PROPERTY_ID = '?'
|
||||
|
@ -106,7 +107,7 @@ def get_spotify_token():
|
|||
invoke_spotify_token()
|
||||
return spotify_token
|
||||
|
||||
|
||||
|
||||
def is_spotify_token_expired():
|
||||
global spotify_token_expire_time
|
||||
return True if spotify_token_expire_time <= time.time() else False
|
||||
|
|
|
@ -10,6 +10,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
@SiteManager.register
|
||||
class Steam(AbstractSite):
|
||||
SITE_NAME = SiteName.Steam
|
||||
ID_TYPE = IdType.Steam
|
||||
URL_PATTERNS = [r"\w+://store\.steampowered\.com/app/(\d+)"]
|
||||
WIKI_PROPERTY_ID = '?'
|
||||
|
|
|
@ -20,6 +20,12 @@ def search_tmdb_by_imdb_id(imdb_id):
|
|||
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):
|
||||
d = {}
|
||||
for src, dst in key_map.items():
|
||||
|
@ -27,39 +33,9 @@ def _copy_dict(s, key_map):
|
|||
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
|
||||
class TMDB_Movie(AbstractSite):
|
||||
SITE_NAME = SiteName.TMDB
|
||||
ID_TYPE = IdType.TMDB_Movie
|
||||
URL_PATTERNS = [r'\w+://www.themoviedb.org/movie/(\d+)']
|
||||
WIKI_PROPERTY_ID = '?'
|
||||
|
@ -98,8 +74,7 @@ class TMDB_Movie(AbstractSite):
|
|||
# in minutes
|
||||
duration = res_data['runtime'] if res_data['runtime'] else None
|
||||
|
||||
genre = list(map(lambda x: genre_map[x['name']] if x['name']
|
||||
in genre_map else 'Other', res_data['genres']))
|
||||
genre = [x['name'] for x in res_data['genres']]
|
||||
language = list(map(lambda x: x['name'], res_data['spoken_languages']))
|
||||
brief = res_data['overview']
|
||||
|
||||
|
@ -199,8 +174,8 @@ class TMDB_TV(AbstractSite):
|
|||
# in minutes
|
||||
duration = res_data['runtime'] if res_data['runtime'] else None
|
||||
|
||||
genre = list(map(lambda x: genre_map[x['name']] if x['name']
|
||||
in genre_map else 'Other', res_data['genres']))
|
||||
genre = [x['name'] for x in res_data['genres']]
|
||||
|
||||
language = list(map(lambda x: x['name'], res_data['spoken_languages']))
|
||||
brief = res_data['overview']
|
||||
|
||||
|
@ -248,6 +223,7 @@ class TMDB_TV(AbstractSite):
|
|||
'language': language,
|
||||
'year': year,
|
||||
'duration': duration,
|
||||
'season_count': res_data['number_of_seasons'],
|
||||
'season': None,
|
||||
'episodes': None,
|
||||
'single_episode_length': None,
|
||||
|
@ -292,7 +268,7 @@ class TMDB_TVSeason(AbstractSite):
|
|||
d = BasicDownloader(api_url).download().json()
|
||||
if not d.get('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'] = [{
|
||||
'model': 'TVShow',
|
||||
'id_type': IdType.TMDB_TV,
|
||||
|
|
|
@ -12,8 +12,106 @@
|
|||
|
||||
<!-- class specific 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 %}
|
||||
|
||||
<!-- class specific 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 %}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
<div>{% if item.binding %}{% trans '装帧:' %}{{ item.binding }}{% endif %}</div>
|
||||
<div>{% if item.price %}{% trans '定价:' %}{{ item.price }}{% endif %}</div>
|
||||
<div>{% if item.pages %}{% trans '页数:' %}{{ item.pages }}{% endif %}</div>
|
||||
<div>{% if item.imprint %}{% trans '出品方:' %}{{ item.imprint }}{% endif %}</div>
|
||||
{% if item.other_info %}
|
||||
{% for k, v in item.other_info.items %}
|
||||
<div>
|
||||
|
|
|
@ -12,6 +12,100 @@
|
|||
|
||||
<!-- class specific 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 %}
|
||||
|
||||
<!-- class specific sidebar -->
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
</a>
|
||||
|
||||
<div class="entity-detail__info">
|
||||
{% block title %}
|
||||
<h5 class="entity-detail__title">
|
||||
{{ item.title }}
|
||||
|
||||
|
@ -61,6 +62,7 @@
|
|||
</a>
|
||||
{% endfor %}
|
||||
</h5>
|
||||
{% endblock %}
|
||||
|
||||
{% block details %}
|
||||
<div class="entity-detail__fields">
|
||||
|
@ -112,6 +114,16 @@
|
|||
</div>
|
||||
{% 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">
|
||||
<h5 class="entity-marks__title">{% trans '标记' %}</h5>
|
||||
<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">
|
||||
{% if mark.shelf_type %}
|
||||
<div class="mark-panel">
|
||||
<span class="mark-panel__status">{% trans '我' %}{% trans mark.shelf_label %}</span>
|
||||
{% if mark.rating %}
|
||||
<span class="mark-panel__rating-star rating-star" data-rating-score="{{ mark.rating | floatformat:"0" }}"></span>
|
||||
{% endif %}
|
||||
<span class="mark-panel__status">{% trans '我' %}{% trans mark.shelf_label %}</span>
|
||||
{% 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 %}
|
||||
|
|
|
@ -10,8 +10,162 @@
|
|||
{% load strip_scheme %}
|
||||
{% 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 -->
|
||||
{% 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 %}
|
||||
|
||||
<!-- class specific sidebar -->
|
||||
|
|
|
@ -10,8 +10,162 @@
|
|||
{% load strip_scheme %}
|
||||
{% 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 -->
|
||||
{% 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 %}
|
||||
|
||||
<!-- class specific sidebar -->
|
||||
|
|
|
@ -10,8 +10,162 @@
|
|||
{% load strip_scheme %}
|
||||
{% 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 -->
|
||||
{% 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 %}
|
||||
|
||||
<!-- 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 django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class TVShow(Item):
|
||||
category = ItemCategory.TV
|
||||
url_path = 'tv'
|
||||
demonstrative = _('这部剧集')
|
||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||
tmdb_tv = PrimaryLookupIdDescriptor(IdType.TMDB_TV)
|
||||
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):
|
||||
category = ItemCategory.TV
|
||||
url_path = 'tv/season'
|
||||
demonstrative = _('这部剧集')
|
||||
douban_movie = PrimaryLookupIdDescriptor(IdType.DoubanMovie)
|
||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||
tmdb_tvseason = PrimaryLookupIdDescriptor(IdType.TMDB_TVSeason)
|
||||
show = models.ForeignKey(TVShow, null=True, on_delete=models.SET_NULL, related_name='seasons')
|
||||
season_number = models.PositiveIntegerField()
|
||||
episode_count = jsondata.IntegerField(blank=True, default=None)
|
||||
METADATA_COPY_LIST = ['title', 'brief', 'season_number', 'episode_count']
|
||||
season_number = models.PositiveIntegerField(null=True)
|
||||
episode_count = models.PositiveIntegerField(null=True)
|
||||
|
||||
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):
|
||||
"""add Work from resource.metadata['work'] if not yet"""
|
||||
|
@ -63,6 +132,6 @@ class TVEpisode(Item):
|
|||
url_path = 'tv/episode'
|
||||
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')
|
||||
episode_number = models.PositiveIntegerField()
|
||||
episode_number = models.PositiveIntegerField(null=True)
|
||||
imdb = PrimaryLookupIdDescriptor(IdType.IMDB)
|
||||
METADATA_COPY_LIST = ['title', 'brief', 'episode_number']
|
||||
|
|
|
@ -79,6 +79,13 @@ class DoubanMovieTVTestCase(TestCase):
|
|||
p3 = SiteManager.get_site_by_url(url3).get_resource_ready()
|
||||
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):
|
||||
@use_local_response
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
from django.urls import path, re_path
|
||||
from .api import api
|
||||
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 = [
|
||||
path("", api.urls),
|
||||
re_path('book/(?P<uid>[A-Za-z0-9]{21,22})/', retrieve, name='retrieve'),
|
||||
re_path(r'(?P<item_path>' + _get_all_url_paths() + ')/(?P<item_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__)
|
||||
|
||||
|
||||
def retrieve(request, uid):
|
||||
def retrieve(request, item_path, item_uid):
|
||||
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
|
||||
review = None
|
||||
mark_list = None
|
||||
|
|
|
@ -327,23 +327,33 @@ class ShelfManager:
|
|||
# metadata=None means no change
|
||||
if not item:
|
||||
raise ValueError('empty item')
|
||||
lastqm = self._shelf_member_for_item(item)
|
||||
lastqmm = lastqm.metadata if lastqm else None
|
||||
lastq = lastqm._shelf if lastqm else None
|
||||
lastqt = lastq.shelf_type if lastq else None
|
||||
last_shelfmember = self._shelf_member_for_item(item)
|
||||
last_shelf = last_shelfmember._shelf if last_shelfmember else None
|
||||
last_metadata = last_shelfmember.metadata if last_shelfmember 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
|
||||
if lastq != shelf:
|
||||
if lastq:
|
||||
lastq.remove_item(item)
|
||||
changed = False
|
||||
if last_shelf != shelf: # change shelf
|
||||
changed = True
|
||||
if last_shelf:
|
||||
last_shelf.remove_item(item)
|
||||
if shelf:
|
||||
shelf.append_item(item, visibility=visibility, metadata=metadata or {})
|
||||
elif metadata is not None:
|
||||
lastqm.metadata = metadata
|
||||
lastqm.save()
|
||||
elif lastqm:
|
||||
metadata = lastqm.metadata
|
||||
if lastqt != shelf_type or (lastqt and metadata != lastqmm):
|
||||
ShelfLogEntry.objects.create(owner=self.owner, shelf=shelf, item=item, metadata=metadata or {})
|
||||
elif last_shelf is None:
|
||||
raise ValueError('empty shelf')
|
||||
else:
|
||||
if metadata is not None and metadata != last_metadata: # change metadata
|
||||
changed = True
|
||||
last_shelfmember.metadata = metadata
|
||||
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):
|
||||
return ShelfLogEntry.objects.filter(owner=self.owner).order_by('timestamp')
|
||||
|
@ -488,6 +498,10 @@ class Mark:
|
|||
def shelf_label(self):
|
||||
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
|
||||
def visibility(self):
|
||||
return self.shelfmember.visibility if self.shelfmember else None
|
||||
|
|
|
@ -62,9 +62,13 @@ class ShelfTest(TestCase):
|
|||
log = shelf_manager.get_log_for_item(book1)
|
||||
self.assertEqual(log.count(), 4)
|
||||
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)
|
||||
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):
|
||||
|
|
|
@ -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