Coverage for otaku_info/db/MediaItem.py: 54%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

66 statements  

1"""LICENSE 

2Copyright 2020 Hermann Krumrey <hermann@krumreyh.com> 

3 

4This file is part of otaku-info. 

5 

6otaku-info is free software: you can redistribute it and/or modify 

7it under the terms of the GNU General Public License as published by 

8the Free Software Foundation, either version 3 of the License, or 

9(at your option) any later version. 

10 

11otaku-info is distributed in the hope that it will be useful, 

12but WITHOUT ANY WARRANTY; without even the implied warranty of 

13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14GNU General Public License for more details. 

15 

16You should have received a copy of the GNU General Public License 

17along with otaku-info. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

20from flask import url_for 

21from datetime import datetime 

22from typing import Dict, Optional, List, TYPE_CHECKING 

23from jerrycan.base import db 

24from jerrycan.db.ModelMixin import ModelMixin 

25from otaku_info.enums import ReleasingState, MediaType, MediaSubType, \ 

26 ListService 

27from otaku_info.utils.urls import generate_service_url, \ 

28 generate_service_icon_url 

29if TYPE_CHECKING: 29 ↛ 30line 29 didn't jump to line 30, because the condition on line 29 was never true

30 from otaku_info.db.MediaIdMapping import MediaIdMapping 

31 from otaku_info.db.LnRelease import LnRelease 

32 from otaku_info.db.MediaUserState import MediaUserState 

33 from otaku_info.db.MangaChapterGuess import MangaChapterGuess 

34 

35 

36class MediaItem(ModelMixin, db.Model): 

37 """ 

38 Database model for media items. 

39 These model a representation of a series specific to one list service 

40 like anilist or mangadex. 

41 """ 

42 

43 def __init__(self, *args, **kwargs): 

44 """ 

45 Initializes the Model 

46 :param args: The constructor arguments 

47 :param kwargs: The constructor keyword arguments 

48 """ 

49 super().__init__(*args, **kwargs) 

50 

51 __tablename__ = "media_items" 

52 service: ListService = db.Column(db.Enum(ListService), primary_key=True) 

53 service_id: str = db.Column(db.String(255), primary_key=True) 

54 media_type: MediaType = db.Column(db.Enum(MediaType), primary_key=True) 

55 

56 media_subtype: MediaSubType = \ 

57 db.Column(db.Enum(MediaSubType), nullable=False) 

58 english_title: Optional[str] = db.Column(db.Unicode(255), nullable=True) 

59 romaji_title: str = db.Column(db.Unicode(255), nullable=False) 

60 cover_url: str = db.Column(db.String(255), nullable=False) 

61 latest_release: Optional[int] = db.Column(db.Integer, nullable=True) 

62 latest_volume_release: Optional[int] = db.Column(db.Integer, nullable=True) 

63 next_episode: Optional[int] = db.Column(db.Integer, nullable=True) 

64 next_episode_airing_time: Optional[int] = \ 

65 db.Column(db.Integer, nullable=True) 

66 releasing_state: ReleasingState = \ 

67 db.Column(db.Enum(ReleasingState), nullable=False) 

68 

69 id_mappings: List["MediaIdMapping"] = db.relationship( 

70 "MediaIdMapping", back_populates="media_item", cascade="all, delete" 

71 ) 

72 user_states: List["MediaUserState"] = db.relationship( 

73 "MediaUserState", back_populates="media_item", cascade="all, delete" 

74 ) 

75 ln_releases: List["LnRelease"] = db.relationship( 

76 "LnRelease", back_populates="media_item", cascade="all, delete" 

77 ) 

78 chapter_guess: Optional["MangaChapterGuess"] = db.relationship( 

79 "MangaChapterGuess", 

80 uselist=False, 

81 back_populates="media_item", 

82 cascade="all, delete" 

83 ) 

84 

85 @property 

86 def service_url(self) -> str: 

87 """ 

88 :return: The URL to the series for the given service 

89 """ 

90 return generate_service_url( 

91 self.service, 

92 self.media_type, 

93 self.service_id 

94 ) 

95 

96 @property 

97 def service_icon(self) -> str: 

98 """ 

99 :return: The path to the service's icon file 

100 """ 

101 return generate_service_icon_url(self.service) 

102 

103 @property 

104 def current_release(self) -> Optional[int]: 

105 """ 

106 The most current release, specifically tailored to the type of media 

107 :return: None 

108 """ 

109 if self.next_episode is not None: 

110 return self.next_episode - 1 

111 elif self.latest_volume_release is not None: 

112 return self.latest_volume_release 

113 elif self.latest_release is not None: 

114 return self.latest_release 

115 else: 

116 return None 

117 

118 @property 

119 def ids(self) -> Dict[ListService, "MediaIdMapping"]: 

120 """ 

121 :return: A dictionary mapping list services to IDs for this media item 

122 """ 

123 from otaku_info.db.MediaIdMapping import MediaIdMapping 

124 related = { 

125 x.service: x 

126 for x in self.id_mappings 

127 } 

128 related[self.service] = MediaIdMapping( 

129 service=self.service, 

130 service_id=self.service_id, 

131 media_type=self.media_type, 

132 parent_service=self.service, 

133 parent_service_id=self.service_id 

134 ) 

135 return related 

136 

137 @property 

138 def title(self) -> str: 

139 """ 

140 :return: The default title for the media item. 

141 """ 

142 if self.english_title is None: 

143 return self.romaji_title 

144 else: 

145 return self.english_title 

146 

147 @property 

148 def own_url(self) -> str: 

149 """ 

150 :return: The URL to the item's page on the otaku-info site 

151 """ 

152 return url_for( 

153 "media.media", 

154 service=self.service.value, 

155 service_id=self.service_id, 

156 media_type=self.media_type.value 

157 ) 

158 

159 @property 

160 def next_episode_datetime(self) -> Optional[datetime]: 

161 """ 

162 :return: The datetime for when the next episode airs 

163 """ 

164 if self.next_episode_airing_time is None: 

165 return None 

166 else: 

167 return datetime.fromtimestamp(self.next_episode_airing_time)