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

41 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-24 17:35 +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 

16import contextlib 

17import types 

18from base64 import b64encode 

19from collections.abc import Awaitable, Mapping 

20 

21from tornado.web import HTTPError 

22 

23from ..utils.options import COLOUR_SCHEMES, Options 

24from ..utils.request_handler import HTMLRequestHandler 

25from ..utils.themes import THEMES 

26from ..utils.utils import ( 

27 BUMPSCOSITY_VALUES, 

28 ModuleInfo, 

29 bool_to_str, 

30 str_to_bool, 

31) 

32 

33 

34def get_module_info() -> ModuleInfo: 

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

36 return ModuleInfo( 

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

38 name="Einstellungen", 

39 description="Stelle wichtige Sachen ein", 

40 path="/einstellungen", 

41 keywords=( 

42 "Einstellungen", 

43 "Config", 

44 "Settings", 

45 ), 

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

47 ) 

48 

49 

50class SettingsPage(HTMLRequestHandler): 

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

52 

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

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

55 if head: 

56 return 

57 await self.render_settings( # nosec: B106 

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

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

60 token="", 

61 advanced_settings=self.show_advanced_settings(), 

62 ) 

63 

64 async def post(self) -> None: 

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

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

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

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

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

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

71 access_token: None | str = ( 

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

73 ) 

74 

75 if save_in_cookie: 

76 if access_token: 

77 self.set_cookie("access_token", access_token) 

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

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

80 self.set_cookie( 

81 option.name, 

82 option.value_to_string( 

83 option.get_value( 

84 self, 

85 include_cookie=False, 

86 include_query_argument=False, 

87 ) 

88 ), 

89 httponly=option.httponly, 

90 ) 

91 

92 replace_url_with = self.fix_url( 

93 self.request.full_url(), 

94 include_protocol_and_host=True, 

95 query_args={ 

96 "access_token": None, # nosec B105:hardcoded_password_string 

97 "advanced_settings": None, 

98 "save_in_cookie": None, 

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

100 }, 

101 ) 

102 else: 

103 replace_url_with = self.fix_url( 

104 self.request.full_url(), 

105 include_protocol_and_host=True, 

106 query_args={ 

107 "access_token": access_token, 

108 "advanced_settings": advanced_settings, 

109 "save_in_cookie": False, 

110 **self.user_settings.as_dict_with_str_values( 

111 include_cookie=False, include_query_argument=False 

112 ), 

113 }, 

114 ) 

115 

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

117 return self.redirect(replace_url_with) 

118 

119 await self.render_settings( 

120 advanced_settings=advanced_settings, 

121 save_in_cookie=save_in_cookie, 

122 show_token_input=bool( 

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

124 ), 

125 token=token, 

126 kwargs={ 

127 option.name: option.get_value( 

128 self, include_cookie=False, include_query_argument=False 

129 ) 

130 for option in self.user_settings.iter_options() 

131 }, 

132 ) 

133 

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

135 self, 

136 *, 

137 save_in_cookie: bool, 

138 show_token_input: bool, 

139 token: str, 

140 advanced_settings: bool, 

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

142 ) -> Awaitable[None]: 

143 """Render the settings page.""" 

144 return self.render( 

145 "pages/settings.html", 

146 advanced_settings=advanced_settings, 

147 ask_before_leaving_default=( 

148 Options.ask_before_leaving.get_default_value(self) 

149 ), 

150 bumpscosity_values=BUMPSCOSITY_VALUES, 

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

152 save_in_cookie=save_in_cookie, 

153 show_token_input=show_token_input, 

154 themes=THEMES, 

155 colour_schemes=COLOUR_SCHEMES, 

156 token=token, 

157 **kwargs, 

158 ) 

159 

160 def show_advanced_settings(self) -> bool: 

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

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

163 with contextlib.suppress(ValueError): 

164 return str_to_bool(arg) 

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