Coverage for webapp/app.py: 69%

86 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-18 22:11 +0000

1# We import the config module before anything else to make sure env vars are 

2# loaded properly and the FLASK_* prefix is stripped before they are parsed 

3import webapp.config # noqa: F401 

4 

5from flask import render_template, make_response, request, session 

6from dateutil import parser 

7 

8from canonicalwebteam.flask_base.app import FlaskBase 

9 

10from webapp.store_api import publisher_gateway 

11from webapp.extensions import csrf, vite 

12from webapp.config import APP_NAME, VITE_CONFIG 

13from webapp.handlers import set_handlers 

14from webapp.login.views import login 

15from webapp.topics.views import topics 

16from webapp.publisher.views import publisher 

17from webapp.store.views import store 

18from webapp.integrations.views import integrations 

19from webapp.search.views import search 

20from webapp.solutions.views import solutions 

21from webapp.helpers import ( 

22 markdown_to_html, 

23 humanize_date, 

24 format_solution_status, 

25 get_solution_form_value, 

26) 

27from webapp.decorators import login_required 

28from webapp.packages.store_packages import store_packages 

29from opentelemetry.instrumentation.requests import RequestsInstrumentor 

30from opentelemetry.instrumentation.flask import FlaskInstrumentor 

31from opentelemetry.trace import Span 

32 

33 

34app = FlaskBase( 

35 __name__, 

36 "charmhub.io", 

37 template_404="404.html", 

38 template_500="500.html", 

39 favicon_url="https://assets.ubuntu.com/v1/5d4edefd-jaas-favicon.png", 

40 static_folder="../static", 

41 template_folder="../templates", 

42) 

43 

44 

45app.name = APP_NAME 

46app.config["LOGIN_REQUIRED"] = login_required 

47app.config.update(VITE_CONFIG) 

48 

49set_handlers(app) 

50 

51csrf.init_app(app) 

52vite.init_app(app) 

53 

54app.register_blueprint(store_packages) 

55app.register_blueprint(publisher) 

56app.register_blueprint(store) 

57app.register_blueprint(login) 

58app.register_blueprint(topics) 

59app.register_blueprint(integrations) 

60app.register_blueprint(search) 

61app.register_blueprint(solutions) 

62 

63 

64app.jinja_env.filters["markdown"] = markdown_to_html 

65app.jinja_env.filters["humanize_date"] = humanize_date 

66app.jinja_env.filters["format_solution_status"] = format_solution_status 

67app.jinja_env.globals["form_value"] = get_solution_form_value 

68 

69# OpenTelemetry 

70UNTRACED_ROUTES = ["/_status", "/static", ".json"] 

71 

72 

73def request_hook(span: Span, environ): 

74 if span and span.is_recording(): 

75 span.update_name(f"{environ['REQUEST_METHOD']} {environ['PATH_INFO']}") 

76 

77 

78# Add tracing auto instrumentation 

79FlaskInstrumentor().instrument_app( 

80 app, excluded_urls=",".join(UNTRACED_ROUTES), request_hook=request_hook 

81) 

82RequestsInstrumentor().instrument() 

83 

84 

85@app.route("/account.json") 

86def get_account_json(): 

87 """ 

88 A JSON endpoint to request login status 

89 """ 

90 account = None 

91 

92 if "account" in session: 

93 account = session["account"] 

94 

95 response = {"account": account} 

96 response = make_response(response) 

97 response.headers["Cache-Control"] = "no-store" 

98 

99 return response 

100 

101 

102@app.route("/contact-us") 

103def contact_us(): 

104 return render_template("contact-us.html") 

105 

106 

107@app.route("/thank-you") 

108def thank_you(): 

109 return render_template("thank-you.html") 

110 

111 

112@app.route("/icon-validator") 

113def icon_validator(): 

114 return render_template("icon-validator.html") 

115 

116 

117@app.route("/sitemap.xml") 

118def site_map(): 

119 xml_sitemap = render_template( 

120 "sitemap/sitemap.xml", 

121 base_url=f"{request.scheme}://{request.host}", 

122 ) 

123 response = make_response(xml_sitemap) 

124 response.headers["Content-Type"] = "application/xml" 

125 

126 return response 

127 

128 

129@app.route("/sitemap-links.xml") 

130def site_map_links(): 

131 links = [ 

132 "/contact-us", 

133 ] 

134 

135 xml_sitemap = render_template( 

136 "sitemap/sitemap-links.xml", 

137 base_url=f"{request.scheme}://{request.host}", 

138 links=links, 

139 ) 

140 response = make_response(xml_sitemap) 

141 response.headers["Content-Type"] = "application/xml" 

142 

143 return response 

144 

145 

146@app.route("/sitemap-operators.xml") 

147def site_map_operators(): 

148 charms = publisher_gateway.find( 

149 fields=["default-release.channel.released-at"] 

150 ).get("results", []) 

151 

152 for charm in charms: 

153 charm["date"] = ( 

154 parser.parse(charm["default-release"]["channel"]["released-at"]) 

155 .replace(tzinfo=None) 

156 .strftime("%Y-%m-%d") 

157 ) 

158 

159 xml_sitemap = render_template( 

160 "sitemap/sitemap-operators.xml", 

161 base_url=f"{request.scheme}://{request.host}", 

162 charms=charms, 

163 ) 

164 response = make_response(xml_sitemap) 

165 response.headers["Content-Type"] = "application/xml" 

166 

167 return response