Coverage for webapp/app.py: 69%
86 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-18 22:11 +0000
« 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
5from flask import render_template, make_response, request, session
6from dateutil import parser
8from canonicalwebteam.flask_base.app import FlaskBase
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
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)
45app.name = APP_NAME
46app.config["LOGIN_REQUIRED"] = login_required
47app.config.update(VITE_CONFIG)
49set_handlers(app)
51csrf.init_app(app)
52vite.init_app(app)
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)
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
69# OpenTelemetry
70UNTRACED_ROUTES = ["/_status", "/static", ".json"]
73def request_hook(span: Span, environ):
74 if span and span.is_recording():
75 span.update_name(f"{environ['REQUEST_METHOD']} {environ['PATH_INFO']}")
78# Add tracing auto instrumentation
79FlaskInstrumentor().instrument_app(
80 app, excluded_urls=",".join(UNTRACED_ROUTES), request_hook=request_hook
81)
82RequestsInstrumentor().instrument()
85@app.route("/account.json")
86def get_account_json():
87 """
88 A JSON endpoint to request login status
89 """
90 account = None
92 if "account" in session:
93 account = session["account"]
95 response = {"account": account}
96 response = make_response(response)
97 response.headers["Cache-Control"] = "no-store"
99 return response
102@app.route("/contact-us")
103def contact_us():
104 return render_template("contact-us.html")
107@app.route("/thank-you")
108def thank_you():
109 return render_template("thank-you.html")
112@app.route("/icon-validator")
113def icon_validator():
114 return render_template("icon-validator.html")
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"
126 return response
129@app.route("/sitemap-links.xml")
130def site_map_links():
131 links = [
132 "/contact-us",
133 ]
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"
143 return response
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", [])
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 )
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"
167 return response