From 74d04846e8d882628405d78a90f62611879446f4 Mon Sep 17 00:00:00 2001 From: Carlos Wu Date: Wed, 13 Dec 2023 14:13:18 +0000 Subject: [PATCH] Parse query params for /certified --- requirements.txt | 1 + static/js/src/certified-search-results.js | 6 +- .../TestCertification.test_filters_json.yaml | 387 ++++++++++++++---- webapp/certified/views.py | 245 ++++++++--- webapp/handlers.py | 4 + 5 files changed, 487 insertions(+), 156 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9217aeb47f3..dd72ce4c4f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ google-cloud-datastore==2.16.1 django-openid-auth==0.17 time-machine==2.9.0 filelock==3.12.0 +bleach==6.0.0 diff --git a/static/js/src/certified-search-results.js b/static/js/src/certified-search-results.js index 360a3a0ec41..1e6d86eeb1d 100644 --- a/static/js/src/certified-search-results.js +++ b/static/js/src/certified-search-results.js @@ -43,16 +43,16 @@ function retrieveSelectedFilters() { const path = window.location.pathname; const pathCategory = path.replace("/certified/", ""); - let selectedCategories; + let selectedCategories = []; let selectedVendors = urlParams.getAll("vendor"); let selectedReleases = urlParams.getAll("release"); if (pathCategory !== "/certified") { - selectedCategories = urlParams.getAll("category").push(pathCategory); + selectedCategories = urlParams.getAll("category"); + selectedCategories.push(pathCategory); } else { selectedCategories = urlParams.getAll("category"); } - return { category: selectedCategories, vendor: selectedVendors, diff --git a/tests/cassettes/TestCertification.test_filters_json.yaml b/tests/cassettes/TestCertification.test_filters_json.yaml index 744a806596b..3ab05798dbf 100644 --- a/tests/cassettes/TestCertification.test_filters_json.yaml +++ b/tests/cassettes/TestCertification.test_filters_json.yaml @@ -15,50 +15,48 @@ interactions: response: body: string: '{"meta": {"limit": 1000, "next": null, "offset": 0, "previous": null, - "total_count": 8}, "objects": [{"desktops": 199, "laptops": 442, "release": - "20.04 LTS", "resource_uri": "/api/v1/certifiedreleases/20.04%20LTS/", "servers": - 398, "smart_core": 46, "soc": 5, "total": 1097}, {"desktops": 0, "laptops": - 0, "release": "Core 20", "resource_uri": "/api/v1/certifiedreleases/Core%2020/", - "servers": 0, "smart_core": 26, "soc": 0, "total": 26}, {"desktops": 0, "laptops": + "total_count": 9}, "objects": [{"desktops": 5, "laptops": 0, "release": "Core + 22", "resource_uri": "/api/v1/certifiedreleases/Core%2022/", "servers": 0, + "smart_core": 13, "soc": 0, "total": 18}, {"desktops": 204, "laptops": 431, + "release": "20.04 LTS", "resource_uri": "/api/v1/certifiedreleases/20.04%20LTS/", + "servers": 437, "smart_core": 45, "soc": 5, "total": 1142}, {"desktops": 0, + "laptops": 0, "release": "Core 20", "resource_uri": "/api/v1/certifiedreleases/Core%2020/", + "servers": 0, "smart_core": 26, "soc": 0, "total": 26}, {"desktops": 2, "laptops": 0, "release": "Core 18", "resource_uri": "/api/v1/certifiedreleases/Core%2018/", - "servers": 0, "smart_core": 16, "soc": 0, "total": 16}, {"desktops": 115, - "laptops": 258, "release": "18.04 LTS", "resource_uri": "/api/v1/certifiedreleases/18.04%20LTS/", - "servers": 400, "smart_core": 10, "soc": 10, "total": 795}, {"desktops": 0, + "servers": 0, "smart_core": 5, "soc": 0, "total": 7}, {"desktops": 115, "laptops": + 254, "release": "18.04 LTS", "resource_uri": "/api/v1/certifiedreleases/18.04%20LTS/", + "servers": 396, "smart_core": 6, "soc": 10, "total": 791}, {"desktops": 0, "laptops": 0, "release": "18.04", "resource_uri": "/api/v1/certifiedreleases/18.04/", "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 2, "laptops": 0, "release": "16.04 LTS", "resource_uri": "/api/v1/certifiedreleases/16.04%20LTS/", - "servers": 419, "smart_core": 0, "soc": 0, "total": 421}, {"desktops": 0, + "servers": 414, "smart_core": 0, "soc": 0, "total": 420}, {"desktops": 0, "laptops": 0, "release": "Core 16", "resource_uri": "/api/v1/certifiedreleases/Core%2016/", - "servers": 0, "smart_core": 5, "soc": 0, "total": 5}, {"desktops": 91, "laptops": - 139, "release": "22.04 LTS", "resource_uri": "/api/v1/certifiedreleases/22.04%20LTS/", - "servers": 349, "smart_core": 13, "soc": 0, "total": 602}]}' + "servers": 0, "smart_core": 6, "soc": 0, "total": 6}, {"desktops": 115, "laptops": + 213, "release": "22.04 LTS", "resource_uri": "/api/v1/certifiedreleases/22.04%20LTS/", + "servers": 407, "smart_core": 33, "soc": 0, "total": 804}]}' headers: Cache-Control: - no-cache Connection: - - Keep-Alive + - keep-alive Content-Length: - - '1443' + - '1605' Content-Type: - application/json Date: - - Fri, 11 Aug 2023 14:22:41 GMT - Keep-Alive: - - timeout=5, max=100 - Server: - - gunicorn/19.9.0 + - Fri, 15 Dec 2023 09:10:30 GMT Strict-Transport-Security: - - max-age=2592000 + - max-age=15724800; includeSubDomains Vary: - - Accept,Cookie + - Accept, Cookie X-QueryInspect-Duplicate-SQL-Queries: - '0' X-QueryInspect-Num-SQL-Queries: - '2' X-QueryInspect-Total-Request-Time: - - 104 ms + - 100 ms X-QueryInspect-Total-SQL-Time: - - 97 ms + - 86 ms status: code: 200 message: OK @@ -78,127 +76,344 @@ interactions: response: body: string: '{"meta": {"limit": 1000, "next": null, "offset": 0, "previous": null, - "total_count": 46}, "objects": [{"desktops": 139, "laptops": 465, "make": - "Dell", "resource_uri": "/api/v1/certifiedmakes/Dell/", "servers": 2, "smart_core": - 8, "soc": 0, "total": 621}, {"desktops": 115, "laptops": 198, "make": "Lenovo", - "resource_uri": "/api/v1/certifiedmakes/Lenovo/", "servers": 96, "smart_core": - 3, "soc": 0, "total": 413}, {"desktops": 113, "laptops": 141, "make": "HP", - "resource_uri": "/api/v1/certifiedmakes/HP/", "servers": 0, "smart_core": - 0, "soc": 0, "total": 257}, {"desktops": 0, "laptops": 0, "make": "Supermicro", + "total_count": 52}, "objects": [{"desktops": 139, "laptops": 457, "make": + "Dell", "resource_uri": "/api/v1/certifiedmakes/Dell/", "servers": 4, "smart_core": + 7, "soc": 0, "total": 631}, {"desktops": 124, "laptops": 241, "make": "Lenovo", + "resource_uri": "/api/v1/certifiedmakes/Lenovo/", "servers": 102, "smart_core": + 5, "soc": 0, "total": 488}, {"desktops": 129, "laptops": 161, "make": "HP", + "resource_uri": "/api/v1/certifiedmakes/HP/", "servers": 2, "smart_core": + 0, "soc": 0, "total": 305}, {"desktops": 0, "laptops": 0, "make": "Supermicro", "resource_uri": "/api/v1/certifiedmakes/Supermicro/", "servers": 152, "smart_core": 0, "soc": 0, "total": 152}, {"desktops": 0, "laptops": 0, "make": "Dell Technologies", "resource_uri": "/api/v1/certifiedmakes/Dell%20Technologies/", "servers": - 121, "smart_core": 0, "soc": 0, "total": 121}, {"desktops": 0, "laptops": + 119, "smart_core": 0, "soc": 0, "total": 119}, {"desktops": 0, "laptops": 0, "make": "HPE", "resource_uri": "/api/v1/certifiedmakes/HPE/", "servers": - 86, "smart_core": 0, "soc": 0, "total": 86}, {"desktops": 0, "laptops": 0, + 90, "smart_core": 0, "soc": 0, "total": 90}, {"desktops": 0, "laptops": 0, "make": "IBM", "resource_uri": "/api/v1/certifiedmakes/IBM/", "servers": 79, "smart_core": 0, "soc": 0, "total": 79}, {"desktops": 0, "laptops": 0, "make": "ASUSTeK Computer Inc.", "resource_uri": "/api/v1/certifiedmakes/ASUSTeK%20Computer%20Inc./", - "servers": 65, "smart_core": 1, "soc": 0, "total": 66}, {"desktops": 0, "laptops": + "servers": 71, "smart_core": 1, "soc": 0, "total": 72}, {"desktops": 0, "laptops": 0, "make": "Huawei Technologies Co., Ltd.", "resource_uri": "/api/v1/certifiedmakes/Huawei%20Technologies%20Co.,%20Ltd./", "servers": 53, "smart_core": 0, "soc": 6, "total": 59}, {"desktops": 0, "laptops": + 0, "make": "QUANTA Computer Inc", "resource_uri": "/api/v1/certifiedmakes/QUANTA%20Computer%20Inc/", + "servers": 57, "smart_core": 0, "soc": 0, "total": 57}, {"desktops": 0, "laptops": 0, "make": "Fujitsu Limited.", "resource_uri": "/api/v1/certifiedmakes/Fujitsu%20Limited./", "servers": 56, "smart_core": 0, "soc": 0, "total": 56}, {"desktops": 0, "laptops": - 0, "make": "QUANTA Computer Inc", "resource_uri": "/api/v1/certifiedmakes/QUANTA%20Computer%20Inc/", - "servers": 54, "smart_core": 0, "soc": 0, "total": 54}, {"desktops": 0, "laptops": 0, "make": "Cisco UCS", "resource_uri": "/api/v1/certifiedmakes/Cisco%20UCS/", - "servers": 49, "smart_core": 0, "soc": 0, "total": 49}, {"desktops": 0, "laptops": + "servers": 54, "smart_core": 0, "soc": 0, "total": 54}, {"desktops": 0, "laptops": 0, "make": "New H3C Technologies Co., Ltd", "resource_uri": "/api/v1/certifiedmakes/New%20H3C%20Technologies%20Co.,%20Ltd/", - "servers": 44, "smart_core": 0, "soc": 0, "total": 44}, {"desktops": 0, "laptops": + "servers": 51, "smart_core": 0, "soc": 0, "total": 51}, {"desktops": 0, "laptops": 0, "make": "NEC Corporation", "resource_uri": "/api/v1/certifiedmakes/NEC%20Corporation/", "servers": 34, "smart_core": 0, "soc": 0, "total": 34}, {"desktops": 0, "laptops": 0, "make": "Inspur Electronic Information Industry Co., Ltd.", "resource_uri": "/api/v1/certifiedmakes/Inspur%20Electronic%20Information%20Industry%20Co.,%20Ltd./", - "servers": 29, "smart_core": 0, "soc": 0, "total": 29}, {"desktops": 0, "laptops": + "servers": 33, "smart_core": 0, "soc": 0, "total": 33}, {"desktops": 0, "laptops": 0, "make": "xFusion Digital Technologies Co., Ltd.", "resource_uri": "/api/v1/certifiedmakes/xFusion%20Digital%20Technologies%20Co.,%20Ltd./", - "servers": 18, "smart_core": 0, "soc": 0, "total": 18}, {"desktops": 0, "laptops": + "servers": 23, "smart_core": 0, "soc": 0, "total": 23}, {"desktops": 0, "laptops": + 0, "make": "ADVANTECH", "resource_uri": "/api/v1/certifiedmakes/ADVANTECH/", + "servers": 0, "smart_core": 20, "soc": 0, "total": 20}, {"desktops": 0, "laptops": 0, "make": "Giga Computing", "resource_uri": "/api/v1/certifiedmakes/Giga%20Computing/", + "servers": 18, "smart_core": 0, "soc": 0, "total": 18}, {"desktops": 0, "laptops": + 0, "make": "IEIT SYSTEMS Co.,Ltd.", "resource_uri": "/api/v1/certifiedmakes/IEIT%20SYSTEMS%20Co.,Ltd./", "servers": 17, "smart_core": 0, "soc": 0, "total": 17}, {"desktops": 0, "laptops": - 0, "make": "ADVANTECH", "resource_uri": "/api/v1/certifiedmakes/ADVANTECH/", - "servers": 0, "smart_core": 17, "soc": 0, "total": 17}, {"desktops": 5, "laptops": 0, "make": "Raspberry Pi Foundation", "resource_uri": "/api/v1/certifiedmakes/Raspberry%20Pi%20Foundation/", - "servers": 0, "smart_core": 12, "soc": 0, "total": 17}, {"desktops": 0, "laptops": + "servers": 0, "smart_core": 15, "soc": 0, "total": 15}, {"desktops": 0, "laptops": 0, "make": "Ericsson, Inc.", "resource_uri": "/api/v1/certifiedmakes/Ericsson,%20Inc./", "servers": 11, "smart_core": 0, "soc": 0, "total": 11}, {"desktops": 0, "laptops": 0, "make": "Kontron", "resource_uri": "/api/v1/certifiedmakes/Kontron/", "servers": - 7, "smart_core": 0, "soc": 0, "total": 7}, {"desktops": 0, "laptops": 0, "make": + 7, "smart_core": 0, "soc": 0, "total": 7}, {"desktops": 2, "laptops": 0, "make": "Intel Corp.", "resource_uri": "/api/v1/certifiedmakes/Intel%20Corp./", "servers": - 0, "smart_core": 6, "soc": 0, "total": 6}, {"desktops": 0, "laptops": 0, "make": - "Cavium, Inc.", "resource_uri": "/api/v1/certifiedmakes/Cavium,%20Inc./", + 0, "smart_core": 4, "soc": 0, "total": 6}, {"desktops": 0, "laptops": 0, "make": + "nVidia", "resource_uri": "/api/v1/certifiedmakes/nVidia/", "servers": 4, + "smart_core": 0, "soc": 0, "total": 5}, {"desktops": 2, "laptops": 0, "make": + "Xilinx", "resource_uri": "/api/v1/certifiedmakes/Xilinx/", "servers": 0, + "smart_core": 3, "soc": 0, "total": 5}, {"desktops": 0, "laptops": 0, "make": + "AAEON Technology Inc.", "resource_uri": "/api/v1/certifiedmakes/AAEON%20Technology%20Inc./", + "servers": 0, "smart_core": 5, "soc": 0, "total": 5}, {"desktops": 0, "laptops": + 0, "make": "ADLink Technology, Inc.", "resource_uri": "/api/v1/certifiedmakes/ADLink%20Technology,%20Inc./", + "servers": 0, "smart_core": 5, "soc": 0, "total": 5}, {"desktops": 0, "laptops": + 0, "make": "Cavium, Inc.", "resource_uri": "/api/v1/certifiedmakes/Cavium,%20Inc./", "servers": 3, "smart_core": 0, "soc": 2, "total": 5}, {"desktops": 0, "laptops": - 0, "make": "Xilinx", "resource_uri": "/api/v1/certifiedmakes/Xilinx/", "servers": - 0, "smart_core": 5, "soc": 0, "total": 5}, {"desktops": 0, "laptops": 0, "make": + 0, "make": "DFI", "resource_uri": "/api/v1/certifiedmakes/DFI/", "servers": + 0, "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": "OnLogic", "resource_uri": "/api/v1/certifiedmakes/OnLogic/", "servers": 0, "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": - "DFI", "resource_uri": "/api/v1/certifiedmakes/DFI/", "servers": 0, "smart_core": - 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": "nVidia", - "resource_uri": "/api/v1/certifiedmakes/nVidia/", "servers": 3, "smart_core": - 0, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": "AAEON Technology - Inc.", "resource_uri": "/api/v1/certifiedmakes/AAEON%20Technology%20Inc./", - "servers": 0, "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": - 0, "make": "Honeywell, Inc.", "resource_uri": "/api/v1/certifiedmakes/Honeywell,%20Inc./", + "Rigado", "resource_uri": "/api/v1/certifiedmakes/Rigado/", "servers": 0, + "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": + "Eurotech", "resource_uri": "/api/v1/certifiedmakes/Eurotech/", "servers": + 0, "smart_core": 3, "soc": 0, "total": 3}, {"desktops": 0, "laptops": 0, "make": + "Honeywell, Inc.", "resource_uri": "/api/v1/certifiedmakes/Honeywell,%20Inc./", "servers": 0, "smart_core": 3, "soc": 0, "total": 3}, {"desktops": 0, "laptops": - 0, "make": "Mellanox Technologies", "resource_uri": "/api/v1/certifiedmakes/Mellanox%20Technologies/", + 0, "make": "Fujitsu", "resource_uri": "/api/v1/certifiedmakes/Fujitsu/", "servers": + 3, "smart_core": 0, "soc": 0, "total": 3}, {"desktops": 0, "laptops": 0, "make": + "Mellanox Technologies", "resource_uri": "/api/v1/certifiedmakes/Mellanox%20Technologies/", "servers": 0, "smart_core": 1, "soc": 0, "total": 3}, {"desktops": 0, "laptops": - 0, "make": "ASRock Industrial", "resource_uri": "/api/v1/certifiedmakes/ASRock%20Industrial/", - "servers": 0, "smart_core": 2, "soc": 0, "total": 2}, {"desktops": 0, "laptops": 0, "make": "Penguin Computing", "resource_uri": "/api/v1/certifiedmakes/Penguin%20Computing/", "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 0, "laptops": - 0, "make": "Ampere Computing, LLC", "resource_uri": "/api/v1/certifiedmakes/Ampere%20Computing,%20LLC/", - "servers": 0, "smart_core": 0, "soc": 2, "total": 2}, {"desktops": 0, "laptops": - 0, "make": "Eurotech", "resource_uri": "/api/v1/certifiedmakes/Eurotech/", + 0, "make": "ASRock Industrial", "resource_uri": "/api/v1/certifiedmakes/ASRock%20Industrial/", "servers": 0, "smart_core": 2, "soc": 0, "total": 2}, {"desktops": 0, "laptops": 0, "make": "Wiwynn", "resource_uri": "/api/v1/certifiedmakes/Wiwynn/", "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 0, "laptops": 0, "make": - "GIGA-BYTE TECHNOLOGY CO., LTD.", "resource_uri": "/api/v1/certifiedmakes/GIGA-BYTE%20TECHNOLOGY%20CO.,%20LTD./", - "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 0, "laptops": + "Ampere Computing, LLC", "resource_uri": "/api/v1/certifiedmakes/Ampere%20Computing,%20LLC/", + "servers": 0, "smart_core": 0, "soc": 2, "total": 2}, {"desktops": 1, "laptops": + 0, "make": "Dell Computer Corporation", "resource_uri": "/api/v1/certifiedmakes/Dell%20Computer%20Corporation/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 2}, {"desktops": 0, "laptops": + 0, "make": "GIGA-BYTE TECHNOLOGY CO., LTD.", "resource_uri": "/api/v1/certifiedmakes/GIGA-BYTE%20TECHNOLOGY%20CO.,%20LTD./", + "servers": 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "FETCi", "resource_uri": "/api/v1/certifiedmakes/FETCi/", "servers": + 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": + "Tyrone Systems", "resource_uri": "/api/v1/certifiedmakes/Tyrone%20Systems/", + "servers": 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "Avnet IoT Gateway", "resource_uri": "/api/v1/certifiedmakes/Avnet%20IoT%20Gateway/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 1, "laptops": + 0, "make": "IEI", "resource_uri": "/api/v1/certifiedmakes/IEI/", "servers": + 0, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": + "ZTE", "resource_uri": "/api/v1/certifiedmakes/ZTE/", "servers": 1, "smart_core": + 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": "Axiomtek", + "resource_uri": "/api/v1/certifiedmakes/Axiomtek/", "servers": 0, "smart_core": + 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": "Open Compute + Project", "resource_uri": "/api/v1/certifiedmakes/Open%20Compute%20Project/", + "servers": 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "InoNet Computer GmbH", "resource_uri": "/api/v1/certifiedmakes/InoNet%20Computer%20GmbH/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "Element Biosciences", "resource_uri": "/api/v1/certifiedmakes/Element%20Biosciences/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "Asus", "resource_uri": "/api/v1/certifiedmakes/Asus/", "servers": + 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": + "Interactive Strength Inc", "resource_uri": "/api/v1/certifiedmakes/Interactive%20Strength%20Inc/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 1}]}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Length: + - '9068' + Content-Type: + - application/json + Date: + - Fri, 15 Dec 2023 09:10:30 GMT + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept, Cookie + X-QueryInspect-Duplicate-SQL-Queries: + - '0' + X-QueryInspect-Num-SQL-Queries: + - '2' + X-QueryInspect-Total-Request-Time: + - 137 ms + X-QueryInspect-Total-SQL-Time: + - 98 ms + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.28.2 + method: GET + uri: https://certification.canonical.com/api/v1/certifiedreleases/?format=json&limit=0 + response: + body: + string: '{"meta": {"limit": 1000, "next": null, "offset": 0, "previous": null, + "total_count": 9}, "objects": [{"desktops": 5, "laptops": 0, "release": "Core + 22", "resource_uri": "/api/v1/certifiedreleases/Core%2022/", "servers": 0, + "smart_core": 13, "soc": 0, "total": 18}, {"desktops": 204, "laptops": 431, + "release": "20.04 LTS", "resource_uri": "/api/v1/certifiedreleases/20.04%20LTS/", + "servers": 437, "smart_core": 45, "soc": 5, "total": 1142}, {"desktops": 0, + "laptops": 0, "release": "Core 20", "resource_uri": "/api/v1/certifiedreleases/Core%2020/", + "servers": 0, "smart_core": 26, "soc": 0, "total": 26}, {"desktops": 2, "laptops": + 0, "release": "Core 18", "resource_uri": "/api/v1/certifiedreleases/Core%2018/", + "servers": 0, "smart_core": 5, "soc": 0, "total": 7}, {"desktops": 115, "laptops": + 254, "release": "18.04 LTS", "resource_uri": "/api/v1/certifiedreleases/18.04%20LTS/", + "servers": 396, "smart_core": 6, "soc": 10, "total": 791}, {"desktops": 0, + "laptops": 0, "release": "18.04", "resource_uri": "/api/v1/certifiedreleases/18.04/", + "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 2, "laptops": + 0, "release": "16.04 LTS", "resource_uri": "/api/v1/certifiedreleases/16.04%20LTS/", + "servers": 414, "smart_core": 0, "soc": 0, "total": 420}, {"desktops": 0, + "laptops": 0, "release": "Core 16", "resource_uri": "/api/v1/certifiedreleases/Core%2016/", + "servers": 0, "smart_core": 6, "soc": 0, "total": 6}, {"desktops": 115, "laptops": + 213, "release": "22.04 LTS", "resource_uri": "/api/v1/certifiedreleases/22.04%20LTS/", + "servers": 407, "smart_core": 33, "soc": 0, "total": 804}]}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Length: + - '1605' + Content-Type: + - application/json + Date: + - Fri, 15 Dec 2023 09:10:30 GMT + Strict-Transport-Security: + - max-age=15724800; includeSubDomains + Vary: + - Accept, Cookie + X-QueryInspect-Duplicate-SQL-Queries: + - '0' + X-QueryInspect-Num-SQL-Queries: + - '2' + X-QueryInspect-Total-Request-Time: + - 101 ms + X-QueryInspect-Total-SQL-Time: + - 93 ms + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - python-requests/2.28.2 + method: GET + uri: https://certification.canonical.com/api/v1/certifiedmakes/?format=json&limit=0 + response: + body: + string: '{"meta": {"limit": 1000, "next": null, "offset": 0, "previous": null, + "total_count": 52}, "objects": [{"desktops": 139, "laptops": 457, "make": + "Dell", "resource_uri": "/api/v1/certifiedmakes/Dell/", "servers": 4, "smart_core": + 7, "soc": 0, "total": 631}, {"desktops": 124, "laptops": 241, "make": "Lenovo", + "resource_uri": "/api/v1/certifiedmakes/Lenovo/", "servers": 102, "smart_core": + 5, "soc": 0, "total": 488}, {"desktops": 129, "laptops": 161, "make": "HP", + "resource_uri": "/api/v1/certifiedmakes/HP/", "servers": 2, "smart_core": + 0, "soc": 0, "total": 305}, {"desktops": 0, "laptops": 0, "make": "Supermicro", + "resource_uri": "/api/v1/certifiedmakes/Supermicro/", "servers": 152, "smart_core": + 0, "soc": 0, "total": 152}, {"desktops": 0, "laptops": 0, "make": "Dell Technologies", + "resource_uri": "/api/v1/certifiedmakes/Dell%20Technologies/", "servers": + 119, "smart_core": 0, "soc": 0, "total": 119}, {"desktops": 0, "laptops": + 0, "make": "HPE", "resource_uri": "/api/v1/certifiedmakes/HPE/", "servers": + 90, "smart_core": 0, "soc": 0, "total": 90}, {"desktops": 0, "laptops": 0, + "make": "IBM", "resource_uri": "/api/v1/certifiedmakes/IBM/", "servers": 79, + "smart_core": 0, "soc": 0, "total": 79}, {"desktops": 0, "laptops": 0, "make": + "ASUSTeK Computer Inc.", "resource_uri": "/api/v1/certifiedmakes/ASUSTeK%20Computer%20Inc./", + "servers": 71, "smart_core": 1, "soc": 0, "total": 72}, {"desktops": 0, "laptops": + 0, "make": "Huawei Technologies Co., Ltd.", "resource_uri": "/api/v1/certifiedmakes/Huawei%20Technologies%20Co.,%20Ltd./", + "servers": 53, "smart_core": 0, "soc": 6, "total": 59}, {"desktops": 0, "laptops": + 0, "make": "QUANTA Computer Inc", "resource_uri": "/api/v1/certifiedmakes/QUANTA%20Computer%20Inc/", + "servers": 57, "smart_core": 0, "soc": 0, "total": 57}, {"desktops": 0, "laptops": + 0, "make": "Fujitsu Limited.", "resource_uri": "/api/v1/certifiedmakes/Fujitsu%20Limited./", + "servers": 56, "smart_core": 0, "soc": 0, "total": 56}, {"desktops": 0, "laptops": + 0, "make": "Cisco UCS", "resource_uri": "/api/v1/certifiedmakes/Cisco%20UCS/", + "servers": 54, "smart_core": 0, "soc": 0, "total": 54}, {"desktops": 0, "laptops": + 0, "make": "New H3C Technologies Co., Ltd", "resource_uri": "/api/v1/certifiedmakes/New%20H3C%20Technologies%20Co.,%20Ltd/", + "servers": 51, "smart_core": 0, "soc": 0, "total": 51}, {"desktops": 0, "laptops": + 0, "make": "NEC Corporation", "resource_uri": "/api/v1/certifiedmakes/NEC%20Corporation/", + "servers": 34, "smart_core": 0, "soc": 0, "total": 34}, {"desktops": 0, "laptops": + 0, "make": "Inspur Electronic Information Industry Co., Ltd.", "resource_uri": + "/api/v1/certifiedmakes/Inspur%20Electronic%20Information%20Industry%20Co.,%20Ltd./", + "servers": 33, "smart_core": 0, "soc": 0, "total": 33}, {"desktops": 0, "laptops": + 0, "make": "xFusion Digital Technologies Co., Ltd.", "resource_uri": "/api/v1/certifiedmakes/xFusion%20Digital%20Technologies%20Co.,%20Ltd./", + "servers": 23, "smart_core": 0, "soc": 0, "total": 23}, {"desktops": 0, "laptops": + 0, "make": "ADVANTECH", "resource_uri": "/api/v1/certifiedmakes/ADVANTECH/", + "servers": 0, "smart_core": 20, "soc": 0, "total": 20}, {"desktops": 0, "laptops": + 0, "make": "Giga Computing", "resource_uri": "/api/v1/certifiedmakes/Giga%20Computing/", + "servers": 18, "smart_core": 0, "soc": 0, "total": 18}, {"desktops": 0, "laptops": + 0, "make": "IEIT SYSTEMS Co.,Ltd.", "resource_uri": "/api/v1/certifiedmakes/IEIT%20SYSTEMS%20Co.,Ltd./", + "servers": 17, "smart_core": 0, "soc": 0, "total": 17}, {"desktops": 0, "laptops": + 0, "make": "Raspberry Pi Foundation", "resource_uri": "/api/v1/certifiedmakes/Raspberry%20Pi%20Foundation/", + "servers": 0, "smart_core": 15, "soc": 0, "total": 15}, {"desktops": 0, "laptops": + 0, "make": "Ericsson, Inc.", "resource_uri": "/api/v1/certifiedmakes/Ericsson,%20Inc./", + "servers": 11, "smart_core": 0, "soc": 0, "total": 11}, {"desktops": 0, "laptops": + 0, "make": "Kontron", "resource_uri": "/api/v1/certifiedmakes/Kontron/", "servers": + 7, "smart_core": 0, "soc": 0, "total": 7}, {"desktops": 2, "laptops": 0, "make": + "Intel Corp.", "resource_uri": "/api/v1/certifiedmakes/Intel%20Corp./", "servers": + 0, "smart_core": 4, "soc": 0, "total": 6}, {"desktops": 0, "laptops": 0, "make": + "nVidia", "resource_uri": "/api/v1/certifiedmakes/nVidia/", "servers": 4, + "smart_core": 0, "soc": 0, "total": 5}, {"desktops": 2, "laptops": 0, "make": + "Xilinx", "resource_uri": "/api/v1/certifiedmakes/Xilinx/", "servers": 0, + "smart_core": 3, "soc": 0, "total": 5}, {"desktops": 0, "laptops": 0, "make": + "AAEON Technology Inc.", "resource_uri": "/api/v1/certifiedmakes/AAEON%20Technology%20Inc./", + "servers": 0, "smart_core": 5, "soc": 0, "total": 5}, {"desktops": 0, "laptops": 0, "make": "ADLink Technology, Inc.", "resource_uri": "/api/v1/certifiedmakes/ADLink%20Technology,%20Inc./", - "servers": 0, "smart_core": 2, "soc": 0, "total": 2}, {"desktops": 0, "laptops": + "servers": 0, "smart_core": 5, "soc": 0, "total": 5}, {"desktops": 0, "laptops": + 0, "make": "Cavium, Inc.", "resource_uri": "/api/v1/certifiedmakes/Cavium,%20Inc./", + "servers": 3, "smart_core": 0, "soc": 2, "total": 5}, {"desktops": 0, "laptops": + 0, "make": "DFI", "resource_uri": "/api/v1/certifiedmakes/DFI/", "servers": + 0, "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": + "OnLogic", "resource_uri": "/api/v1/certifiedmakes/OnLogic/", "servers": 0, + "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": + "Rigado", "resource_uri": "/api/v1/certifiedmakes/Rigado/", "servers": 0, + "smart_core": 4, "soc": 0, "total": 4}, {"desktops": 0, "laptops": 0, "make": + "Eurotech", "resource_uri": "/api/v1/certifiedmakes/Eurotech/", "servers": + 0, "smart_core": 3, "soc": 0, "total": 3}, {"desktops": 0, "laptops": 0, "make": + "Honeywell, Inc.", "resource_uri": "/api/v1/certifiedmakes/Honeywell,%20Inc./", + "servers": 0, "smart_core": 3, "soc": 0, "total": 3}, {"desktops": 0, "laptops": 0, "make": "Fujitsu", "resource_uri": "/api/v1/certifiedmakes/Fujitsu/", "servers": + 3, "smart_core": 0, "soc": 0, "total": 3}, {"desktops": 0, "laptops": 0, "make": + "Mellanox Technologies", "resource_uri": "/api/v1/certifiedmakes/Mellanox%20Technologies/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 3}, {"desktops": 0, "laptops": + 0, "make": "Penguin Computing", "resource_uri": "/api/v1/certifiedmakes/Penguin%20Computing/", + "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 0, "laptops": + 0, "make": "ASRock Industrial", "resource_uri": "/api/v1/certifiedmakes/ASRock%20Industrial/", + "servers": 0, "smart_core": 2, "soc": 0, "total": 2}, {"desktops": 0, "laptops": + 0, "make": "Wiwynn", "resource_uri": "/api/v1/certifiedmakes/Wiwynn/", "servers": 2, "smart_core": 0, "soc": 0, "total": 2}, {"desktops": 0, "laptops": 0, "make": - "Interactive Strength Inc", "resource_uri": "/api/v1/certifiedmakes/Interactive%20Strength%20Inc/", - "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + "Ampere Computing, LLC", "resource_uri": "/api/v1/certifiedmakes/Ampere%20Computing,%20LLC/", + "servers": 0, "smart_core": 0, "soc": 2, "total": 2}, {"desktops": 1, "laptops": + 0, "make": "Dell Computer Corporation", "resource_uri": "/api/v1/certifiedmakes/Dell%20Computer%20Corporation/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 2}, {"desktops": 0, "laptops": + 0, "make": "GIGA-BYTE TECHNOLOGY CO., LTD.", "resource_uri": "/api/v1/certifiedmakes/GIGA-BYTE%20TECHNOLOGY%20CO.,%20LTD./", + "servers": 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "FETCi", "resource_uri": "/api/v1/certifiedmakes/FETCi/", "servers": + 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": + "Tyrone Systems", "resource_uri": "/api/v1/certifiedmakes/Tyrone%20Systems/", + "servers": 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": "Avnet IoT Gateway", "resource_uri": "/api/v1/certifiedmakes/Avnet%20IoT%20Gateway/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 1, "laptops": + 0, "make": "IEI", "resource_uri": "/api/v1/certifiedmakes/IEI/", "servers": + 0, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": + "ZTE", "resource_uri": "/api/v1/certifiedmakes/ZTE/", "servers": 1, "smart_core": + 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": "Axiomtek", + "resource_uri": "/api/v1/certifiedmakes/Axiomtek/", "servers": 0, "smart_core": + 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": "Open Compute + Project", "resource_uri": "/api/v1/certifiedmakes/Open%20Compute%20Project/", + "servers": 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": + 0, "make": "InoNet Computer GmbH", "resource_uri": "/api/v1/certifiedmakes/InoNet%20Computer%20GmbH/", "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": - 0, "make": "ZTE", "resource_uri": "/api/v1/certifiedmakes/ZTE/", "servers": - 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": - "Element Biosciences", "resource_uri": "/api/v1/certifiedmakes/Element%20Biosciences/", + 0, "make": "Element Biosciences", "resource_uri": "/api/v1/certifiedmakes/Element%20Biosciences/", "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": - 0, "make": "FETCi", "resource_uri": "/api/v1/certifiedmakes/FETCi/", "servers": - 1, "smart_core": 0, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": - "Rigado", "resource_uri": "/api/v1/certifiedmakes/Rigado/", "servers": 0, - "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": - "Axiomtek", "resource_uri": "/api/v1/certifiedmakes/Axiomtek/", "servers": + 0, "make": "Asus", "resource_uri": "/api/v1/certifiedmakes/Asus/", "servers": 0, "smart_core": 1, "soc": 0, "total": 1}, {"desktops": 0, "laptops": 0, "make": - "Open Compute Project", "resource_uri": "/api/v1/certifiedmakes/Open%20Compute%20Project/", - "servers": 1, "smart_core": 0, "soc": 0, "total": 1}]}' + "Interactive Strength Inc", "resource_uri": "/api/v1/certifiedmakes/Interactive%20Strength%20Inc/", + "servers": 0, "smart_core": 1, "soc": 0, "total": 1}]}' headers: Cache-Control: - no-cache Connection: - - Keep-Alive + - keep-alive Content-Length: - - '8037' + - '9068' Content-Type: - application/json Date: - - Fri, 11 Aug 2023 14:22:41 GMT - Keep-Alive: - - timeout=5, max=99 - Server: - - gunicorn/19.9.0 + - Fri, 15 Dec 2023 09:10:31 GMT Strict-Transport-Security: - - max-age=2592000 + - max-age=15724800; includeSubDomains Vary: - - Accept,Cookie + - Accept, Cookie X-QueryInspect-Duplicate-SQL-Queries: - '0' X-QueryInspect-Num-SQL-Queries: - '2' X-QueryInspect-Total-Request-Time: - - 100 ms + - 148 ms X-QueryInspect-Total-SQL-Time: - - 81 ms + - 113 ms status: code: 200 message: OK diff --git a/webapp/certified/views.py b/webapp/certified/views.py index 76a488028f7..e6ee2b450ea 100644 --- a/webapp/certified/views.py +++ b/webapp/certified/views.py @@ -2,7 +2,7 @@ import talisker.sentry import requests import math -import flask +import bleach from flask import ( request, @@ -10,6 +10,8 @@ abort, current_app, redirect, + jsonify, + url_for, ) from requests import Session from webapp.certified.api import CertificationAPI, PartnersAPI @@ -80,6 +82,75 @@ def certified_routes(app): ) +def _parse_query_params(all_releases, all_vendors): + new_query_params = {} + if request.args.get("q") or request.args.get("q") == "": + new_query_params["q"] = [request.args.get("q")] + + if request.args.getlist("category"): + category_params = [] + # These include UX replacements + # Filters, navigation and pathnames + for category in [ + "Laptop", + "Desktop", + "Server", + "IoT", + "SoC", + "laptops", + "desktops", + "servers", + "iot", + "socs", + ]: + for item in request.args.getlist("category"): + if request.args["category"] == "Ubuntu Core": + item = "IoT" + if request.args["category"] == "Server SoC": + item = "SoC" + if item == category: + new_query_params["category"] = category_params.append( + category + ) + new_query_params["category"] = category_params + + if request.args.getlist("vendor"): + vendor_params = [] + for vendor in all_vendors: + for item in request.args.getlist("vendor"): + if item == vendor: + vendor_params.append(vendor) + new_query_params["vendor"] = vendor_params + + if request.args.getlist("release"): + release_params = [] + for release in all_releases: + for item in request.args.getlist("release"): + if item == release: + release_params.append(release) + new_query_params["release"] = release_params + + if request.args.get("limit"): + new_query_params["limit"] = [request.args.get("limit")] + + if request.args.get("offset"): + new_query_params["offset"] = [request.args.get("offset")] + + if request.args.get("vendors_limit"): + new_query_params["vendors_limit"] = [request.args.get("vendors_limit")] + + if request.args.get("releases_limit"): + new_query_params["releases_limit"] = [ + request.args.get("releases_limit") + ] + + if new_query_params == request.args.to_dict(flat=False): + # No parsing was done + return None + else: + return new_query_params + + def get_vendors_releases_filters(): categories = request.args.getlist("category") selected_vendors = request.args.getlist("vendor") @@ -90,74 +161,104 @@ def get_vendors_releases_filters(): certified_releases = api.certified_releases(limit="0")["objects"] certified_makes = api.certified_makes(limit="0")["objects"] - vendor_filters = [] - release_filters = [] - - if len(categories) == 0: - categories = ["smart_core", "soc", "laptops", "desktops", "servers"] - - for cat in categories: - cat = cat.lower() - # pathname replacements - if cat == "iot": - cat = "smart_core" - elif cat == "ubuntu core": - cat = "smart_core" - elif cat == "socs": - cat = "soc" - elif cat == "laptop": - cat = "laptops" - elif cat == "desktop": - cat = "desktops" - elif cat == "server": - cat = "servers" - elif cat == "server soc": - cat = "soc" - - for vendor in certified_makes: - make = vendor["make"] - - if ( - int(vendor[cat]) > 0 - and make not in vendor_filters - and make not in selected_vendors - ): - vendor_filters.append(make) - - for release in certified_releases: - version = release["release"] + ( + laptop_releases, + laptop_vendors, + desktop_releases, + desktop_vendors, + soc_releases, + soc_vendors, + iot_releases, + iot_vendors, + server_releases, + server_vendors, + all_releases, + all_vendors, + vendors, + releases, + ) = get_filters(request.args) - if ( - int(release[cat]) > 0 - and version not in release_filters - and version != "18.04" - and version not in selected_releases - ): - release_filters.append(version) + new_certified_params = _parse_query_params(vendors, releases) + if not new_certified_params: + vendor_filters = [] + release_filters = [] + + if len(categories) == 0: + categories = [ + "smart_core", + "soc", + "laptops", + "desktops", + "servers", + ] + + for cat in categories: + cat = cat.lower() + # pathname replacements + if cat == "iot": + cat = "smart_core" + elif cat == "ubuntu core": + cat = "smart_core" + elif cat == "socs": + cat = "soc" + elif cat == "laptop": + cat = "laptops" + elif cat == "desktop": + cat = "desktops" + elif cat == "server": + cat = "servers" + elif cat == "server soc": + cat = "soc" + + for vendor in certified_makes: + make = vendor["make"] + + if ( + int(vendor[cat]) > 0 + and make not in vendor_filters + and make not in selected_vendors + ): + vendor_filters.append(make) + + for release in certified_releases: + version = release["release"] + + if ( + int(release[cat]) > 0 + and version not in release_filters + and version != "18.04" + and version not in selected_releases + ): + release_filters.append(version) - # Reorder and put selected filters on top - vendor_filters.sort() - selected_vendors.extend(vendor_filters) - vendor_filters = selected_vendors - release_filters.sort(reverse=True) - selected_releases.extend(release_filters) - release_filters = selected_releases + # Reorder and put selected filters on top + vendor_filters.sort() + selected_vendors.extend(vendor_filters) + vendor_filters = selected_vendors + release_filters.sort(reverse=True) + selected_releases.extend(release_filters) + release_filters = selected_releases - total_vendors = len(vendor_filters) - total_releases = len(release_filters) + total_vendors = len(vendor_filters) + total_releases = len(release_filters) - if vendors_limit != -1: - vendor_filters = vendor_filters[:vendors_limit] + if vendors_limit != -1: + vendor_filters = vendor_filters[:vendors_limit] - if releases_limit != -1: - release_filters = release_filters[:releases_limit] + if releases_limit != -1: + release_filters = release_filters[:releases_limit] - filters = { - "vendor_filters": {"data": vendor_filters, "total": total_vendors}, - "release_filters": {"data": release_filters, "total": total_releases}, - } + filters = { + "vendor_filters": {"data": vendor_filters, "total": total_vendors}, + "release_filters": { + "data": release_filters, + "total": total_releases, + }, + } - return flask.jsonify(filters) + return jsonify(filters) + else: + return redirect(url_for(request.endpoint, **new_certified_params)) def get_filters(request_args=None, json: bool = False): @@ -278,7 +379,7 @@ def get_filters(request_args=None, json: bool = False): "vendor_filters": sorted(vendor_filters), "release_filters": sorted(release_filters, reverse=True), } - return flask.jsonify(filters) + return jsonify(filters) else: return ( @@ -491,6 +592,11 @@ def certified_home(): release_filters, ) = get_filters(request.args) + # Parse url + new_certified_params = _parse_query_params(release_filters, vendor_filters) + if new_certified_params: + return redirect(url_for(request.endpoint, **new_certified_params)) + if ( "category" in request.args and len(request.args.getlist("category")) == 1 @@ -607,10 +713,10 @@ def create_category_views(category, template_path): template_path -- full template path (e.g. certified/search-results.html) """ if len(request.args.getlist("category")) > 1: + url = f"/certified?{request.query_string.decode()}&category={category}" + clean_url = bleach.clean(url, tags=[], strip=True) # UX requirement - return redirect( - f"/certified?{request.query_string.decode()}&category={category}" - ) + return redirect(clean_url) if category == "Desktop": certified_releases = api.certified_releases( @@ -681,6 +787,11 @@ def create_category_views(category, template_path): limit = request.args.get("limit", default=20, type=int) offset = request.args.get("offset", default=0, type=int) + # Parse url + new_cert_params = _parse_query_params(release_filters, vendor_filters) + if new_cert_params: + return redirect(url_for(request.endpoint, **new_cert_params)) + releases = ( ",".join(request.args.getlist("release")) if request.args.getlist("release") @@ -768,7 +879,7 @@ def certified_vendors(vendor): vendor_data = partners_data[0] except Exception: # Most likely all exceptions are related to not having data - return flask.redirect("/certified?q=" + vendor) + return redirect("/certified?q=" + vendor) # Pagination limit = request.args.get("limit", default=20, type=int) diff --git a/webapp/handlers.py b/webapp/handlers.py index c84d50e4aef..6ff41e7671b 100644 --- a/webapp/handlers.py +++ b/webapp/handlers.py @@ -47,6 +47,10 @@ def cache_headers(response): if flask.request.path.startswith(disable_cache_on): response.cache_control.no_store = True + # Prevent XSS + if flask.request.path.startswith("/certified"): + response.headers["X-Frame-Options"] = "DENY" + return response # Error pages