Coverage for bundesliga_tippspiel/background/season_events.py: 88%

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

61 statements  

1"""LICENSE 

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

3 

4This file is part of bundesliga-tippspiel. 

5 

6bundesliga-tippspiel 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 

11bundesliga-tippspiel 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 bundesliga-tippspiel. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

20from typing import List 

21from flask import render_template 

22from bs4 import BeautifulSoup 

23from smtplib import SMTPAuthenticationError 

24from datetime import datetime, timedelta 

25from puffotter.smtp import send_email 

26from jerrycan.base import db, app 

27from jerrycan.db.User import User 

28from bundesliga_tippspiel.Config import Config 

29from bundesliga_tippspiel.db.SeasonEvent import SeasonEvent, SeasonEventType 

30from bundesliga_tippspiel.db.match_data.Match import Match 

31 

32 

33def handle_season_events(): 

34 """ 

35 Handles any events that happen once every season 

36 :return: None 

37 """ 

38 for event in load_season_events(): 

39 

40 try: 

41 if event.executed: 

42 continue 

43 elif event.event_type == SeasonEventType.PRE_SEASON_MAIL: 

44 event.executed = handle_preseason_reminder() 

45 elif event.event_type == SeasonEventType.MID_SEASON_REMINDER: 

46 event.executed = handle_midseason_reminder() 

47 elif event.event_type == SeasonEventType.POST_SEASON_WRAPUP: 47 ↛ 52line 47 didn't jump to line 52, because the condition on line 47 was never false

48 event.executed = handle_postseason_wrapup() 

49 else: # pragma: no cover 

50 pass 

51 

52 db.session.commit() 

53 except SMTPAuthenticationError: 

54 app.logger.error("Failed to send email (Authentication failed)") 

55 

56 

57def load_season_events() -> List[SeasonEvent]: 

58 """ 

59 Loads all event states from the database 

60 :return: The event states 

61 """ 

62 existing = { 

63 x.event_type: x 

64 for x in SeasonEvent.query.filter_by( 

65 season=Config.season(), 

66 league=Config.OPENLIGADB_LEAGUE 

67 ).all() 

68 } 

69 for event_type in SeasonEventType: 

70 if event_type not in existing: 

71 new = SeasonEvent( 

72 event_type=event_type, 

73 executed=False, 

74 season=Config.season(), 

75 league=Config.OPENLIGADB_LEAGUE 

76 ) 

77 db.session.add(new) 

78 existing[event_type] = new 

79 

80 db.session.commit() 

81 return list(existing.values()) 

82 

83 

84def handle_preseason_reminder() -> bool: 

85 """ 

86 Sends a reminder to existing users a week before the start of the season 

87 :return: Whether or not the reminder was sent 

88 """ 

89 return __handle_reminder( 

90 1, 

91 timedelta(days=7), 

92 "email/preseason.html", 

93 True 

94 ) 

95 

96 

97def handle_midseason_reminder() -> bool: 

98 """ 

99 Handles sending out midseason reminders 

100 :return: Whether the reminder was sent or not 

101 """ 

102 return __handle_reminder( 

103 18, 

104 timedelta(days=7), 

105 "email/midseason.html", 

106 True 

107 ) 

108 

109 

110def handle_postseason_wrapup() -> bool: 

111 """ 

112 Handles the post-season wrapup 

113 :return: None 

114 """ 

115 return __handle_reminder( 

116 34, 

117 timedelta(days=1), 

118 "email/postseason.html", 

119 False 

120 ) 

121 

122 

123def __handle_reminder( 

124 matchday: int, 

125 delta: timedelta, 

126 message_file: str, 

127 before: bool 

128) -> bool: 

129 """ 

130 Handles sending out a reminder after/before a specified time 

131 :param matchday: The matchday that acts as an anchor 

132 :param delta: The time delta relative to the matchday 

133 :param message_file: The HTML template containing the message 

134 :param before: Whether or not the email should be sent before the event 

135 :return: True if the message was sent, False if it was not yet due 

136 """ 

137 kickoff_times = [ 

138 x.kickoff_datetime 

139 for x in Match.query.filter_by( 

140 matchday=matchday, 

141 season=Config.season(), 

142 league=Config.OPENLIGADB_LEAGUE 

143 ).all() 

144 ] 

145 

146 if len(kickoff_times) == 0: 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true

147 return False 

148 elif before: 

149 kickoff = min(kickoff_times) 

150 else: 

151 kickoff = max(kickoff_times) 

152 

153 now = datetime.utcnow() 

154 

155 if before and kickoff - delta > now: 

156 return False 

157 elif not before and kickoff + delta > now: 

158 return False 

159 else: 

160 for user in User.query.filter_by(confirmed=True).all(): 

161 

162 message = render_template( 

163 message_file, 

164 user=user 

165 ) 

166 

167 send_email( 

168 user.email, 

169 f"Fußball Tippspiel Saison {Config.season_string()}", 

170 message, 

171 Config.SMTP_HOST, 

172 Config.SMTP_ADDRESS, 

173 Config.SMTP_PASSWORD, 

174 Config.SMTP_PORT 

175 ) 

176 

177 telegram = user.telegram_chat_id 

178 if telegram is not None: 178 ↛ 179line 178 didn't jump to line 179, because the condition on line 178 was never true

179 message = BeautifulSoup(message, "html.parser").text 

180 message = "\n".join([x.strip() for x in message.split("\n")]) 

181 telegram.send_message(message) 

182 return True