)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"c276d1e4f8564b5e1d2b4ef27c2cbd3d32618e53","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"2c99575a_e7bb15bc","updated":"2025-11-17 13:19:25.000000000","message":"recheck","commit_id":"28e4be1ee2a73f7ad1469a7fc08f40e884d6f914"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"e22466e11c724b5f214ba66306a72c6d8ad502bf","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"41452c6e_a7546c9d","updated":"2025-11-18 15:30:46.000000000","message":"recheck","commit_id":"db25601446a5b58c358483f1528b4b342f8be396"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"896eea11b424f8d0e3ec7d095f2d2cd36a845441","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"068c70e2_2ddfeb9e","updated":"2026-01-20 15:44:12.000000000","message":"recheck","commit_id":"d0ea0174c4c0c82065972cd029a2eed2dc2a2363"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"606a9ea8d13548fc75dfc386ce302cbd4eaa794e","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"95665fcc_cad9548d","updated":"2026-01-20 20:08:06.000000000","message":"recheck","commit_id":"d0ea0174c4c0c82065972cd029a2eed2dc2a2363"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"8828d643f8da6dbb877bb0f1ff6a74141b72be31","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":15,"id":"8ef7a036_7b64b89d","updated":"2026-02-10 10:31:37.000000000","message":"I have tried this, but I never get redirected to a download page after key creation.","commit_id":"356e7c2f0fbb5d354a7cc858bcb020d59b40a067"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"d82fed80e37b3dd5d481f47f2ad01a927a573981","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":17,"id":"c9015de0_2fde80c2","updated":"2026-03-10 14:23:43.000000000","message":"recheck gate fixed","commit_id":"f549ebf127e6e67fbd6fd61a2766b038208dfbd0"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"87fe2d2b4762fdee0bb48386e928bb5933181855","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":20,"id":"1c0ee902_6b24a9c8","updated":"2026-03-11 02:33:18.000000000","message":"NOTE: patchset 19 contains changes that I have reviewed and moved from experimental review https://review.opendev.org/c/openstack/horizon/+/976512\nopenstack_dashboard/dashboards/project/key_pairs/forms.py\nopenstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/private_key.pem\nopenstack_dashboard/dashboards/project/key_pairs/tests.py\nopenstack_dashboard/dashboards/project/key_pairs/urls.py\nopenstack_dashboard/dashboards/project/key_pairs/views.py\n\nThese changes removed using two steps for triggering the file download.","commit_id":"478907fc70e4bd496e86183850684c3cc9877ed8"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"a4f0549a0bd5ca373e4503bfd02aea5abb02a60c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":23,"id":"445756ed_159d0e60","updated":"2026-03-13 07:32:52.000000000","message":"Looks good, thank you, one small nitpick.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"9125a38b720b40e6ea78642fe250e942c564138d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":23,"id":"f84bb97e_21aef06a","updated":"2026-03-12 15:54:07.000000000","message":"recheck","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"5202a8bb2dbc9c86d489c625ba4ed1f6ef82a40a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":25,"id":"fc246407_1cc3cb2e","updated":"2026-03-13 15:54:03.000000000","message":"My -1 review disappeared, probably because new patch was uploaded meanwhile.","commit_id":"fb7ddb2593737e35c942f5f335a1cb794f47fa95"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"afdd4c4a293dfebd53ddb61446c3bbd6fbd8e492","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":28,"id":"b42baaec_9eba09d6","updated":"2026-03-14 12:29:40.000000000","message":"I just made a major shift back to a version that introduces a \"redirect\" to help\nfix the issue of the key pairs table/page not refreshing after key file\ndownload.\n\nIt is a bit of a major refactor - the code now represents a \"cleaned up\" version pushed at patchset 22 - only one \"redirect\" is now used","commit_id":"09361524c23feeeeec4d1c432044618911fd6484"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"573a6dad5f63048cf2af9ee6ab9cd7695a571685","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":30,"id":"b1db10f2_25458524","updated":"2026-03-17 13:40:17.000000000","message":"A little visual nitpick. When I open a chevron of the created KeyPair then the first two lines (Key Pair Name and Key Pair Type) are visible only as “Key Pair N…” and “Key Pair T…” In all sizes of window wider than 768px. Which is majority of today\u0027s computer displays I guess. And it looks quite odd. I think it would be good to remove “Key Pair” from the \u003cdt\u003e and leave only “Name” and “Type”. I think that it is obvious it is related to Keypair when it is under the chevron of Key Pair.","commit_id":"1695b78c83eb97c7c5c663957bed489cc20c87ec"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f30554a9fe06209e646f59b2d6037c27daf706b7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":30,"id":"307a26d2_9237b9d7","in_reply_to":"7161cf17_0fdc0965","updated":"2026-03-17 19:50:13.000000000","message":"Created patch https://review.opendev.org/c/openstack/horizon/+/981026","commit_id":"1695b78c83eb97c7c5c663957bed489cc20c87ec"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"b758e5f10fdcc91abd4283dfbe901528bd378e37","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":30,"id":"7161cf17_0fdc0965","in_reply_to":"b1db10f2_25458524","updated":"2026-03-17 15:24:17.000000000","message":"I think that should be a separate patch.","commit_id":"1695b78c83eb97c7c5c663957bed489cc20c87ec"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"fadce0c68ffff8cd4983b65ef5fc30f17be5297b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":32,"id":"608b9492_fd998aa2","updated":"2026-03-18 10:12:42.000000000","message":"Test test_dashboard_help_redirection failed because https://docs.openstack.org/2025.2/ is down again.\n\nLet\u0027s wait for a while and then recheck.","commit_id":"e6076c030d26764aa1ff8987683b5289225e4cef"}],"openstack_dashboard/dashboards/project/key_pairs/forms.py":[{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"61f746210b59f1a088433425adc558e0337911fe","unresolved":true,"context_lines":[{"line_number":66,"context_line":"            raise forms.ValidationError(_(\"Key pair name is required\"))"},{"line_number":67,"context_line":""},{"line_number":68,"context_line":"        # Validate name pattern (alphanumeric, hyphens, underscores)"},{"line_number":69,"context_line":"        if not re.match(r\u0027^[a-zA-Z0-9-_]+$\u0027, name):"},{"line_number":70,"context_line":"            raise forms.ValidationError("},{"line_number":71,"context_line":"                _(\"Key pair name can only contain letters, numbers, \""},{"line_number":72,"context_line":"                  \"hyphens, and underscores\")"}],"source_content_type":"text/x-python","patch_set":2,"id":"9b40958a_52053d47","line":69,"updated":"2025-12-02 13:15:19.000000000","message":"please make sure the \"-\" is at the end of the \"[]\" section of the regexp, otherwise it has special meaning","commit_id":"db25601446a5b58c358483f1528b4b342f8be396"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"db63c95d1d0f581414afa9358a41e9c44f7d4c21","unresolved":false,"context_lines":[{"line_number":66,"context_line":"            raise forms.ValidationError(_(\"Key pair name is required\"))"},{"line_number":67,"context_line":""},{"line_number":68,"context_line":"        # Validate name pattern (alphanumeric, hyphens, underscores)"},{"line_number":69,"context_line":"        if not re.match(r\u0027^[a-zA-Z0-9-_]+$\u0027, name):"},{"line_number":70,"context_line":"            raise forms.ValidationError("},{"line_number":71,"context_line":"                _(\"Key pair name can only contain letters, numbers, \""},{"line_number":72,"context_line":"                  \"hyphens, and underscores\")"}],"source_content_type":"text/x-python","patch_set":2,"id":"69151f53_8c4cac3d","line":69,"in_reply_to":"9b40958a_52053d47","updated":"2025-12-08 20:32:22.000000000","message":"Done","commit_id":"db25601446a5b58c358483f1528b4b342f8be396"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"db63c95d1d0f581414afa9358a41e9c44f7d4c21","unresolved":false,"context_lines":[{"line_number":107,"context_line":"            )"},{"line_number":108,"context_line":"            return False"},{"line_number":109,"context_line":""},{"line_number":110,"context_line":"KEYPAIR_NAME_REGEX \u003d re.compile(r\"^\\w+(?:[- ]\\w+)*$\", re.UNICODE)"},{"line_number":111,"context_line":"KEYPAIR_ERROR_MESSAGES \u003d {"},{"line_number":112,"context_line":"    \u0027invalid\u0027: _(\u0027Key pair name may only contain letters, \u0027"},{"line_number":113,"context_line":"                 \u0027numbers, underscores, spaces, and hyphens \u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"fe47d57f_6e3de435","line":110,"in_reply_to":"441feb8e_67943d0b","updated":"2025-12-08 20:32:22.000000000","message":"Done","commit_id":"db25601446a5b58c358483f1528b4b342f8be396"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"61f746210b59f1a088433425adc558e0337911fe","unresolved":true,"context_lines":[{"line_number":107,"context_line":"            )"},{"line_number":108,"context_line":"            return False"},{"line_number":109,"context_line":""},{"line_number":110,"context_line":"KEYPAIR_NAME_REGEX \u003d re.compile(r\"^\\w+(?:[- ]\\w+)*$\", re.UNICODE)"},{"line_number":111,"context_line":"KEYPAIR_ERROR_MESSAGES \u003d {"},{"line_number":112,"context_line":"    \u0027invalid\u0027: _(\u0027Key pair name may only contain letters, \u0027"},{"line_number":113,"context_line":"                 \u0027numbers, underscores, spaces, and hyphens \u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"441feb8e_67943d0b","line":110,"in_reply_to":"480a003d_210b6621","updated":"2025-12-02 13:15:19.000000000","message":"\u003e pep8: E305 expected 2 blank lines after class or function definition, found 1\n\nPlease fix.","commit_id":"db25601446a5b58c358483f1528b4b342f8be396"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f6f4e50a41bf6e9c0af324575b0b2570235fce3b","unresolved":true,"context_lines":[{"line_number":57,"context_line":"        help_text\u003d_(\"Type of key pair to generate (SSH is recommended)\")"},{"line_number":58,"context_line":"    )"},{"line_number":59,"context_line":""},{"line_number":60,"context_line":"    def clean_name(self):"},{"line_number":61,"context_line":"        \"\"\"Validate key pair name format.\"\"\""},{"line_number":62,"context_line":"        name \u003d self.cleaned_data.get(\u0027name\u0027)"},{"line_number":63,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"fe83d14e_92dd8d1e","line":60,"updated":"2025-12-16 14:51:33.000000000","message":"Validation parity with AngularJS differs here.\nThe AngularJS Key Pairs \"Create\" flow allows spaces in the keypair name, while\nthis Django Create form currently restricts names to [A-Za-z0-9_-].\nImportKeypair already uses KEYPAIR_NAME_REGEX (allows spaces/hyphens) and\nKEYPAIR_ERROR_MESSAGES[\u0027invalid\u0027] (\"... may not be white space.\").\nKeeping strict Create behavior for now until we decide on parity","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"83f4a8a67f64bf2c944d9c45569afe836fe7da83","unresolved":false,"context_lines":[{"line_number":57,"context_line":"        help_text\u003d_(\"Type of key pair to generate (SSH is recommended)\")"},{"line_number":58,"context_line":"    )"},{"line_number":59,"context_line":""},{"line_number":60,"context_line":"    def clean_name(self):"},{"line_number":61,"context_line":"        \"\"\"Validate key pair name format.\"\"\""},{"line_number":62,"context_line":"        name \u003d self.cleaned_data.get(\u0027name\u0027)"},{"line_number":63,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"7b5132d5_d112819a","line":60,"in_reply_to":"6c1b375e_e1802197","updated":"2026-01-19 21:10:55.000000000","message":"Thanks — agreed. Nova accepts spaces in keypair names, so in Patchset 12 we aligned the Django “Create/Generate” validation to allow spaces as well.\nI reused the existing Import form regex + error messaging (so Create/Import stay consistent) and still reject whitespace-only names (to preserve the “may not be white space” behavior).","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"f5a041e9c2f594bb56c4cfa6fb37cb57219b7fe9","unresolved":true,"context_lines":[{"line_number":57,"context_line":"        help_text\u003d_(\"Type of key pair to generate (SSH is recommended)\")"},{"line_number":58,"context_line":"    )"},{"line_number":59,"context_line":""},{"line_number":60,"context_line":"    def clean_name(self):"},{"line_number":61,"context_line":"        \"\"\"Validate key pair name format.\"\"\""},{"line_number":62,"context_line":"        name \u003d self.cleaned_data.get(\u0027name\u0027)"},{"line_number":63,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"6c1b375e_e1802197","line":60,"in_reply_to":"fe83d14e_92dd8d1e","updated":"2025-12-18 16:49:40.000000000","message":"Looks like Nova is fine with spaces in Key Pairs names","commit_id":"1893c92154ee021e560865515171000985b54636"}],"openstack_dashboard/dashboards/project/key_pairs/tables.py":[{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"8828d643f8da6dbb877bb0f1ff6a74141b72be31","unresolved":true,"context_lines":[{"line_number":97,"context_line":"        return True"},{"line_number":98,"context_line":""},{"line_number":99,"context_line":""},{"line_number":100,"context_line":"class CreateLinkNG(QuotaKeypairMixin, tables.LinkAction):"},{"line_number":101,"context_line":"    name \u003d \"create-keypair-ng\""},{"line_number":102,"context_line":"    verbose_name \u003d _(\"Create Key Pair\")"},{"line_number":103,"context_line":"    url \u003d \"horizon:project:key_pairs:index\""}],"source_content_type":"text/x-python","patch_set":15,"id":"49f2c0a3_c577ae6a","line":100,"updated":"2026-02-10 10:31:37.000000000","message":"I think this entire class can be removed?","commit_id":"356e7c2f0fbb5d354a7cc858bcb020d59b40a067"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"87fe2d2b4762fdee0bb48386e928bb5933181855","unresolved":false,"context_lines":[{"line_number":97,"context_line":"        return True"},{"line_number":98,"context_line":""},{"line_number":99,"context_line":""},{"line_number":100,"context_line":"class CreateLinkNG(QuotaKeypairMixin, tables.LinkAction):"},{"line_number":101,"context_line":"    name \u003d \"create-keypair-ng\""},{"line_number":102,"context_line":"    verbose_name \u003d _(\"Create Key Pair\")"},{"line_number":103,"context_line":"    url \u003d \"horizon:project:key_pairs:index\""}],"source_content_type":"text/x-python","patch_set":15,"id":"35b37324_8fffa31c","line":100,"in_reply_to":"49f2c0a3_c577ae6a","updated":"2026-03-11 02:33:18.000000000","message":"removed.","commit_id":"356e7c2f0fbb5d354a7cc858bcb020d59b40a067"}],"openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/download.html":[{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f6f4e50a41bf6e9c0af324575b0b2570235fce3b","unresolved":true,"context_lines":[{"line_number":3,"context_line":"{% block title %}{{ page_title }}{% endblock %}"},{"line_number":4,"context_line":""},{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"}],"source_content_type":"text/html","patch_set":11,"id":"486590f0_7d508605","line":6,"updated":"2025-12-16 14:51:33.000000000","message":"How much do we need the python implementation to match AngularJS UX Parity.\n- Do we need (must) we change to use a copy-to-clipboard vs. this page-load auto-download mechanism?\n\nAlso, with this page-load download mechanism - could we run into some sort of browswer restriction(s) with this approach?","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"f5a041e9c2f594bb56c4cfa6fb37cb57219b7fe9","unresolved":true,"context_lines":[{"line_number":3,"context_line":"{% block title %}{{ page_title }}{% endblock %}"},{"line_number":4,"context_line":""},{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"}],"source_content_type":"text/html","patch_set":11,"id":"52871ea0_bf78e724","line":6,"in_reply_to":"486590f0_7d508605","updated":"2025-12-18 16:49:40.000000000","message":"Well, we have both options implemented in AngularJS already :) The one that is visible to the user by default is the download one, so I assume we would want our python implementation match it.","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"83f4a8a67f64bf2c944d9c45569afe836fe7da83","unresolved":false,"context_lines":[{"line_number":3,"context_line":"{% block title %}{{ page_title }}{% endblock %}"},{"line_number":4,"context_line":""},{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"}],"source_content_type":"text/html","patch_set":11,"id":"c224c4b7_c1fe5b6f","line":6,"in_reply_to":"52871ea0_bf78e724","updated":"2026-01-19 21:10:55.000000000","message":"Agreed — in Patchset 12 we made the Django flow download-first, matching Angular’s default-visible intent.\nAfter Create, we redirect to a dedicated `/download/` page where \"Download Private Key\" is the primary action.\nWe also provide \"Copy to Clipboard\" as a secondary/fallback action (useful if download is blocked).","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"b409cec99b3b2f871e8198f1e0cfdcc4c27161e2","unresolved":true,"context_lines":[{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"},{"line_number":10,"context_line":"    \u003cp\u003e"},{"line_number":11,"context_line":"      {% trans \"To download your private key, click the button below. The download will only occur when you click the button.\" %}"}],"source_content_type":"text/html","patch_set":11,"id":"45b4abf7_5dcd0e6c","line":8,"updated":"2025-12-18 17:00:51.000000000","message":"Please add spaces around \"—\"","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"b409cec99b3b2f871e8198f1e0cfdcc4c27161e2","unresolved":true,"context_lines":[{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"},{"line_number":10,"context_line":"    \u003cp\u003e"},{"line_number":11,"context_line":"      {% trans \"To download your private key, click the button below. The download will only occur when you click the button.\" %}"}],"source_content_type":"text/html","patch_set":11,"id":"08794e19_b5365abc","line":8,"updated":"2025-12-18 17:00:51.000000000","message":"UX wise, I really like this extra warning. Now thinking: if we have an extra page for the download, should we display the private key as well? Not just matching one of the previous implementations, but having them both?","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"83f4a8a67f64bf2c944d9c45569afe836fe7da83","unresolved":false,"context_lines":[{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"},{"line_number":10,"context_line":"    \u003cp\u003e"},{"line_number":11,"context_line":"      {% trans \"To download your private key, click the button below. The download will only occur when you click the button.\" %}"}],"source_content_type":"text/html","patch_set":11,"id":"1111106f_978f5242","line":8,"in_reply_to":"08794e19_b5365abc","updated":"2026-01-19 21:10:55.000000000","message":"Yes — agreed. Since we already have the dedicated `/download/` page in the Django flow, Patchset 12 implements the combined UX:\n- keep the warning banner,\n- display the private key inline (readonly textarea),\n- provide \"Download\" (primary) and \"Copy\" (secondary/fallback),\n- and enforce one-time semantics (key is removed from session after the page is visited).","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"83f4a8a67f64bf2c944d9c45569afe836fe7da83","unresolved":false,"context_lines":[{"line_number":5,"context_line":"{% block main %}"},{"line_number":6,"context_line":"  \u003cdiv class\u003d\"alert alert-warning\"\u003e"},{"line_number":7,"context_line":"    \u003cp\u003e"},{"line_number":8,"context_line":"      {% blocktrans with name\u003dkeypair_name %}Your private key for \"{{ name }}\" is ready. This key can only be downloaded once—please save it now.{% endblocktrans %}"},{"line_number":9,"context_line":"    \u003c/p\u003e"},{"line_number":10,"context_line":"    \u003cp\u003e"},{"line_number":11,"context_line":"      {% trans \"To download your private key, click the button below. The download will only occur when you click the button.\" %}"}],"source_content_type":"text/html","patch_set":11,"id":"1e93f988_62fef90a","line":8,"in_reply_to":"45b4abf7_5dcd0e6c","updated":"2026-01-19 21:10:55.000000000","message":"Good catch — updated the warning text to add spaces around the em dash (` — `) for readability/typographic consistency.","commit_id":"1893c92154ee021e560865515171000985b54636"}],"openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/index.html":[{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"573a6dad5f63048cf2af9ee6ab9cd7695a571685","unresolved":true,"context_lines":[{"line_number":1,"context_line":"{% extends \u0027horizon/common/_data_table_view.html\u0027 %}"},{"line_number":2,"context_line":"{% load i18n %}"},{"line_number":3,"context_line":""},{"line_number":4,"context_line":"{% block main %}"},{"line_number":5,"context_line":"{% if download_pem_url %}"},{"line_number":6,"context_line":"\u003ciframe src\u003d\"{{ download_pem_url }}\" style\u003d\"display:none\" title\u003d\"{% trans \u0027Download key\u0027 %}\"\u003e\u003c/iframe\u003e"},{"line_number":7,"context_line":"{% endif %}"},{"line_number":8,"context_line":"{{ block.super }}"},{"line_number":9,"context_line":"{% endblock %}"}],"source_content_type":"text/html","patch_set":30,"id":"46a0c854_1cd136a4","line":9,"range":{"start_line":1,"start_character":0,"end_line":9,"end_character":14},"updated":"2026-03-17 13:40:17.000000000","message":"Hi @omcgonag@redhat.com,\nI am a little bit confused about this file. Where exactly is this template used?\nI can not find any template_name that is using this template. Is there some deeper logic that I am missing?\n\nYou made quite a lot of switches in this patch so I am not sure here if I am missing something or if this part of code is forgotten leftover. Could you please clarify it for me? I removed it (in my env) and your patch seems still working fine so I am not sure.","commit_id":"1695b78c83eb97c7c5c663957bed489cc20c87ec"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f30554a9fe06209e646f59b2d6037c27daf706b7","unresolved":false,"context_lines":[{"line_number":1,"context_line":"{% extends \u0027horizon/common/_data_table_view.html\u0027 %}"},{"line_number":2,"context_line":"{% load i18n %}"},{"line_number":3,"context_line":""},{"line_number":4,"context_line":"{% block main %}"},{"line_number":5,"context_line":"{% if download_pem_url %}"},{"line_number":6,"context_line":"\u003ciframe src\u003d\"{{ download_pem_url }}\" style\u003d\"display:none\" title\u003d\"{% trans \u0027Download key\u0027 %}\"\u003e\u003c/iframe\u003e"},{"line_number":7,"context_line":"{% endif %}"},{"line_number":8,"context_line":"{{ block.super }}"},{"line_number":9,"context_line":"{% endblock %}"}],"source_content_type":"text/html","patch_set":30,"id":"88b64e79_5f124cbc","line":9,"range":{"start_line":1,"start_character":0,"end_line":9,"end_character":14},"in_reply_to":"0ed8fffc_63bd7c4a","updated":"2026-03-17 19:50:13.000000000","message":"This file is a \"leftover\" from the earlier solution that used a \"hidden iframe\" to trigger the file download, this flow us no longer used, file is deleted.","commit_id":"1695b78c83eb97c7c5c663957bed489cc20c87ec"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"b758e5f10fdcc91abd4283dfbe901528bd378e37","unresolved":true,"context_lines":[{"line_number":1,"context_line":"{% extends \u0027horizon/common/_data_table_view.html\u0027 %}"},{"line_number":2,"context_line":"{% load i18n %}"},{"line_number":3,"context_line":""},{"line_number":4,"context_line":"{% block main %}"},{"line_number":5,"context_line":"{% if download_pem_url %}"},{"line_number":6,"context_line":"\u003ciframe src\u003d\"{{ download_pem_url }}\" style\u003d\"display:none\" title\u003d\"{% trans \u0027Download key\u0027 %}\"\u003e\u003c/iframe\u003e"},{"line_number":7,"context_line":"{% endif %}"},{"line_number":8,"context_line":"{{ block.super }}"},{"line_number":9,"context_line":"{% endblock %}"}],"source_content_type":"text/html","patch_set":30,"id":"0ed8fffc_63bd7c4a","line":9,"range":{"start_line":1,"start_character":0,"end_line":9,"end_character":14},"in_reply_to":"46a0c854_1cd136a4","updated":"2026-03-17 15:24:17.000000000","message":"That file is not used anywhere as far as I can tell. I must have been used in the old key pair create code.","commit_id":"1695b78c83eb97c7c5c663957bed489cc20c87ec"}],"openstack_dashboard/dashboards/project/key_pairs/views.py":[{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"4c26ff2c50d47d1354012bdd13eddd8347b8a564","unresolved":true,"context_lines":[{"line_number":90,"context_line":"        context[\u0027submit_url\u0027] \u003d self.submit_url"},{"line_number":91,"context_line":"        return context"},{"line_number":92,"context_line":""},{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("}],"source_content_type":"text/x-python","patch_set":23,"id":"bd633dbf_ddff14d2","line":93,"range":{"start_line":93,"start_character":8,"end_line":93,"end_character":18},"updated":"2026-03-13 15:51:51.000000000","message":"This seems to be a side effect of the issue I described on line 96 rather than a direct logic change in this patch.\n\nWhen I create a keypair, buttons “Create Key Pair” and “Import Public Key” are “grey” (unclickable) when I hover over them by mouse, there is a “stop sign cursor” but both buttons are still clickable.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f30554a9fe06209e646f59b2d6037c27daf706b7","unresolved":false,"context_lines":[{"line_number":90,"context_line":"        context[\u0027submit_url\u0027] \u003d self.submit_url"},{"line_number":91,"context_line":"        return context"},{"line_number":92,"context_line":""},{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("}],"source_content_type":"text/x-python","patch_set":23,"id":"21832f03_b99644fc","line":93,"range":{"start_line":93,"start_character":8,"end_line":93,"end_character":18},"in_reply_to":"04aa14f8_aa8ca8eb","updated":"2026-03-17 19:50:13.000000000","message":"Done","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"931a4ae4768d57283dad7d0c7a8b362caaddfd30","unresolved":true,"context_lines":[{"line_number":90,"context_line":"        context[\u0027submit_url\u0027] \u003d self.submit_url"},{"line_number":91,"context_line":"        return context"},{"line_number":92,"context_line":""},{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("}],"source_content_type":"text/x-python","patch_set":23,"id":"04aa14f8_aa8ca8eb","line":93,"range":{"start_line":93,"start_character":8,"end_line":93,"end_character":18},"in_reply_to":"28dff166_a7af3b4b","updated":"2026-03-16 15:38:02.000000000","message":"Please see previous comment about revert to patchset 23","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"3c2a575b078de443243e147817b98ea17a37f45f","unresolved":true,"context_lines":[{"line_number":90,"context_line":"        context[\u0027submit_url\u0027] \u003d self.submit_url"},{"line_number":91,"context_line":"        return context"},{"line_number":92,"context_line":""},{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("}],"source_content_type":"text/x-python","patch_set":23,"id":"28dff166_a7af3b4b","line":93,"range":{"start_line":93,"start_character":8,"end_line":93,"end_character":18},"in_reply_to":"bd633dbf_ddff14d2","updated":"2026-03-13 22:40:12.000000000","message":"Nice catch Jan. \n\nI could not find a way to get the page refreshed without me going back to a version I had running, using redirects, as part of patchset 22.\n\nI had recently changed the code to remove a few redirects (patchset 23), and use a simpler model that did not create a session, did not store a key on the server, and went straight from Nova to the browser file donwload.\n\nThis is why we do not see the key in the table, and the buttons not being refreshed.\n\nI just updated the review with a cleaner version of the patch 22 \"redirect\" implemntation to a single redirect that refreshes the table and buttons.\n\nThe largest difference is that we now store the session key on the server, which gets removed after user downloads the key.\n\nI still need to verify: if the user cancels the Save dialog or abandons the flow, are temporary keys cleaned up correctly?\n\nPlease re-test and let me know if you notice anything else.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"4c26ff2c50d47d1354012bdd13eddd8347b8a564","unresolved":true,"context_lines":[{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("},{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"}],"source_content_type":"text/x-python","patch_set":23,"id":"86440001_ade786aa","line":96,"range":{"start_line":96,"start_character":12,"end_line":96,"end_character":29},"updated":"2026-03-13 15:51:51.000000000","message":"This replace redirect with a file download. When I create a keypair and download it, the keypair is not visible in the page (tab) until I manually refresh a web page, which can be quite confusing. Is it possible to show it in the tab immediately after creation?","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"931a4ae4768d57283dad7d0c7a8b362caaddfd30","unresolved":true,"context_lines":[{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("},{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"}],"source_content_type":"text/x-python","patch_set":23,"id":"b8440210_5d0524ad","line":96,"range":{"start_line":96,"start_character":12,"end_line":96,"end_character":29},"in_reply_to":"25f6a0d2_3d1a8020","updated":"2026-03-16 15:38:02.000000000","message":"Yes - adding this line fixes the refresh - I put things back to patchset 23 with the soulution without a called out redirect, where we do not store the key in a \"session\" before download.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"3c2a575b078de443243e147817b98ea17a37f45f","unresolved":true,"context_lines":[{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("},{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"}],"source_content_type":"text/x-python","patch_set":23,"id":"e5646a2a_e1253002","line":96,"range":{"start_line":96,"start_character":12,"end_line":96,"end_character":29},"in_reply_to":"86440001_ade786aa","updated":"2026-03-13 22:40:12.000000000","message":"see previous comment.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f30554a9fe06209e646f59b2d6037c27daf706b7","unresolved":false,"context_lines":[{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("},{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"}],"source_content_type":"text/x-python","patch_set":23,"id":"49829789_7087764f","line":96,"range":{"start_line":96,"start_character":12,"end_line":96,"end_character":29},"in_reply_to":"b8440210_5d0524ad","updated":"2026-03-17 19:50:13.000000000","message":"Done","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"680f11ea1578e6bcdf1aef02afedd15ccc032178","unresolved":true,"context_lines":[{"line_number":93,"context_line":"    def form_valid(self, form):"},{"line_number":94,"context_line":"        response \u003d super().form_valid(form)"},{"line_number":95,"context_line":"        if form.keypair_private_key is not None:"},{"line_number":96,"context_line":"            response \u003d render("},{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"}],"source_content_type":"text/x-python","patch_set":23,"id":"25f6a0d2_3d1a8020","line":96,"range":{"start_line":96,"start_character":12,"end_line":96,"end_character":29},"in_reply_to":"e5646a2a_e1253002","updated":"2026-03-16 09:34:22.000000000","message":"I think adding `response[\u0027X-Horizon-Location\u0027] \u003d self.success_url` fixes this, by making Horizon reload the page after the modal closes.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"4c26ff2c50d47d1354012bdd13eddd8347b8a564","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"48a3681d_1be7d7d0","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"updated":"2026-03-13 15:51:51.000000000","message":"Does type like this exist? Maybe it should be text/plain?","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"f30554a9fe06209e646f59b2d6037c27daf706b7","unresolved":false,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"b27935e2_42bf5f4d","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"in_reply_to":"3317f65a_df1287cc","updated":"2026-03-17 19:50:13.000000000","message":"Keeping it at text/plain","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"3c2a575b078de443243e147817b98ea17a37f45f","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"a97c6675_e7ae9a47","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"in_reply_to":"48a3681d_1be7d7d0","updated":"2026-03-13 22:40:12.000000000","message":"this code has been refactored with new redirect implementation","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"f3664d55d465e23de86df9b54fc9e31c1358542f","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"3317f65a_df1287cc","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"in_reply_to":"77ec5cff_252f471a","updated":"2026-03-17 06:29:11.000000000","message":"That is exactly that. A certificate is a key, at least when asymmetric cryptography is used, like it is here. But it really doesn\u0027t matter in this case what mime type we use.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"edfb57b16d643a7fe658d0ee0ad41b6aa79e1a17","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"77ec5cff_252f471a","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"in_reply_to":"7875ec46_c06e6189","updated":"2026-03-16 17:54:12.000000000","message":"The RFC states that this content type \"Carries a cryptographic certificate and its\nassociated certificate chain.\" That seems a bit different than a \"private key\".\nMaybe we revisit this idea/change outside of this review?","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"931a4ae4768d57283dad7d0c7a8b362caaddfd30","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"aed4fd76_34912cb4","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"in_reply_to":"a97c6675_e7ae9a47","updated":"2026-03-16 15:38:02.000000000","message":"changed it to test/plain","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"4dbf68fac9bf765fe64299d5391a3c13359fd41c","unresolved":true,"context_lines":[{"line_number":97,"context_line":"                self.request,"},{"line_number":98,"context_line":"                \u0027project/key_pairs/private_key.pem\u0027,"},{"line_number":99,"context_line":"                {\u0027key\u0027: form.keypair_private_key},"},{"line_number":100,"context_line":"                content_type\u003d\u0027application/text\u0027,"},{"line_number":101,"context_line":"            )"},{"line_number":102,"context_line":"            response[\u0027Content-Disposition\u0027] \u003d ("},{"line_number":103,"context_line":"                \u0027attachment; filename\u003d%s.pem\u0027 % slugify(form.keypair_name))"}],"source_content_type":"text/x-python","patch_set":23,"id":"7875ec46_c06e6189","line":100,"range":{"start_line":100,"start_character":30,"end_line":100,"end_character":46},"in_reply_to":"aed4fd76_34912cb4","updated":"2026-03-16 15:45:40.000000000","message":"We could actually be more specific and use application/pem-certificate-chain https://www.iana.org/assignments/media-types/application/pem-certificate-chain but we would need to see if the browser doesn\u0027t do something stupid with it.","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"a4f0549a0bd5ca373e4503bfd02aea5abb02a60c","unresolved":true,"context_lines":[{"line_number":118,"context_line":"            msg \u003d _(\u0027Unable to retrieve details for keypair \"%s\".\u0027)\\"},{"line_number":119,"context_line":"                % (self.kwargs[\u0027keypair_name\u0027])"},{"line_number":120,"context_line":"            exceptions.handle(self.request, msg,"},{"line_number":121,"context_line":"                              redirect\u003dredirect_url)"},{"line_number":122,"context_line":"        return keypair"},{"line_number":123,"context_line":""},{"line_number":124,"context_line":"    def get_context_data(self, **kwargs):"}],"source_content_type":"text/x-python","patch_set":23,"id":"309f198b_f63f8c5b","line":121,"updated":"2026-03-13 07:32:52.000000000","message":"This change seems to be unrelated to the rest? Is it needed?","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"863f069a9562a92b38db82627edfbf01c238165e","unresolved":false,"context_lines":[{"line_number":118,"context_line":"            msg \u003d _(\u0027Unable to retrieve details for keypair \"%s\".\u0027)\\"},{"line_number":119,"context_line":"                % (self.kwargs[\u0027keypair_name\u0027])"},{"line_number":120,"context_line":"            exceptions.handle(self.request, msg,"},{"line_number":121,"context_line":"                              redirect\u003dredirect_url)"},{"line_number":122,"context_line":"        return keypair"},{"line_number":123,"context_line":""},{"line_number":124,"context_line":"    def get_context_data(self, **kwargs):"}],"source_content_type":"text/x-python","patch_set":23,"id":"984e8ddb_c466106a","line":121,"in_reply_to":"309f198b_f63f8c5b","updated":"2026-03-13 15:51:08.000000000","message":"Done","commit_id":"4ccdf1bef7cf348bc6fb2831dbc24f11c66fb716"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"4dbf68fac9bf765fe64299d5391a3c13359fd41c","unresolved":true,"context_lines":[{"line_number":120,"context_line":"            msg \u003d _(\u0027Unable to retrieve details for keypair \"%s\".\u0027)\\"},{"line_number":121,"context_line":"                % (self.kwargs[\u0027keypair_name\u0027])"},{"line_number":122,"context_line":"            exceptions.handle(self.request, msg,"},{"line_number":123,"context_line":"                              redirect\u003dredirect_url)"},{"line_number":124,"context_line":"        return keypair"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    def get_context_data(self, **kwargs):"}],"source_content_type":"text/x-python","patch_set":29,"id":"f416ccbf_7c96a6db","line":123,"updated":"2026-03-16 15:45:40.000000000","message":"This snuck in again.","commit_id":"fd608d4369fab9fb33cd8ef72c0f055b67f887b5"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"edfb57b16d643a7fe658d0ee0ad41b6aa79e1a17","unresolved":false,"context_lines":[{"line_number":120,"context_line":"            msg \u003d _(\u0027Unable to retrieve details for keypair \"%s\".\u0027)\\"},{"line_number":121,"context_line":"                % (self.kwargs[\u0027keypair_name\u0027])"},{"line_number":122,"context_line":"            exceptions.handle(self.request, msg,"},{"line_number":123,"context_line":"                              redirect\u003dredirect_url)"},{"line_number":124,"context_line":"        return keypair"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    def get_context_data(self, **kwargs):"}],"source_content_type":"text/x-python","patch_set":29,"id":"e7ff07c0_55d88595","line":123,"in_reply_to":"f416ccbf_7c96a6db","updated":"2026-03-16 17:54:12.000000000","message":"fixed.","commit_id":"fd608d4369fab9fb33cd8ef72c0f055b67f887b5"}],"openstack_dashboard/dashboards/settings/password/tests.py":[{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"9bc31ab3d016bbf125b1cf9574c67e0c79f68bda","unresolved":true,"context_lines":[{"line_number":67,"context_line":"                         \"Password changed. Please log in again to continue.\")"},{"line_number":68,"context_line":"        self.assertEqual(\u0027success\u0027, res.cookies[\u0027logout_status\u0027].value)"},{"line_number":69,"context_line":"        scheme, netloc, path, query, fragment \u003d urlsplit(res.url)"},{"line_number":70,"context_line":"        # Django 5.2+ treats the logout endpoint as POST-only."},{"line_number":71,"context_line":"        redirect_response \u003d res.client.post("},{"line_number":72,"context_line":"            path,"},{"line_number":73,"context_line":"            http.QueryDict(query),"}],"source_content_type":"text/x-python","patch_set":11,"id":"3caf3421_ca8361ee","line":70,"updated":"2025-12-18 16:51:59.000000000","message":"Thanks for fixing that, but it definitely belongs in a separate patch","commit_id":"1893c92154ee021e560865515171000985b54636"},{"author":{"_account_id":32432,"name":"Owen McGonagle","email":"omcgonag@redhat.com","username":"omcgonag"},"change_message_id":"83f4a8a67f64bf2c944d9c45569afe836fe7da83","unresolved":false,"context_lines":[{"line_number":67,"context_line":"                         \"Password changed. Please log in again to continue.\")"},{"line_number":68,"context_line":"        self.assertEqual(\u0027success\u0027, res.cookies[\u0027logout_status\u0027].value)"},{"line_number":69,"context_line":"        scheme, netloc, path, query, fragment \u003d urlsplit(res.url)"},{"line_number":70,"context_line":"        # Django 5.2+ treats the logout endpoint as POST-only."},{"line_number":71,"context_line":"        redirect_response \u003d res.client.post("},{"line_number":72,"context_line":"            path,"},{"line_number":73,"context_line":"            http.QueryDict(query),"}],"source_content_type":"text/x-python","patch_set":11,"id":"8c3ae2c4_cd3a55ee","line":70,"in_reply_to":"3caf3421_ca8361ee","updated":"2026-01-19 21:10:55.000000000","message":"Agreed — thanks. I’ll drop the unrelated password/logout test change from this review and follow up with a separate patch/review focused only on that Django 5.2+ test behavior.\nFor Patchset 12, we keep the diff focused on the Key Pairs de-angularization changes.","commit_id":"1893c92154ee021e560865515171000985b54636"}]}
