Coverage for an_website/settings/settings.py: 92.857%

42 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-04 17:54 +0000

1# This program is free software: you can redistribute it and/or modify 

2# it under the terms of the GNU Affero General Public License as 

3# published by the Free Software Foundation, either version 3 of the 

4# License, or (at your option) any later version. 

5# 

6# This program is distributed in the hope that it will be useful, 

7# but WITHOUT ANY WARRANTY; without even the implied warranty of 

8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

9# GNU Affero General Public License for more details. 

10# 

11# You should have received a copy of the GNU Affero General Public License 

12# along with this program. If not, see <https://www.gnu.org/licenses/>. 

13 

14"""The settings page used to change settings.""" 

15 

16from __future__ import annotations 

17 

18import contextlib 

19import types 

20from base64 import b64encode 

21from collections.abc import Awaitable, Mapping 

22 

23from tornado.web import HTTPError 

24 

25from ..utils.options import COLOUR_SCHEMES, Options 

26from ..utils.request_handler import HTMLRequestHandler 

27from ..utils.themes import THEMES 

28from ..utils.utils import ( 

29 BUMPSCOSITY_VALUES, 

30 ModuleInfo, 

31 bool_to_str, 

32 str_to_bool, 

33) 

34 

35 

36def get_module_info() -> ModuleInfo: 

37 """Create and return the ModuleInfo for this module.""" 

38 return ModuleInfo( 

39 handlers=((r"/einstellungen", SettingsPage),), 

40 name="Einstellungen", 

41 description="Stelle wichtige Sachen ein", 

42 path="/einstellungen", 

43 keywords=( 

44 "Einstellungen", 

45 "Config", 

46 "Settings", 

47 ), 

48 aliases=("/config", "/settings"), 

49 ) 

50 

51 

52class SettingsPage(HTMLRequestHandler): 

53 """The request handler for the settings page.""" 

54 

55 async def get(self, *, head: bool = False) -> None: 

56 """Handle GET requests to the settings page.""" 

57 if head: 

58 return 

59 await self.render_settings( # nosec: B106 

60 save_in_cookie=self.get_bool_argument("save_in_cookie", True), 

61 show_token_input=self.get_bool_argument("auth", False), 

62 token="", 

63 advanced_settings=self.show_advanced_settings(), 

64 ) 

65 

66 async def post(self) -> None: 

67 """Handle POST requests to the settings page.""" 

68 if not self.request.body.strip(): 

69 raise HTTPError(400, "No body provided") 

70 advanced_settings = self.get_bool_argument("advanced_settings", False) 

71 save_in_cookie: bool = self.get_bool_argument("save_in_cookie", False) 

72 token = self.get_argument("access_token", "") 

73 access_token: None | str = ( 

74 b64encode(token.encode("UTF-8")).decode("ASCII") if token else None 

75 ) 

76 

77 if save_in_cookie: 

78 if access_token: 

79 self.set_cookie("access_token", access_token) 

80 self.set_cookie("advanced_settings", bool_to_str(advanced_settings)) 

81 for option in self.user_settings.iter_options(): 

82 self.set_cookie( 

83 option.name, 

84 option.value_to_string( 

85 option.get_value( 

86 self, 

87 include_cookie=False, 

88 include_query_argument=False, 

89 ) 

90 ), 

91 httponly=option.httponly, 

92 ) 

93 

94 replace_url_with = self.fix_url( 

95 self.request.full_url(), 

96 include_protocol_and_host=True, 

97 query_args={ 

98 "access_token": None, 

99 "advanced_settings": None, 

100 "save_in_cookie": None, 

101 **dict.fromkeys(self.user_settings.iter_option_names()), 

102 }, 

103 ) 

104 else: 

105 replace_url_with = self.fix_url( 

106 self.request.full_url(), 

107 include_protocol_and_host=True, 

108 query_args={ 

109 "access_token": access_token, 

110 "advanced_settings": advanced_settings, 

111 "save_in_cookie": False, 

112 **self.user_settings.as_dict_with_str_values( 

113 include_cookie=False, include_query_argument=False 

114 ), 

115 }, 

116 ) 

117 

118 if replace_url_with != self.request.full_url(): 

119 return self.redirect(replace_url_with) 

120 

121 await self.render_settings( 

122 advanced_settings=advanced_settings, 

123 save_in_cookie=save_in_cookie, 

124 show_token_input=bool( 

125 token or self.get_bool_argument("auth", False) 

126 ), 

127 token=token, 

128 kwargs={ 

129 option.name: option.get_value( 

130 self, include_cookie=False, include_query_argument=False 

131 ) 

132 for option in self.user_settings.iter_options() 

133 }, 

134 ) 

135 

136 def render_settings( # pylint: disable=too-many-arguments 

137 self, 

138 *, 

139 save_in_cookie: bool, 

140 show_token_input: bool, 

141 token: str, 

142 advanced_settings: bool, 

143 kwargs: Mapping[str, object] = types.MappingProxyType({}), 

144 ) -> Awaitable[None]: 

145 """Render the settings page.""" 

146 return self.render( 

147 "pages/settings.html", 

148 advanced_settings=advanced_settings, 

149 ask_before_leaving_default=( 

150 Options.ask_before_leaving.get_default_value(self) 

151 ), 

152 bumpscosity_values=BUMPSCOSITY_VALUES, 

153 no_3rd_party_default=Options.no_3rd_party.get_default_value(self), 

154 save_in_cookie=save_in_cookie, 

155 show_token_input=show_token_input, 

156 themes=THEMES, 

157 colour_schemes=COLOUR_SCHEMES, 

158 token=token, 

159 **kwargs, 

160 ) 

161 

162 def show_advanced_settings(self) -> bool: 

163 """Whether advanced settings should be shown.""" 

164 if arg := self.get_argument("advanced_settings", ""): 

165 with contextlib.suppress(ValueError): 

166 return str_to_bool(arg) 

167 return str_to_bool(self.get_cookie("advanced_settings", ""), False)