Coverage for webapp/publisher/snaps/listing_views.py: 72%

151 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-28 22:05 +0000

1# Standard library 

2from json import loads 

3 

4# Packages 

5import bleach 

6import flask 

7from canonicalwebteam.exceptions import ( 

8 StoreApiError, 

9 StoreApiResponseErrorList, 

10) 

11from canonicalwebteam.store_api.dashboard import Dashboard 

12from canonicalwebteam.store_api.devicegw import DeviceGW 

13 

14# Local 

15from webapp import helpers 

16from webapp.helpers import api_session 

17from webapp.decorators import login_required 

18from webapp.markdown import parse_markdown_description 

19from webapp.publisher.snaps import logic, preview_data 

20from webapp.store.logic import ( 

21 filter_screenshots, 

22 get_categories, 

23 get_video, 

24) 

25 

26dashboard = Dashboard(api_session) 

27device_gateway = DeviceGW("snap", api_session) 

28 

29 

30def get_market_snap(snap_name): 

31 return flask.redirect( 

32 flask.url_for(".get_listing_data", snap_name=snap_name) 

33 ) 

34 

35 

36def redirect_post_market_snap(snap_name): 

37 return flask.redirect( 

38 flask.url_for(".post_listing_data", snap_name=snap_name) 

39 ) 

40 

41 

42@login_required 

43def get_listing_data(snap_name): 

44 snap_details = dashboard.get_snap_info(flask.session, snap_name) 

45 

46 details_metrics_enabled = snap_details["public_metrics_enabled"] 

47 details_blacklist = snap_details.get("public_metrics_blacklist", []) 

48 

49 # Filter icon & screenshot urls from the media set. 

50 icon_urls, screenshot_urls, banner_urls = logic.categorise_media( 

51 snap_details["media"] 

52 ) 

53 

54 primary_website = "" 

55 if ( 

56 "website" in snap_details["links"] 

57 and len(snap_details["links"]["website"]) > 0 

58 ): 

59 primary_website = snap_details["links"]["website"][0] 

60 

61 websites = [] 

62 if "website" in snap_details["links"]: 

63 if len(snap_details["links"]["website"]) > 1: 

64 snap_details["links"]["website"].pop(0) 

65 for website in snap_details["links"]["website"]: 

66 websites.append({"url": website}) 

67 

68 def format_links(key): 

69 result = [] 

70 if key in snap_details["links"]: 

71 for url in snap_details["links"][key]: 

72 result.append({"url": url}) 

73 return result 

74 

75 licenses = [] 

76 for license in helpers.get_licenses(): 

77 licenses.append({"key": license["licenseId"], "name": license["name"]}) 

78 

79 license = snap_details["license"] 

80 license_type = "custom" 

81 

82 if " AND " not in license.upper() and " WITH " not in license.upper(): 

83 license_type = "simple" 

84 

85 try: 

86 categories_results = device_gateway.get_categories() 

87 except StoreApiError: 

88 categories_results = [] 

89 

90 categories = sorted( 

91 get_categories(categories_results), 

92 key=lambda category: category["slug"], 

93 ) 

94 

95 snap_categories = logic.replace_reserved_categories_key( 

96 snap_details["categories"] 

97 ) 

98 

99 snap_categories = logic.filter_categories(snap_categories) 

100 

101 snap_categories["categories"] = [ 

102 category["name"] for category in snap_categories["categories"] 

103 ] 

104 

105 filename = "publisher/content/listing_tour.yaml" 

106 tour_steps = helpers.get_yaml(filename, typ="rt") 

107 

108 primary_category = "" 

109 if len(snap_categories["categories"]) > 0: 

110 primary_category = snap_categories["categories"][0] 

111 

112 secondary_category = "" 

113 if len(snap_categories["categories"]) > 1: 

114 secondary_category = snap_categories["categories"][1] 

115 

116 video_urls = None 

117 

118 if len(snap_details["video_urls"]) > 0: 

119 video_urls = snap_details["video_urls"][0] 

120 

121 context = { 

122 "title": snap_details["title"], 

123 "video_urls": video_urls, 

124 "summary": snap_details["summary"], 

125 "description": snap_details["description"], 

126 "categories": categories, 

127 "primary_category": primary_category, 

128 "secondary_category": secondary_category, 

129 "websites": websites, 

130 "contacts": format_links("contact"), 

131 "donations": format_links("donations"), 

132 "source_code": format_links("source"), 

133 "issues": format_links("issues"), 

134 "primary_website": primary_website, 

135 "snap_id": snap_details["snap_id"], 

136 "public_metrics_enabled": details_metrics_enabled, 

137 "public_metrics_blacklist": details_blacklist, 

138 "license": license, 

139 "license_type": license_type, 

140 "licenses": licenses, 

141 "icon_url": icon_urls[0] if icon_urls else None, 

142 "screenshot_urls": screenshot_urls, 

143 "banner_urls": banner_urls, 

144 "update_metadata_on_release": snap_details[ 

145 "update_metadata_on_release" 

146 ], 

147 "tour_steps": tour_steps, 

148 } 

149 

150 res = {} 

151 res["data"] = context 

152 res["message"] = "" 

153 res["success"] = True 

154 

155 return flask.jsonify(res) 

156 

157 

158@login_required 

159def get_listing_snap(snap_name): 

160 snap_details = dashboard.get_snap_info(flask.session, snap_name) 

161 token = "" 

162 if snap_details["links"]["website"]: 

163 token = helpers.get_dns_verification_token( 

164 snap_details["snap_name"], snap_details["links"]["website"][0] 

165 ) 

166 return flask.render_template( 

167 "store/publisher.html", 

168 snap_name=snap_name, 

169 dns_verification_token=token, 

170 ) 

171 

172 

173@login_required 

174def post_listing_data(snap_name): 

175 changes = None 

176 changed_fields = flask.request.form.get("changes") 

177 

178 if changed_fields: 

179 changes = loads(changed_fields) 

180 

181 if changes: 

182 snap_id = flask.request.form.get("snap_id") 

183 error_list = [] 

184 

185 if "images" in changes: 

186 # Add existing screenshots 

187 current_screenshots = dashboard.snap_screenshots( 

188 flask.session, snap_id 

189 ) 

190 

191 icon_input = ( 

192 flask.request.files.get("icon") 

193 if flask.request.files.get("icon") 

194 else None 

195 ) 

196 screenshots_input = [ 

197 s if s else None 

198 for s in flask.request.files.getlist("screenshots") 

199 ] 

200 banner_image_input = ( 

201 flask.request.files.get("banner-image") 

202 if flask.request.files.get("banner-image") 

203 else None 

204 ) 

205 

206 images_json, images_files = logic.build_changed_images( 

207 changes["images"], 

208 current_screenshots, 

209 icon_input, 

210 screenshots_input, 

211 banner_image_input, 

212 ) 

213 

214 try: 

215 dashboard.snap_screenshots( 

216 flask.session, snap_id, images_json, images_files 

217 ) 

218 except StoreApiResponseErrorList as api_response_error_list: 

219 if api_response_error_list.status_code != 404: 

220 error_list = error_list + api_response_error_list.errors 

221 

222 body_json = logic.filter_changes_data(changes) 

223 

224 if body_json: 

225 if "description" in body_json: 

226 body_json["description"] = logic.remove_invalid_characters( 

227 body_json["description"] 

228 ) 

229 

230 try: 

231 dashboard.snap_metadata(flask.session, snap_id, body_json) 

232 except StoreApiResponseErrorList as api_response_error_list: 

233 if api_response_error_list.status_code != 404: 

234 error_list = error_list + api_response_error_list.errors 

235 

236 if error_list: 

237 try: 

238 snap_details = dashboard.get_snap_info( 

239 flask.session, snap_name 

240 ) 

241 except StoreApiResponseErrorList as api_response_error_list: 

242 if api_response_error_list.status_code == 404: 

243 return flask.abort( 

244 404, "No snap named {}".format(snap_name) 

245 ) 

246 else: 

247 error_list = error_list + api_response_error_list.errors 

248 

249 licenses = [] 

250 for license in helpers.get_licenses(): 

251 licenses.append( 

252 {"key": license["licenseId"], "name": license["name"]} 

253 ) 

254 

255 license = snap_details["license"] 

256 

257 snap_categories = logic.replace_reserved_categories_key( 

258 snap_details["categories"] 

259 ) 

260 

261 snap_categories = logic.filter_categories(snap_categories) 

262 

263 res = {"success", True} 

264 

265 return flask.make_response(res, 200) 

266 

267 return flask.make_response({}, 200) 

268 

269 

270@login_required 

271def post_preview(snap_name): 

272 snap_details = dashboard.get_snap_info(flask.session, snap_name) 

273 

274 context = { 

275 "publisher": snap_details["publisher"]["display-name"], 

276 "username": snap_details["publisher"]["username"], 

277 "developer_validation": snap_details["publisher"]["validation"], 

278 "categories": [], 

279 } 

280 

281 state = loads(flask.request.form["state"]) 

282 

283 for item in state: 

284 if item == "description": 

285 context[item] = parse_markdown_description( 

286 bleach.clean(state[item], tags=[]) 

287 ) 

288 else: 

289 context[item] = state[item] 

290 

291 context["is_preview"] = True 

292 context["package_name"] = context["snap_name"] 

293 context["snap_title"] = context["title"] 

294 context["appliances"] = [] 

295 

296 # Images 

297 icon = helpers.get_icon(context["images"]) 

298 context["screenshots"] = filter_screenshots(context["images"]) 

299 context["icon_url"] = icon 

300 

301 if context["video"]: 

302 context["video"] = get_video(context["video"]) 

303 

304 # Channel map 

305 context["channel_map"] = [] 

306 context["default_track"] = "latest" 

307 context["lowest_risk_available"] = "stable" 

308 context["version"] = "test" 

309 context["has_stable"] = True 

310 

311 # metadata 

312 context["last_updated"] = "Preview" 

313 context["filesize"] = "1mb" 

314 

315 # maps 

316 context["countries"] = preview_data.get_countries() 

317 context["normalized_os"] = preview_data.get_normalised_oses() 

318 

319 return flask.render_template("store/snap-details.html", **context)